基于Direct  API  手动维护kafka 的偏移量,  将偏移量同步导了 redis 中,

我将对比较重要的代码拿出来说明, 完整代码在下方:

 首先是通过Direct AIP 获取 JavaInputDStream 对象 , 

JavaInputDStream<String> message = KafkaUtils.createDirectStream(jssc, String.class, String.class,
				StringDecoder.class, StringDecoder.class, String.class, kafkaParams, maptopic,
				new Function<MessageAndMetadata<String, String>, String>() {
					public String call(MessageAndMetadata<String, String> v1) throws Exception {
						return v1.message();
					}
				});


//参数说明, jssc , kafka中记录的key 类型, kafka 中记录的value类型, key 的解码方式,value 的解码方式, kafka 中记录的类型, 为kafka 设置的参数, offsets 的集合 (这个会单独拿出来介绍的),最终返回的结果类型


获取DStream 对象,接下来是如何获取的 offset  (偏移量)  后门我都会称呼为offset的,


OffsetRange[] offsetRanges = ((HasOffsetRanges) stringJavaRDD.rdd()).offsetRanges();


上面这个rdd 转化的操作, 必须是DStream 第一个转换操作,  因为 DStream 读取数据是根据分区读取的 rdd的分区恰好何 kafka的分区成映射关系,如果你进行了其他转换操作, 当前rdd分区就会发送改变, 你就不能将它转化为  HasOffsetRanges

 

说明一下offsetRange 这个对象, 这个对象是用于封装  offset  的对象,

spark流实时读取kafka persist sparkstreaming读取kafka 偏移量_redis

topic  为  当前 当前主题, parttion  为主题所对应的分区,  fromOffset 为当前偏移量的位置, untilOffset 为偏移量的最大值,

从这里我们可以看出 ,偏移量这个概念是由 主题+分区+fromOffset 确立的, 获取到这个  之后就可以就实现偏移量的维护。 

之后就可以指定偏移量读取数据, 

将获取的offset 分别保存的redis 中 ,  当我们程序异常终止后, 我们接着当前偏移量 继续处理操作,

接着回到上面的创建 JavaInputDStream 对象, 这里面  当我们 设置的maptopic,不为空时,我们将 根据maptopic 这个map 对象 里面的 offset  ,读取数据 , 如果没有 默认从开始位置读取,  


Map<TopicAndPartition, Long> maptopic = new HashMap<>();


解释一下,TopicAndPartition这个对象,   用于指定   JavaInputDStream从那读取kafka,  也是用来封装 offset的,和offsetRange 差不多

 

public class TestStreaming {
	private static final Pattern SPACE = Pattern.compile(" ");

