Use Spark Streaming to consume data in real time
Use Spark Structured Streaming and the DataFrame API to build a streaming job that reads log data from Simple Log Service (SLS). This topic provides working code samples for both Scala and PySpark, along with build and submission instructions.
Prerequisites
Before you begin, ensure that you have:
An E-MapReduce (EMR) cluster with Spark installed
A Simple Log Service project and Logstore containing data to consume
Your Alibaba Cloud AccessKey ID and AccessKey secret
Apache Maven installed (for the Scala sample)
How it works
All samples use the loghub format — the Spark DataSource for Simple Log Service — to connect Structured Streaming to an SLS Logstore. The general flow is:
Create a streaming read from the
loghubsource with your SLS credentials and connection options.Apply a transformation on the streaming Dataset.
Write the output stream to a sink (Parquet files or console).
The EMR cluster ships with the loghub DataSource JAR pre-installed at /opt/apps/SPARK-EXTENSION/spark-extension-current/. Pass this JAR to spark-submit using --jars. Omitting --jars causes java.lang.ClassNotFoundException: loghub.DefaultSource.
Scala sample
Sample code
The following example reads from an SLS Logstore, casts log values to strings, and writes output as Parquet files with a 30-second processing trigger.
object StructuredLoghubSample {
def main(args: Array[String]) {
if (args.length < 7) {
System.err.println("Usage: StructuredLoghubSample <logService-project> " +
"<logService-store> <access-key-id> <access-key-secret> <endpoint> " +
"<starting-offsets> <max-offsets-per-trigger>[outputPath] [<checkpoint-location>]")
System.exit(1)
}
val Array(project, logStore, accessKeyId, accessKeySecret, endpoint, startingOffsets, maxOffsetsPerTrigger, outputPath, _*) = args
val checkpointLocation =
if (args.length > 8) args(8) else "/tmp/temporary-" + UUID.randomUUID.toString
val spark = SparkSession
.builder
.appName("StructuredLoghubSample")
.getOrCreate()
import spark.implicits._
// Create DataSet representing the stream of input lines from loghub
val lines = spark
.readStream
.format("loghub")
.option("sls.project", project)
.option("sls.store", logStore)
.option("access.key.id", accessKeyId)
.option("access.key.secret", accessKeySecret)
.option("endpoint", endpoint)
.option("startingoffsets", startingOffsets)
.option("maxOffsetsPerTrigger", maxOffsetsPerTrigger)
.load()
.selectExpr("CAST(__value__ AS STRING)")
.as[String]
val query = lines.writeStream
.format("parquet")
.option("checkpointLocation", checkpointLocation)
.option("path", outputPath)
.outputMode("append")
.trigger(Trigger.ProcessingTime(30000))
.start()
query.awaitTermination()
}
}For the Maven POM file, see aliyun-emapreduce-demo.
Build and submit
Step 1: Build the JAR.
mvn clean package -DskipTestsThe compiled JAR is placed in the target/ directory.
Step 2: Submit the job.
spark-submit \
--master --master yarn \
--deploy-mode cluster \
--executor-cores 2 \
--executor-memory 1g \
--driver-memory 1g \
--num-executors 2 \
--jars /opt/apps/SPARK-EXTENSION/spark-extension-current/spark2-emrsdk/emr-datasources_shaded_2.11-2.3.1.jar \
--class x.x.x.StructuredLoghubSample xxx.jar \
<logService-project> <logService-store> <access-key-id> <access-key-secret> <endpoint> \
<starting-offsets><max-offsets-per-trigger> <output-path> <checkpoint-location>Replace the placeholders:
| Placeholder | Description | Example |
|---|---|---|
x.x.x.StructuredLoghubSample | Fully qualified class name (x.x.x is your package name) | com.example.StructuredLoghubSample |
xxx.jar | Your project JAR file | my-spark-job-1.0.jar |
<starting-offsets> | Starting position for consumption | earliest or latest |
<max-offsets-per-trigger> | Maximum number of messages consumed per trigger interval | 10000 |
<output-path> | Directory for output data | /loghub/data/ |
<checkpoint-location> | Directory for checkpoint data | /loghub/checkpoint |
Adjust resource settings (--executor-cores, --executor-memory, --num-executors) based on your actual data volume and cluster capacity.
--jars path by Spark version:
| Spark version | JAR path |
|---|---|
| Spark 2 | /opt/apps/SPARK-EXTENSION/spark-extension-current/spark2-emrsdk/emr-datasources_shaded_2.11-2.3.1.jar |
| Spark 3 | /opt/apps/SPARK-EXTENSION/spark-extension-current/spark3-emrsdk/emr-datasources_shaded_2.12-3.0.2.jar |
If the path above does not exist in your cluster, use
/usr/lib/emrsdk-current/instead.
PySpark sample
Sample code
The following example reads from an SLS Logstore and counts log entries by Logstore name.
The sample uses hardcoded placeholder credentials. In production, read credentials from environment variables to avoid exposing sensitive information in your code.
from pyspark.sql import SparkSession
spark = SparkSession \
.builder \
.appName("xx") \
.getOrCreate()
# Read data from the LogHub data source
lines = spark \
.readStream \
.format("loghub") \
.option("endpoint", "cn-hangzhou-intranet.log.aliyuncs.com") \
.option("access.key.id", "LTAI----") \
.option("access.key.secret", "DTi----") \
.option("sls.project", "emr-test-hz-1") \
.option("sls.store", "test1") \
.option("startingoffsets", "earliest") \
.load()
# Apply transformation
wordCounts = lines.groupBy("__logStore__").count()
# Write to console sink
query = wordCounts \
.writeStream \
.outputMode("complete") \
.format("console") \
.start()
query.awaitTermination()Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
endpoint | Yes | SLS endpoint for your region | cn-hangzhou-intranet.log.aliyuncs.com |
access.key.id | Yes | Your Alibaba Cloud AccessKey ID | LTAI5tXxx |
access.key.secret | Yes | Your Alibaba Cloud AccessKey secret | xXxXxXx |
sls.project | Yes | The name of the Logstore | emr-test-hz-1 |
sls.store | Yes | The name of the Log Service project | test1 |
startingoffsets | Yes | Starting position for data consumption. Valid values: earliest, latest | earliest |
Run the script
Save the sample as loghub.py, then submit it with --jars pointing to the loghub DataSource JAR.
| Spark version | Command |
|---|---|
| Spark 2 | spark-submit --jars /opt/apps/SPARK-EXTENSION/spark-extension-current/spark2-emrsdk/emr-datasources_shaded_2.11-2.3.1.jar --master local loghub.py |
| Spark 3 | spark-submit --jars /opt/apps/SPARK-EXTENSION/spark-extension-current/spark3-emrsdk/emr-datasources_shaded_2.12-3.0.2.jar --master local loghub.py |
If the path above does not exist in your cluster, use
/usr/lib/emrsdk-current/instead.