[消息持久化]: https://pulsar.apache.org/docs/next/concepts-architecture-overview#persistent-storage
Pulsar broker 负责处理通过 Pulsar的消息,包括消息的持久存储。默认情况下,对于每个topic,brokers只保留至少在一个 backlog 中的消息。backlog 是特定订阅的未确认的消息的集合。 每个主题可以有多个订阅者,所以每个主题可以有多个 backlog。
因此,在一个没有创建任何订阅的主题上不会保留任何消息(默认情况下)。
在 Pulsar 中,你有两种方式在命名空间的级别去修改这种行为:
通过设置消息保留策略持久化存储不在 backlog 内的消息
通过指定time to live(TTL) ,设置消息在指定的时间内不被确认的话,自动确认。
[消息保留策略]: https://pulsar.apache.org/docs/next/concepts-messaging#message-retention-and-expiry
当你在一个命名空间设置主题的保留策略,则必须设置两个一个大小限制和时间限制。您可以参考下表在pulsar-adminJava 中设置保留策略。
时限 | 尺寸限制 | 消息保留 |
---|---|---|
-1 | -1 | 无限保留 |
-1 | >0 | 基于大小限制 |
>0 | -1 | 基于时间限制 |
0 | 0 | 禁用消息保留(默认) |
0 | >0 | 无效的 |
>0 | 0 | 无效的 |
>0 | >0 | 当时间或大小达到限制时,不会保留已确认的消息或没有活动订阅的消息。 |
保留设置适用于没有任何订阅的主题的所有消息,或已被所有订阅确认的消息。保留策略设置不会影响订阅主题的未确认消息。未确认的消息由积压配额控制。 | ||
当超出主题的保留限制时,最旧的消息将被标记为删除,直到保留的消息集再次落入指定的限制范围内。 |
默认值
可以使用以下两个参数在实例级别设置消息保留时间:defaultRetentionTimeInMinutes和defaultRetentionSizeInMB。默认情况下,这两个参数都设置为0。
这两个参数的详细信息请参考broker.conf配置文件。
要实现无限留存,将两个值都设置为 -1。
$ pulsar-admin namespaces set-retention my-tenant/my-ns \
--size -1 \
--time -1
要禁用消息保留配置,将值设置为 0。
$ pulsar-admin namespaces set-retention my-tenant/my-ns \
--size 0 \
--time 0
[生存时间 (TTL)]: https://pulsar.apache.org/docs/next/cookbooks-retention-expiry#set-the-ttl-for-a-namespace
默认情况下,Pulsar 会永久存储所有未确认的消息。 在大量消息未得到确认的情况下,可能会导致大量磁盘空间的使用。 如果需要考虑磁盘空间,可以设置生存时间(TTL),以确定未确认的消息将保留多长时间。
设置命名空间的TTL
使用 set-message-ttl 子命令并指定命名空间和TTL(以秒为单位,使用-ttl/–messageTTL参数指定)。
示例
$ pulsar-admin namespaces set-message-ttl my-tenant/my-ns \
--messageTTL 120 # TTL of 2 minutes
从命名空间中删除消息
$ pulsar-admin namespaces remove-message-ttl my-tenant/my-ns
[偷看消息]: https://pulsar.apache.org/docs/next/admin-api-topics#peek-messages
用来查看当前消息中间件里面的消息内容,比如消费者没正常运行,不确定消息是否发送出来了还是消费者某段代码异常了
pulsar-admin topics peek-messages \
--count 10 --subscription my-subscription \
persistent://test-tenant/ns1/tp1 \
api
GET /admin/v2/:schema/:tenant/:namespace/:topic/subscription/:subName/position/:messagePosition
java
String topic = "persistent://my-tenant/my-namespace/my-topic";
String subName = "my-subscription";
int numMessages = 1;
admin.topics().peekMessages(topic, subName, numMessages);
produce的topic注意点
持久化topic的结构
persistent://tenant/namespace/topic
非持久化topic的结构
non-persistent://tenant/namespace/topic
代码例子
String finalTopic = "persistent://" + tenant + "/" + namespaces +"/" + topic;
Producer<byte[]> producer = client.newProducer()
.topic(finalTopic)
.create();
pulsar使用java对象
Producer<Demo> producer = client.newProducer(JSONSchema.of(Demo.class))
.topic(topic)
.batchingMaxPublishDelay(10, TimeUnit.MILLISECONDS)
.sendTimeout(10, TimeUnit.SECONDS)
.blockIfQueueFull(true)
.create();
producer.newMessage()
.key("demo") //message的
.value(Demo.builder().name("阿三").age("123岁").build())
.property("demo","demo-p")
.sendAsync()
.thenAccept(msgId -> {
System.out.println("Message with ID " + msgId + " successfully sent");
});
client.newConsumer()
.topic("public/engma/non")
.subscriptionName("subscription")
.messageListener((consumer, msg) -> {
try{
System.out.println(msg.getKey());
System.out.println(msg.getProperty("demo"));
System.out.println(JSON.toJSONString(msg.getValue()));
consumer.acknowledge(msg);
}catch (Exception e){
log.error(e.getMessage(),e);
consumer.negativeAcknowledge(msg);
}
})
.subscribe();
完整代码例子
public static void main(String[] args) {
try {
PulsarClient client = PulsarClient.builder()
.serviceUrl("pulsar://ip:port")
.build();
String topic = "persistent://tenant/namespace/topic";
String deadLetterTopic = topic + "-subscription-DLQ";
Producer<Demo> producer = client.newProducer(JSONSchema.of(Demo.class))
.topic(topic)
.batchingMaxPublishDelay(10, TimeUnit.MILLISECONDS)//批量发送的最大等待时间
.sendTimeout(10, TimeUnit.SECONDS) //发送超时时间
.blockIfQueueFull(true) //当达到最大缓存数时是否block客户端
.create();
producer.newMessage()
.key("demo") //message的
.value(Demo.builder().name("阿三").age("12岁").build())
.property("demo", "demo-p")
.sendAsync()
.thenAccept(msgId -> {
//todo 处理发送成功的事情,比如记录msgId
});
client.newConsumer()
.topic(topic)
.subscriptionName("subscription")
.receiverQueueSize(100) //设置队列
.ackTimeout(3, TimeUnit.SECONDS) //确认超时
.enableRetry(true) //是否重试
.negativeAckRedeliveryDelay(1,TimeUnit.SECONDS) //拒绝超时
.deadLetterPolicy(
DeadLetterPolicy.builder()
//可以指定最大重试次数,最大重试三次后,进入到死信队列
.maxRedeliverCount(3)
//重试队列,如果不设置重置队列需要设置consumer的subscriptionType Shared或Key_Shared 否则死信队列不起作用
.retryLetterTopic(topic)
//可以指定死信队列
.deadLetterTopic(deadLetterTopic)
.build()
)
.messageListener((consumer, msg) -> {
try {
//todo 业务代码
// msg.getKey() 获取消息的key
// msg.getProperty("demo") 获取消息设置的属性
// msg.getValue() 获取消息内容
//consumer.reconsumeLater(msg,1,TimeUnit.SECONDS); 消息重发,会进入重试队列
consumer.acknowledge(msg);
} catch (Exception e) {
log.error(e.getMessage(), e);
consumer.negativeAcknowledge(msg); //否定消息,触发重发机制
}
})
.subscribe();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
可视化注意点
使用docker部署的情况
首先保证有pulsar
docker pull apachepulsar/pulsar:latest
docker run -d -it \
-p 6650:6650 \
-p 8080:8080 \
-v pulsardata:/pulsar/data \
-v pulsarconf:/pulsar/conf \
--name pulsar-standalone \
apachepulsar/pulsar:latest \
bin/pulsar standalone
这边可能会报错:
java.io.FileNotFoundException: /pulsar/conf/standalone.conf (No such file or directory)
暂时没什么比较好的解决办法,另外使用中没有需要定制化的配置所以,这里忽略了这个问题,去掉了-v pulsarconf:/pulsar/conf \
接下来部署pulsar-manager
docker pull apachepulsar/pulsar-manager:v0.2.0
docker run -it \
-p 9527:9527 -p 7750:7750 \
-e SPRING_CONFIGURATION_FILE=/pulsar-manager/pulsar-manager/application.properties \
-v $PWD/bkvm.conf:/pulsar-manager/pulsar-manager/bkvm.conf \
--link pulsar-standalone \
apachepulsar/pulsar-manager:v0.2.0
部署后可能会无法看到配置文件,这时候可以手动复制出来:
docker cp pulsar-manager:/pulsar-manager/pulsar-manager/application.properties /opt/pulsar/manager/application.properties
docker cp pulsar-manager:/pulsar-manager/pulsar-manager/bkvm.conf /opt/pulsar/manager/bkvm.conf
然后:
1.application.properties
bookie.enable=true
pulsar.peek.message=true
2.bkvm.conf
bookie.enable=true
pulsar可视化:pulsar/pulsar:http://localhost:7750/ui/index.html
bookie可视化:admin/admin:http://localhost:7750/bkvm/
7750端口不行就用9527试试
重启后如果提示账户密码错误,那么就手动添加一个账户
CSRF_TOKEN=$(curl http://backend-service:7750/pulsar-manager/csrf-token)
curl \
-H "X-XSRF-TOKEN: $CSRF_TOKEN" \
-H "Cookie: XSRF-TOKEN=$CSRF_TOKEN;" \
-H 'Content-Type: application/json' \
-X PUT http://backend-service:7750/pulsar-manager/users/superuser \
-d '{"name": "admin", "password": "apachepulsar", "description": "test", "email": "username@test.org"}'
进入bookie可视化如果什么都看不到,那么就是地址错了,具体配置如下:
这里为什么使用pulsar-standalone:2181
是因为启动的pulsar-manager容器,有这么一个配置:--link pulsar-standalone
目的是为了让pulsar-manager可以访问到pulsar容器
消息广播
pulsar的四种消息模式
Exclusive 独占模式
Exclusive 独占模式(默认模式):一个 Subscription 只能与一个 Consumer 关联,只有这个 Consumer 可以接收到 Topic 的全部消息,如果该 Consumer 出现故障了就会停止消费。 Exclusive 订阅模式下,同一个 Subscription 里只有一个 Consumer 能消费 Topic,如果多个 Consumer 订阅则会报错,适用于全局有序消费的场景。
// 构建消费者
Consumer<byte[]> consumer = pulsarClient.newConsumer()
// topic完整路径,格式为persistent://集群(租户)ID/命名空间/Topic名称,从【Topic管理】处复制
.topic("persistent://pulsar-xxx/sdk_java/topic1")
// 需要在控制台Topic详情页创建好一个订阅,此处填写订阅名
.subscriptionName("sub_topic1")
// 声明消费模式为exclusive(独占)模式
.subscriptionType(SubscriptionType.Exclusive)
.subscribe();
共享模式(Shared)
消息通过 round robin 轮询机制(也可以自定义)分发给不同的消费者,并且每个消息仅会被分发给一个消费者。 当消费者断开连接,所有被发送给他,但没有被确认的消息将被重新安排,分发给其它存活的消费者。
// 构建消费者
Consumer<byte[]> consumer = pulsarClient.newConsumer()
// topic完整路径,格式为persistent://集群(租户)ID/命名空间/Topic名称,从【Topic管理】处复制
.topic("persistent://pulsar-xxx/sdk_java/topic1")
// 需要在控制台Topic详情页创建好一个订阅,此处填写订阅名
.subscriptionName("sub_topic1")
// 声明消费模式为 Shared(共享)模式
.subscriptionType(SubscriptionType.Shared)
.subscribe();
多个 Shared 模式消费者。
灾备模式(Failover)
当存在多个 consumer 时,将会按字典顺序排序,第一个 consumer 被初始化为唯一接受消息的消费者。当第 一个 consumer 断开时,所有的消息(未被确认和后续进入的)将会被分发给队列中的下一个 consumer。
// 构建消费者
Consumer<byte[]> consumer = pulsarClient.newConsumer()
// topic完整路径,格式为persistent://集群(租户)ID/命名空间/Topic名称,从【Topic管理】处复制
.topic("persistent://pulsar-xxx/sdk_java/topic1")
// 需要在控制台Topic详情页创建好一个订阅,此处填写订阅名
.subscriptionName("sub_topic1")
// 声明消费模式为灾备模式
.subscriptionType(SubscriptionType.Failover)
.subscribe();
多个 Failover 模式消费者。
KEY 共享模式(Key_Shared)
当存在多个 consumer 时,将根据消息的 key 进行分发,key 相同的消息只会被分发到同一个消费者。
// 构建消费者
Consumer<byte[]> consumer = pulsarClient.newConsumer()
// topic完整路径,格式为persistent://集群(租户)ID/命名空间/Topic名称,从【Topic管理】处复制
.topic("persistent://pulsar-xxx/sdk_java/topic1")
// 需要在控制台Topic详情页创建好一个订阅,此处填写订阅名
.subscriptionName("sub_topic1")
// 声明消费模式为 Key_Shared(Key 共享)模式
.subscriptionType(SubscriptionType.Key_Shared)
.subscribe();
多个 Key_Shared 模式消费者。
如何实现广播功能
Subscription
pulsar ⾥将 consumer 接收消息的过程称之为:subscription(订阅)
在一个topic里如果有多个不一样的订阅就可以实现广播的效果
[java参数的官方地址]:https://pulsar.apache.org/docs/next/client-libraries-java