基於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 的對象,
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 再執行的,
附上博主 鏈接 :https://blog.csdn.net/u013673976/article/details/52603817
官網spark整合kafka api : http://spark.apache.org/docs/2.1.1/streaming-kafka-0-10-integration.html