SparkStreaming消费Kafka保证一次语义总结 Java代码

7 篇文章 0 订阅
6 篇文章 2 订阅

备注:当前技术总结在两年前就已经完成,仅针对2年前版本负责,目前完成该文档,为了记录后续工作可能存在需要,避免遗忘,故而行程文档记录。

一、环境版本:

CDH:5.15.2
Kafka:Kafka_2.1.1-0.10.0.0
Spark:2.3.0
SparkStreaming:2.3.0

下列三种使用方案,官方文档均有文档解释。

目前方案介绍分别为三种:
    第一种方案:
        使用 ssc.checkpoint("/sparkStreaming_consume_kafka/CP"); 方法,当前方法的弊端为偏移量数据存储在Hdfs文件夹目录上,第一最小的隐患会有很多小文件问题,
        但是这并不是一次语义数据丢失最严重的的问题,小问题,可以靠内存解决,HDFS性能会下降,但是数据丢失最为致命。
        问题1、当如果程序发生了升级会产生问题,之前的偏移量无法使用,并且必须删除掉checkpoint文件夹目录数据,否则升级程序无效(因为使用checkpoint会将运行代码 进行转换提交到HDFS目录,进行运行)。
        问题2、如果解决前两个问题,当程序并未升级,删除掉checkpoint目录,当前kafka消费者宕机在两天前,删除了删除掉checkpoint目录,再次启动必定会读取前几天历史数据,会造成数据挤压,一次消费过多
        直接内存溢出。
        问题3、上述问题1,2解决方案,删除checkpoint目录,切换kafka 的Topics消费者组,可以解决当前这个问题,但是数据会只会从最新的地方消费,之前的数据会造成丢失。

备注【说明】:
      //关闭消费之偏移量自动提交
      kafkaParams.put("enable.auto.commit", false);
      |=========================================================================================================|
      |  //设置Kafka的消费方式 ,一共3种                                                                             |
      |  earliest       当前各个分区下已经有提交的offset时,从提交的offset 开始消费,无提交的offset时,从头消费                     |
      |  latest         当前各个分区下已经有提交的offset时,从提交的offset 开始消费,无提交的offset时,消费最新产生的该分区下的数据。|
      |  none(不推荐)  当前各个分区下都存在已提交的offset时,从offset 后开始消费,只要有一个分区不存在已经提交的offset,则抛出异常。  |
      |  但是在实际生产环境中,使用的最多的就是使用latest,没有人使用none,earliest 都存在各自的弊端。                    |
      | =========================================================================================================|
      kafkaParams.put("auto.offset.reset", "latest");

        //从提交的Kafka 偏移量开始消费 如果偏移量文件夹 不存在 即在最新的数据进行消费 ,如果使用earliest 会从数据源头开始消费,会造成数据量巨大,并且内存溢出(需要开启数据反压机制)。
        //如果配置为earliest  Kafka 默认会读取所有数据 从头开始 ,然后内存溢出 ,需要测试,如果修改为自动提交,看看kafka 会不会从 ssc.checkpoint 中读取偏移量 然后增量添加
        //经过测试后验证,开启代码手动提交,然后默认  使用earliest 进行偏移量控制, 可以进行增量读取,并且不会数据丢失。


异常备注说明:当前如果记录了偏移量数据,当时当前记录的偏移量为3天之前,但是Kafka的存储数据为三天时间,必须清空偏移量,从有记录的三天前的启示时间开始消费,否则查找三天之前的偏移量,数据不存在,程序会发生异常。



样例代码如下:

package com.chepai;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka010.ConsumerStrategies;
import org.apache.spark.streaming.kafka010.KafkaUtils;
import org.apache.spark.streaming.kafka010.LocationStrategies;

import java.util.*;

/**
 *  sparkStreaming消费kafka数据1分钟调用一次
 *  处理车牌数据,进行车怕布控 废弃版本
 *  作者 liuwunan
 */

public class ChePaiDeploy {

