Spark Streaming:通过Dstreams 或 DataFrames做流数据处理,结果写入ClickHouse或Hive表

       Apache Spark 当前最流行的大数据处理框架之一。最初它是作为替代 Hadoop 的 MapReduce 批处理框架而创建的,但现在它也支持 SQL、机器学习和流处理。今天我们重点看看 Spark Streaming,展示常用流处理的方式。

        流数据处理常出现在大数据用例中,用于连续生成动态数据的场景。在大多数情况下,数据以近似实时的方式处理,一次一条记录,处理结果用于提供及时的报警、呈现在仪表板上或者提供给机器学习模型,让我们基于数据的变化快速做出反应。

Dstreams 和 Dataframes

        Spark Streaming 在 Spark 0.7.0 中进入 alpha 阶段。它基于离散流或 DStreams 的思想。每个 DStream 表示的是一系列 RDD,如果用户已经熟悉基于RDD的批处理,这种方式很容易使用。目前DStreams 经历了很多改进,但仍然存在各种挑战,主要是因为它是低层次的 API。

        作为这些挑战的解决方案,Spark Structured Streaming 在 Spark 2.0 中被引入(并在 2.2 中变得稳定),作为构建在 Spark SQL 之上的扩展。它利用了 Spark SQL 相关的代码和内存优化方法。Structured Streaming还提供了非常强大的抽象,如Dataset/DataFrame APIs以及 SQL,不再是直接处理 RDD。

        Structured Streaming 和 Streaming with DStreams 都使用微批处理。最大的区别在于延迟和一致性保证:Structured Streaming一般能做到 100毫秒左右的延迟,保重处理并且只处理一次;DStreams 方法仅保证至少处理一次,但延迟可以做到1毫秒左右。

        对于简单的使用场景,个人更喜欢用 Spark Structured Streaming,DStream非常适合更复杂的处理逻辑,因为它灵活性更高。下面先介绍如何使用 Streaming with DStreams 来消费和处理来自 Apache Kafka 的数据。使用的版本是Spark 2.4 和 Apache Kafka 2.0。做后说一下如何将处理结果插入ClickHouse数据库中。下一篇文章会介绍通过Streaming with DataFrames处理流式数据的方法。

第一步:初始化Spark StreamingContext
SparkConf conf = (new SparkConf()).setAppName("SparkStreamingDemo");
JavaStreamingContext jssc = new JavaStreamingContext(conf, Durations.seconds(5));
jssc.sparkContext().setLogLevel("INFO");
第二步:初始化kafka连接
Map kafkaParams = new HashMap<>();
kafkaParams.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "10.20.43.86:9292");
kafkaParams.put(ConsumerConfig.GROUP_ID_CONFIG, "TestGroupId");
kafkaParams.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
kafkaParams.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
kafkaParams.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
kafkaParams.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
第三步:得到从kafka获取的输入数据流
JavaInputDStream> inputStream =
    KafkaUtils.createDirectStream(
         jssc,LocationStrategies.PreferConsistent(),
         ConsumerStrategies.Subscribe(appLogTopics, kafkaParams)
    );
第四步:以Dstream的方式处理数据,支持类似RDD的map、flatmap、filter、reduce、reduceByKey等
JavaDStream> logListRdd = inputStream.map(record -> {
    String value = record.value();
    List ret = new LinkedList<>();
    try {
        JSONObject obj = JSONObject.parseObject(value);
        JSONArray jsonArray = obj.getJSONArray("data");
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject aLog = jsonArray.getJSONObject(i);
            ApiDurationCount apiDurationCountLocal = new ApiDurationCount();
            apiDurationCountLocal.setApi(apiPattern(aLog.getString("uri")));
            apiDurationCountLocal.setCount(1L);
            apiDurationCountLocal.setDuration((long) (aLog.getDouble("duration") * 1000));
            ret.add(apiDurationCountLocal);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return ret;
});
上面的代码有点长, 因为要处理的数据是一个复杂的json结果,需要解析Json然后转换成ApiDurationCount这个自定义Class.根据业务需要还可以加入其它处理逻辑。

第五步:将处理出的结果写入ClickHouse数据库,通过JDBC的方式。

dStream.foreachRDD(rdd -> {
     List toOutput = rdd.collect();
     Connection conn = initClickHouseJdbc(ckJdbcUrl, ckJdbcUser, ckJdbcPw);
     executeUpdateQuery(conn, toOutput);
     });

将数据输出的时候,要注意建立数据库连接和插入数据的操作,是在每个并行的worker中执行,还是在Spark Job的Driver中执行。上面的代码中通过collect()操作将数据都汇集到Driver的内存中,然后建立到ClickHouse的连接,然后将List结构的数据批量插入ClickHouse。

private static Connection initClickHouseJdbc(String url, String user, String pw) {
    Connection connection = null;
    try {
        Class.forName("ru.yandex.clickhouse.ClickHouseDriver");
        connection = DriverManager.getConnection(url, user, pw);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return connection;
}

上面是建立到ClickHouse的Jdbc连接的代码。

private static boolean executeUpdateQuery(Connection conn, List rows) {
    PreparedStatement ps = null;
    try {
        ps = conn.prepareStatement(
                "INSERT INTO pokeck.monitor_api_status_count_local (InsertTime, Api, Status, Duration, Num) VALUES (?, ?, ?, ?, ?)");
        for (ApiDurationCount arow : rows) {
            ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
            ps.setString(2, arow.getApi());
            ps.setString(3, arow.getStatus());
            ps.setLong(4, arow.getDuration());
            ps.setLong(5, arow.getCount());
            ps.addBatch();
        }
        ps.clearParameters();
        ps.executeBatch();
        return true;
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
    return false;
}

上面的是执行批量插入的操作的函数。

本次内容就介绍到这里,欢迎通过留言或私信沟通交流、批评指正。

你可能感兴趣的:(实时系统,数据挖掘算法,spark,hive,大数据)