参考文章技术探究|深入解析 Apache Pulsar Key_Shared 订阅模式原理与最佳实践 - StreamNative - OSCHINA - 中文开源技术交流社区
pulsar的主题订阅模式包括四种:独占(exclusive)、共享(shared)、灾备(failover)、key共享(key_shared)。
独占:一个订阅只与一个消费者可以关联,只有这个消费者接收到topic的全部消息,如果这个消费者故障了就会停止消费。
灾备:一个订阅可以与多个消费者关联,但只有一个消费者会消费到数据,当该消费者故障时,由另一个消费者来继续消费。
共享:一个订阅可以与多个消费者关联,消息通过轮询机制发送给不同的消费者。YPulsar默认使用共享模式。
key共享:一个订阅可以与多个消费者关联,消息根据给定的映射规则,相同key的消息由同一个消费者消费。
在key_share消费模式中, 先启动consume1,再启动consume2。consume1和consume2都是active consume。相同key的消息只能被一个consume消费,但一个key被哪个consume消费是随机的。
Broker 内部的 Dispatcher 组件负责消费的消息分发和流量控制。Pulsar 支持的 4 种订阅模式也是通过 Dispatcher 来控制的,其中 Key_Shared 模式下 Dispatcher 重要的功能是把相同 Key 的消息分配给相同的 Consumer,从而保证相同 Key 的消息有序。
将 Key 进行哈希后得到一个 HashCode,再将 HashCode 映射到一个指定范围 Key Range,然后 Dispatcher 根据 Key Range 对消息进行分配。
Dispatcher 首先获取消息的 OrderingKey。如果 OrderingKey 为空,再尝试获取设置的 Key 字段进行 Hash 运算,默认使用 MurmurHash 算法计算出 HashCode,具体方法如下:
public int makeHash(byte[] b) {
return org.apache.pulsar.common.util.Murmur3_32Hash.getInstance().makeHash(b) & Integer.MAX_VALUE;
}
由此,我们可以在消费端配置消费的HashCode范围来限定消费端消费的消息
例如,生产端发送消息:
//创建生产者
Producer producer = client
.newProducer(Schema.STRING)
.producerName("name")
.topic(topic)
//是否开启批量处理消息,默认true,需要注意的是enableBatching只在异步发送sendAsync生效,同步发送send失效。因此建议生产环境若想使用批处理,则需使用异步发送,或者多线程同步发送
.enableBatching(true)
//消息压缩(四种压缩方式:LZ4,ZLIB,ZSTD,SNAPPY),consumer端不用做改动就能消费,开启后大约可以降低3/4带宽消耗和存储(官方测试)
.compressionType(CompressionType.SNAPPY)
//批量发送不适合key_share模式,会按照这一批数据的第一条key作为整批数据的key,可以将上面的enableBatching设置为false关闭批量发送,或者采用BatcherBuilder.KEY_BASED方式将同样的key作为一批发送
.batcherBuilder(BatcherBuilder.KEY_BASED)
.create();
//下面是模拟的业务数据
Random ran1 = new Random();
String str =ran1.nextInt(300) + "," + ran1.nextInt(300) + "," + ran1.nextInt(300) + "," + "2320.0, 2321.0, 2097.0, 2040.0]";
//分别为数据添加两种key发送
for (int i = 1; i < 2000000000; i++) {
if (i%2 ==0){
producer.newMessage()
.key("dataStream")
.value(str)
.send();
Thread.sleep(5000);
}else {
producer.newMessage()
.key("stateStream")
.value(str)
.send();
Thread.sleep(5000);
}
}
计算key:dataStream和stateStream的HashCode值
String data = "dataStream";
String state = "stateStream";
final int d = Murmur3_32Hash.getInstance().makeHash(data.getBytes()) % 65536;
final int s = Murmur3_32Hash.getInstance().makeHash(state.getBytes()) % 65536;
System.out.println("datakey: "+ d);
System.out.println("statekey: "+ s);
计算的结果是:
datakey: 59815
statekey: 3473
消费端代码:
ArrayList ranges = new ArrayList<>();
ranges.add(Range.of(59815,59815));
Consumer consumer = client.newConsumer(Schema.STRING)
.topic(topic)
.subscriptionName("fixed-key-share-test")
.consumerName("data")
//指定消费方式为Key_share
.subscriptionType(SubscriptionType.Key_Shared)
//指定range消费范围,传入值为key:dataStream经过计算后的HashCode值,范围是[a,b),可以传入多个范围
.keySharedPolicy(KeySharedPolicy
.stickyHashRange()
.ranges(ranges))
.subscribe();
int i = 1;
//接收数据
while (true) {
Messages receive = consumer.batchReceive();
for (Message message : receive) {
final String value = message.getValue();
final String key = message.getKey();
System.out.println("第" + i + "条消息: " + value +" key: "+key);
consumer.acknowledge(message);
i++;
}
}
消费结果
第1条消息: 16,62,100,2320.0, 2321.0, 2097.0, 2040.0] time: 32 key: dataStream
第2条消息: 42,88,46,2320.0, 2321.0, 2097.0, 2040.0] time: 33 key: dataStream
第3条消息: 93,208,130,2320.0, 2321.0, 2097.0, 2040.0] time: 33 key: dataStream
第4条消息: 288,122,256,2320.0, 2321.0, 2097.0, 2040.0] time: 33 key: dataStream
第5条消息: 60,40,77,2320.0, 2321.0, 2097.0, 2040.0] time: 33 key: dataStream
第6条消息: 179,84,64,2320.0, 2321.0, 2097.0, 2040.0] time: 33 key: dataStream
第7条消息: 138,148,19,2320.0, 2321.0, 2097.0, 2040.0] time: 33 key: dataStream
第8条消息: 205,150,266,2320.0, 2321.0, 2097.0, 2040.0] time: 35 key: dataStream
第9条消息: 163,132,39,2320.0, 2321.0, 2097.0, 2040.0] time: 37 key: dataStream
消费端只消费了key为dataStream的数据