    public static void main(String[] args) {
        //初始化spark
        SparkConf conf = new SparkConf().setAppName("ChePaiDeploy")
                                        .set("spark.dynamicAllocation.enabled", "false")
                    .set("spark.serializer","org.apache.spark.serializer.KryoSerializer");

        System.setProperty("es.set.netty.runtime.available.processors","false");
        //代表启用线程数量,根据数据量的级别进行设置,但是如果调用 .coalesce(1,true) 
		//就是就当前线程数数据进行数据汇聚,
        // 如果启用10个线程数,但是在最终调用时一个合并文件,就会造成资源浪费,一个线程离线对应的一个Task,
		//当前为5线程
        conf.setMaster("local[5]");
        JavaSparkContext sc = new JavaSparkContext(conf);
        sc.setLogLevel("ERROR");
        JavaStreamingContext ssc = new JavaStreamingContext(sc, Durations.seconds(60));
		//偏移量记录在Hdfs文件系统中。项目升级,需要删除当前文件夹。
        ssc.checkpoint("/sparkStreaming_consume_kafka/LinZaiDeploy/CPD");
        //配置kafka参数(节点、消费者组、topic)
        String groupId = "group-05";                                               //指定消费者组id
        String topics = "topics";                                                  //指定topic
        String brokers = "IP:9092,IP:9092,IP:9092";     						   //指定kafka的IP地址
        Set<String> topicsSet = new HashSet<>(Arrays.asList(topics.split(",")));
        Map<String, Object> kafkaParams = new HashMap<>();
        kafkaParams.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers);
        kafkaParams.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        kafkaParams.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        kafkaParams.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
     
        kafkaParams.put("auto.offset.reset", "latest");
        //关闭消费之偏移量自动提交
        kafkaParams.put("enable.auto.commit", "false");

        //链接kafka,获得DStream对象
        JavaInputDStream<ConsumerRecord<String, String>> messages = KafkaUtils.createDirectStream
                (ssc, LocationStrategies.PreferConsistent(), ConsumerStrategies.<String, String>Subscribe(topicsSet, kafkaParams));

        JavaDStream<String> map = messages.map(new Function<ConsumerRecord<String, String>, String>() {
            @Override
            public String call(ConsumerRecord<String, String> v1) throws Exception {
                JSONObject data = JSONObject.parseObject(v1.value());
                if("null".equals(data)){
                    return null;
                }

                StringBuilder sb = new StringBuilder();
                String dwmc = data.getString("DWMC");
                String hphm = data.getString("HPHM");
                if("-".equals(hphm)){
                    return null;
                }
                String jgsj = data.getString("JGSJ");
                String kkbh = data.getString("KKBH");
                String tplj = data.getString("TPLJ");
                String xsfx = data.getString("XSFX");
                return sb.append(dwmc).append("^").append(hphm).append("^").append(jgsj).append("^").append(kkbh).append("^").append(tplj).append("^").append(xsfx).toString();
            }
        });

        JavaDStream<String> filter = map.filter(new Function<String, Boolean>() {
            @Override
            public Boolean call(String v1) throws Exception {
                if (StringUtils.isBlank(v1)) {
                    return false;
                }
                return true;
            }
        });

        filter.foreachRDD(new VoidFunction<JavaRDD<String>>() {
            @Override
            public void call(JavaRDD<String> s) throws Exception {
				//此处合并并且Shuffer 可以实现单线程入库,如果数据量大,可以提高数量
                s.repartition(1).foreachPartition(new VoidFunction<Iterator<String>>() {
                    @Override
                    public void call(Iterator<String> st) throws Exception {
                        
                        while (st.hasNext()) {
                            String[] split = st.next().split("\\^", -1);
                            if ("鄂XXXXXX".equals(split[1])) {
                                String year = split[2].substring(0, 4);
                                String yue = split[2].substring(4, 6);
                                String ri = split[2].substring(6, 8);
                                String shi = split[2].substring(8, 10);
                                String fen = split[2].substring(10, 12);
                                String miao = split[2].substring(12, 14);
								Insert("业务操作方法") 
                            }
                        }
                    }
                });
            }
        });