	public static void main(String[] args) throws InterruptedException {
		args = new String[] { "localhost:9092", "test1" };
		if (args.length < 2) {
			System.err.println("Usage: JavaDirectKafkaWordCount <brokers> <topics>\n"
					+ "  <brokers> is a list of one or more Kafka brokers\n"
					+ "  <topics> is a list of one or more kafka topics to consume from\n\n");
			System.exit(1);
		}

		String brokers = args[0];
		String topics = args[1];

		// Create context with a 2 seconds batch interval
		SparkConf sparkConf = new SparkConf().setMaster("local[2]").setAppName("JavaDirectKafkaWordCount");
		JavaStreamingContext jssc = new JavaStreamingContext(sparkConf, Durations.seconds(10));
		Set<String> topicsSet = new HashSet<>(Arrays.asList(topics.split(",")));
		Map<String, String> kafkaParams = new HashMap<>();
		kafkaParams.put("metadata.broker.list", brokers);
		kafkaParams.put("group.id", "group1");
		kafkaParams.put("auto.offset.reset", "smallest");

		Map<TopicAndPartition, Long> maptopic = new HashMap<>();
		Jedis jedis = RedisUtil.getJedis();
		Boolean flag = jedis.exists("offset");
		//判断redis 中是否有保存的偏移量, 如果有 直接读取redis 中的,没有的话 从头开始读取
		if (flag) {
			Map<String, String> offsets = jedis.hgetAll("offset");
			for (Map.Entry<String, String> entry : offsets.entrySet()) {
				TopicAndPartition topic = new TopicAndPartition(topics, Integer.valueOf(entry.getKey()));
				maptopic.put(topic, Long.valueOf(entry.getValue()));
			}

		}

		//		// Create direct kafka stream with brokers and topics
		//		JavaPairInputDStream<String, String> directKafkaStream = KafkaUtils.createDirectStream(jssc, String.class, String.class,
		//				StringDecoder.class, StringDecoder.class, kafkaParams, topicsSet);

		// create direct stream

		JavaInputDStream<String> message = KafkaUtils.createDirectStream(jssc, String.class, String.class,
				StringDecoder.class, StringDecoder.class, String.class, kafkaParams, maptopic,
				new Function<MessageAndMetadata<String, String>, String>() {
					public String call(MessageAndMetadata<String, String> v1) throws Exception {
						return v1.message();
					}
				});

		message.foreachRDD(new VoidFunction<JavaRDD<String>>() {
			@Override
			public void call(JavaRDD<String> stringJavaRDD) throws Exception {
				// 获取jedis 连接对象 ,
				Jedis jedis = RedisUtil.getJedis();
				OffsetRange[] offsetRanges = ((HasOffsetRanges) stringJavaRDD.rdd()).offsetRanges();
				//每次操作之前  ,保存此次操作前的偏移量, 如果当前任务失败, 我们可以回到开始的偏移量 重新计算,
				for (OffsetRange o : offsetRanges) {
					jedis.hincrBy("offset", o.partition() + "", o.fromOffset());
				}
				//计算过程

				stringJavaRDD.flatMapToPair(new PairFlatMapFunction<String, String, Integer>() {
					@Override
					public Iterator<Tuple2<String, Integer>> call(String s) throws Exception {
						List<Tuple2<String, Integer>> list = new ArrayList<>();
						String[] split = s.split(" ");

						for (String string : split) {
							list.add(new Tuple2<>(string, 1));
						}
						return list.iterator();
					}
				}).reduceByKey(new Function2<Integer, Integer, Integer>() {
					@Override
					public Integer call(Integer v1, Integer v2) throws Exception {
						return v1 + v2;
					}
				});
			}
		});

		// Start the computation
		jssc.start();
		jssc.awaitTermination();
		jssc.close();
	}

附上我的redis 工具类, 你也可以使用zk, 或者mysql

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {

	//服务器IP地址
	private static String ADDR = "127.0.0.1";

	//端口
	private static int PORT = 6379;
	//密码
	private static String AUTH = "123456";
	//连接实例的最大连接数
	private static int MAX_ACTIVE = 1024;

	//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
	private static int MAX_IDLE = 200;

	//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
	private static int MAX_WAIT = 10000;

	//连接超时的时间
	private static int TIMEOUT = 10000;

	// 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
	private static boolean TEST_ON_BORROW = true;

	private static JedisPool jedisPool = null;

	/**
	 * 初始化Redis连接池
	 */

	static {

		try {

			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(MAX_ACTIVE);
			config.setMaxIdle(MAX_IDLE);
			config.setMaxWaitMillis(MAX_WAIT);
			config.setTestOnBorrow(TEST_ON_BORROW);
			jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);

		} catch (Exception e) {

			e.printStackTrace();
		}

	}

	/**
	 * 获取Jedis实例
	 */

	public synchronized static Jedis getJedis() {

		try {

			if (jedisPool != null) {
				Jedis resource = jedisPool.getResource();
				return resource;
			} else {
				return null;
			}

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

	}

	/***
	 *
	 * 释放资源
	 */

	public static void returnResource(final Jedis jedis) {
		if (jedis != null) {
			jedisPool.returnResource(jedis);
		}

	}

maven 依赖

<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.11</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming-kafka_2.11</artifactId>
            <version>1.6.3</version>
        </dependency>

最开始我在写这代码的时候, 看了官网,官网没有怎末介绍, 也看了很多人的实现方式, 但是大多数实现方式,都是通过scala 去实现的, 后来找到一个Java的版本, 那个博主确实写的挺不错, 但是我感觉  写的有点复杂 ,他是调用scala 的api , 他将Java 类型进行了转化 成scala 再执行的,