        ssc.start();
        System.out.println("调用函数");
        try {
        ssc.awaitTermination();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 第二种方案:官方提供,由于博主没有使用,认为作用没有第三种好,故而没有使用。

第二种方案:
        使用官方文档提供的第二种使用官方一步提交偏移量,优点处高于第一种,但是也有缺陷,将偏移量存储在特定的Kafka 偏移的主题上,默认情况下,新消费者将默认定期自动提交偏移量,
        但是相当于第一种checkpoint 相比,kafka 默认是一个耐用存储,不管代码如何修改,同样是可以保存偏移量,但是存在一个问题就是kafka 并非有事务性,所以我们的输出结果必须是幂等性。
        目前车辆牌照数据入库使用是第二种方法,经过测试可用性高,但是不是最好的办法,代码详情省略。
        【弊端】无法存在事务。

 第三种方案:官方推荐方案,本博主文章使用(保证一次语义消费)

第三种方案:
        事务性手动管理kafka 偏移量:将kafka 偏移量存储到第三方数据库,支持ZK,Mysql,Redis,Hbase等数据库。
        同样为官方提供的第三种解决方案,使用数据库事务存储的方案,即使在故障的情况下,也可以在同一个事务中保存偏移量作为结果,以保持两者同步,可以完美解决一次性语义问题。
        使用当时 本人使用Redis 作为偏移量存储,因为Redis 为内存型数据库,插入效率极快,并且支持Key 相同数据直接覆盖。
        使用原理 详情见代码 com.chepai.TEST 。
        目前第三种方案是保持一次性语义最好的办法。
本博主使用,将Kafka数据的偏移量记录在Redis中。

代码Demo如下:【数据实施消费Kafka,记录偏移量,并且批量插入ElasticSearch集群,提供实时查询服务】

package com.chepai;

import com.alibaba.fastjson.JSONObject;
import com.utils.DateUtil;
import com.utils.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka010.*;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import java.io.Serializable;
import java.net.InetAddress;
import java.util.*;

/**
 * sparkStreaming消费kafka数据1分钟调用一次,车牌数据查询最终版本 最稳定版本
 * 作者 liuwunan
 */
public class ShengPanChePai_last implements Serializable {
    //设置异常标记,如果发生异常,则记录异常的偏移量,如果正常 则记录实时消费的偏移量
    public static Boolean ExceptionFlag = true;

    //配置kafka参数(节点、消费者组、topic)
    private static String topics = "cljlxx"; 						//指定topic
    private static String groupId = "cs2";							//指定消费者组id
    private static String SPCPOffset = "SPCPOffset";	//定义当前消费Kafka存偏移量的Key,存在Redis里
    private static String brokers = "IP:9092,IP:9092,IP:9092";  	//指定kafka地

    public static void main(String[] args) {
        //初始化spark 并且优化spark					设置启动名称
        SparkConf conf = new SparkConf().setAppName("ShengPanChePai_last");
        //关闭动态调度 开启动态资源配置,根据工作负载来衡量是否应该增加或减少executor,默认false
		conf.set("spark.dynamicAllocation.enabled", "false")
        .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
         //启用反压
        .set("spark.streaming.backpressure.enabled", "true")
         //最小消费条数
        .set("spark.streaming.backpressure.pid.minRate","1")
         //最大消费条数(当前最大,最小消费条数为每秒,同时乘以Kafka每个分区的消费速率)
		 //比如当前为60秒调用一次,并且消费Kafka 分区为10分区,最大消费速率10,那么一次最大消费数据为
		 //60*100*10=60000条数据。故每分钟最大消费6W条数据,如果
        .set("spark.streaming.kafka.maxRatePerPartition","100")
        .set("spark.speculation", "true");
		System.setProperty("es.set.netty.runtime.available.processors", "false");

        //开启数据反压机制  上述配置是限制每秒的条数 conf.set("spark.streaming.kafka.maxRatePerPartition","1000");  结果为每秒1000条,乘以30秒时间,等于当前批次拉取的数据最大值 可以增量拉取
        //源头限制数据为30乘以限制条数  最终入库使用两个分区插入  最终会出现 每次插入1500条数据
		//如果想提高插入效率 那么就使用 多分区批量插入

        //代表启用线程数量,根据数据量的级别进行设置,但是如果调用 .coalesce(1,true) 就是就当前线程数数据进行数据汇聚,
        // 如果启用10个线程数,但是在最终调用时一个合并文件,就会造成资源浪费,一个线程离线对应的一个Task,当前为6个
        
		conf.setMaster("local[6]");//本地模式,client 集群模式切记去掉当前数目。
        JavaSparkContext sc = new JavaSparkContext(conf);
        sc.setLogLevel("ERROR");
        JavaStreamingContext ssc = new JavaStreamingContext(sc, Durations.seconds(60));
        Set<String> topicsSet = new HashSet<>(Arrays.asList(topics.split(",")));
        Map<String, Object> kafkaParams = new HashMap<>();
        kafkaParams.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers);
        kafkaParams.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        kafkaParams.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        kafkaParams.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

        //关闭消费之偏移量自动提交 手动维护偏移量
        kafkaParams.put("enable.auto.commit", "false");
        //如果有偏移量则读取偏移量 如果没有消费最新,此处还可以配置其他两种方案。
        kafkaParams.put("auto.offset.reset", "latest");

        //创建偏移量对象 ,如果redis 没有,偏移量对象为空,如果有,则传入当前kafka 偏移量对象  持续消费
        HashMap<TopicPartition, Long> mapTopic = new HashMap<>();

        //获取redis 中当前kafka 当前操作有没有存在偏移量,如果有存在偏移量 即从偏移量开始读取,
		//如果没有偏移量,即从最新数据读取,不从头消费,会造成数据堆积 内存溢出(如果从头消费,记得数据反压机制)
        Boolean flag = RedisUtil.FlagExits(ShengPanChePaiOffset, 1);
        JavaInputDStream<ConsumerRecord<String, String>> directStream = null;

        String[] s =null;
        String offsetlast =null;

        if (flag) {
            Map<String, String> offsets = RedisUtil.getAll(ShengPanChePaiOffset, 1);
            for (Map.Entry<String, String> entry : offsets.entrySet()) {
                String partition = entry.getKey();
                String offset = entry.getValue();
                //偏移量切割  将下标为0 数据拿到 为当前kafak 偏移量
                s = offset.split("_", -1);
                offsetlast = s[0];
                TopicPartition topicPartition = new TopicPartition(topics, Integer.valueOf(partition));
                mapTopic.put(topicPartition, Long.valueOf(offsetlast));
            }
            directStream = KafkaUtils.createDirectStream(ssc, LocationStrategies.PreferConsistent(), ConsumerStrategies.<String, String>Subscribe(topicsSet, kafkaParams, mapTopic));
        } else {
            directStream = KafkaUtils.createDirectStream(ssc, LocationStrategies.PreferConsistent(), ConsumerStrategies.<String, String>Subscribe(topicsSet, kafkaParams));
        }

        JavaDStream<String> map = directStream.map(new Function<ConsumerRecord<String, String>, String>() {
            @Override
            public String call(ConsumerRecord<String, String> v1) throws Exception {
                JSONObject data = JSONObject.parseObject(v1.value());
                if ("null".equals(data)) {
                    return null;
                }
                StringBuilder sb = new StringBuilder();
                String dwmc = data.getString("DWMC");
                String hphm = data.getString("HPHM");
                String jgsj = data.getString("JGSJ");
                String kkbh = data.getString("KKBH");
                String tplj = data.getString("TPLJ");
                String xsfx = data.getString("XSFX");
                return sb.append(dwmc).append("^").append(hphm).append("^").append(jgsj).append("^").append(kkbh).append("^").append(tplj).append("^").append(xsfx).toString();
            }
        });

        JavaDStream<String> filter = map.filter(new Function<String, Boolean>() {
            @Override
            public Boolean call(String v1) throws Exception {
                if (StringUtils.isBlank(v1)) {
                    return false;
                }
                return true;
            }
        });

        filter.foreachRDD(new VoidFunction<JavaRDD<String>>() {
            @Override
            public void call(JavaRDD<String> s) throws Exception {
                //将数据分两个分区提交,如果一分钟 5000条数据 那么一次则提交2500条数据 不能太多,切记计算时间,数据一次提交ES,也需要需要很长时间
                s.repartition(2).foreachPartition(new VoidFunction<Iterator<String>>() {
                    @Override
                    public void call(Iterator<String> st) throws Exception {
                        TransportClient client = null;
                        try {
                            client = new PreBuiltTransportClient(Settings.builder()
                                    .put("cluster.name", "ga-chepai")
                                    .put("client.transport.sniff", true)
                                    .put("thread_pool.search.size", 5)
                                    .build()).addTransportAddress(new TransportAddress(InetAddress.getByName("IP"), 9300));
                            BulkRequestBuilder builder = client.prepareBulk();
                            //加工厂(2)^-^20210708193014^9950fd61cb9746f8beb4dc5d6be58cb2^http://ip:28092/pic/download/0000qySS8UIHrf4cahyx2028G0HVzk^4
                            HashMap<String, String> esMap = null;
                            IndexRequestBuilder request = null;
                            String[] split = null;
                            String next = null;

                            while (st.hasNext()) {
                                esMap = new HashMap<>();
                                next = st.next();
                                split = next.split("\\^", -1);
                               //去掉尚未识别数据,后续集群可以全部存储
                                if ("-".equals(split[1])) {
                                    continue;
                                }
                                esMap.put("dw_name", split[0]);
                                esMap.put("hp_name", split[1]);
                                esMap.put("cp_time", split[2]);
                                esMap.put("kk_number", split[3]);
                                esMap.put("img_url", split[4]);
                                esMap.put("xs_fangxiang", split[5]);
                                request = client.prepareIndex("chepai", "doc", UUID.randomUUID().toString()).setSource(esMap);
                                builder.add(request);
                            }

                            //System.out.println("当前条数为+ "+ builder.numberOfActions());
                            //判断当前批量请求数据为0条以上 才会进行请求发送。
                            if (builder.numberOfActions() > 0) {
                                BulkResponse response = builder.get();
                                if (response.hasFailures()) {
                                    System.out.println("操作失败,再从请求一次");
                                    builder.execute().actionGet();
                                }
                            }
                        } catch (Exception e) {
                            //如果发生异常将 标记切换 解决 数据重复问题
                            ExceptionFlag = false;
                            e.printStackTrace();
                        }
                        client.close();
                    }
                });
            }
        });
		
		//此处必须调用 KafkaUtils.createDirectStream 生成对象,因为第二步后续都无法拿到Kafka 数据偏移量。
        directStream.foreachRDD(new VoidFunction<JavaRDD<ConsumerRecord<String, String>>>() {
            @Override
            public void call(JavaRDD<ConsumerRecord<String, String>> v3) throws Exception {
                v3.repartition(2).foreachPartition(new VoidFunction<Iterator<ConsumerRecord<String, String>>>() {
                    @Override
                    public void call(Iterator<ConsumerRecord<String, String>> st) throws Exception {
                        HashMap<String, String> redisMapOk = new HashMap<>();
                        HashMap<String, String> redisMapErro = new HashMap<>();

                        String time = DateUtil.formatDateString(new Date(), DateUtil.DATE_FORMAT_12W);
                        OffsetRange[] offsetRanges = ((HasOffsetRanges) v3.rdd()).offsetRanges();
                        for (OffsetRange offsetRange : offsetRanges) {
                            //记录正确的偏移量 如果没有发生错误,则记录当前偏移量的结束位置,因为起始位置已经数据入库, 下次从上次的结束开始
                            redisMapOk.put(String.valueOf(offsetRange.partition()), offsetRange.untilOffset()+"_"+time+"_OK");
                            //记录错误的的偏移量 因为异常插入, 所以记录当前偏移量的起始位置。
                            redisMapErro.put(String.valueOf(offsetRange.partition()),offsetRange.fromOffset()+"_"+time+"_ERROR");
                        }

                        //当数据为空 不对数据添加到redis 然后减少redis 的压力
                        if(st.hasNext()){
                            if (ExceptionFlag) {
                                //System.out.println("正确偏移量~~~~~~~~~~~ 提交" + redisMapOk);
                                RedisUtil.PutAll(ShengPanChePaiOffset, redisMapOk, 1);
                            } else {
                                //System.out.println("错误偏移量~~~~~~~~~~~" + redisMapOk);
                                RedisUtil.PutAll(ShengPanChePaiOffset, redisMapErro, 1);
                            }
                        }
                    }
                });
            }
        });

        ssc.start();
        try {
            ssc.awaitTermination();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 Redis读取Kafka数据偏移量代码

package com.utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashMap;
import java.util.Map;

/**
 * @Date 2019/7/12 19:12
 */
public class RedisUtil {
    private static String host = "IP";
    private static int port = 6379;
    private static int timeout = 10000;
    private static int maxIdle = 8;
    private static long maxWaitMillis = 100;
    private static String password = "password";
    private static boolean blockWhenExhausted = true;
    private static JedisPool jedisPool = null;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
        jedisPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
        // 是否启用pool的jmx管理功能, 默认true
        jedisPoolConfig.setJmxEnabled(true);
        jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
    }

    public static String get(String key, int indexdb) {
        Jedis jedis = null;
        String value = null;
        try {
            jedis = jedisPool.getResource();
            jedis.select(indexdb);
            value = jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            returnResource(jedisPool, jedis);
        }
        return value;
    }

    public static Map getAll(String key, int indexdb) {
        Jedis jedis = null;
        Map<String, String> StringMap=null;
        try {
            jedis = jedisPool.getResource();
            jedis.select(indexdb);
            StringMap= jedis.hgetAll(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            returnResource(jedisPool, jedis);
        }
        return StringMap;
    }

    public static void PutAll(String key, HashMap map, int indexdb) {
        Jedis jedis = null;
        Map<String, String> StringMap=null;
        try {
            jedis = jedisPool.getResource();
            jedis.select(indexdb);
            jedis.hmset(key, map);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            returnResource(jedisPool, jedis);
        }
    }

    public static String del(String key, int indexdb) {
        Jedis jedis = null;
        String value = null;
        try {
            jedis = jedisPool.getResource();
            jedis.select(indexdb);
            if(jedis.get(key) != null){
                jedis.del(key);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            returnResource(jedisPool, jedis);
        }
        return value;
    }

    public static Boolean FlagExits(String key, int indexdb) {
        Jedis jedis = null;
        Boolean exists =true;
        try {
            jedis = jedisPool.getResource();
            jedis.select(indexdb);
            exists = jedis.exists(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            returnResource(jedisPool, jedis);
        }
        return exists;
    }

    public static String set(String key, String value, int indexdb) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.select(indexdb);
            jedis.set(key,value);
           // value = jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            returnResource(jedisPool, jedis);
        }
        return value;
    }

    /**
     * 返还到连接池
     * @param jedisPool
     * @param jedis
     */
    public static void returnResource(JedisPool jedisPool, Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    public synchronized  static Jedis getJedis() {
        Jedis jedis = null;
        if(jedisPool!=null){
            jedis = jedisPool.getResource();
            return jedis;
        }else{
            return null;
        }
    }
}

 

       从上述第三种方案中代码可知,程序初始化的时候,会去默认读取偏移量,如果偏移量的标记为空,则开始从最新的地方消费(latest),如果从Redis中读取到了偏移量数据,按照对应分区消费读取,同时,如果配置的为earliest,没有记录偏移量,则为从开头读取,但是必须限制数据消费速率,开启数据反压机制,否则初始的消费速率巨大,Jvm内存会异常,不好的情况会将ElasticSearch集群写宕机,所以此处涉及到数据反压,代码配置文件已经写的非常清楚,可以直接配置使用。下列为反压解释的相关文档Scala版本。

至此:SprakStreaming 消费Kafka保持一次语义数据偏移量存储Redis,同时开启反压消费机制记录完毕。

Spark Streaming反压机制介绍_风情客家__的博客-CSDN博客_spark反压机制1.反压机制原理Spark Streaming中的反压机制是Spark 1.5.0推出的新特性,可以根据处理效率动态调整摄入速率。当批处理时间(Batch Processing Time)大于批次间隔(Batch Interval,即 BatchDuration)时,说明处理数据的速度小于数据摄入的速度,持续时间过长或源头数据暴增,容易造成数据在内存中堆积,最终导致Executor OOM或任务奔溃。在这种情况下,若是基于Kafka Receiver的数据源,可以通过设置spark.streamhttps://blog.csdn.net/justlpf/article/details/118893985

POM文件坐标如下 

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java.version>1.8</java.version>
        <spark.version>2.3.0</spark.version>
        <scala.version>2.11.8</scala.version>
        <!--<scala.version>2.10.0</scala.version>-->
        <elasticsearch.version>6.6.1</elasticsearch.version>
    </properties>
    <dependencies>
        <!-- spark start -->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
            <!--<artifactId>spark-streaming-kafka-0-8_2.11</artifactId>-->
            <version>${spark.version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>2.6.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>0.10.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>2.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.11</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun.hbase</groupId>
            <artifactId>alihbase-client</artifactId>
            <version>1.1.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.23</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>6.5.4</version>
        </dependency>

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值