5.四种订阅模式、延时消息
四种订阅模式
独占Exclusive
在独占模式下,只允许一个消费者附加到订阅上。如果多个消费者使用同一个订阅来订阅一个主题,就会发生错误。在下图中,只有消费者A-0被允许消费消息。
此模式也是默认模式
Demo
subscriptionName相同,同时执行exclusive/comsumer1和exclusive/comsumer2就会报错
package com.project.pulsar.delayMsgAndSubscriptionsModel;
import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.admin.Namespaces;
import org.apache.pulsar.client.admin.PulsarAdmin;
import org.apache.pulsar.client.admin.Tenants;
import org.apache.pulsar.client.api.*;
import org.apache.pulsar.common.policies.data.DelayedDeliveryPolicies;
import org.apache.pulsar.common.policies.data.TenantInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Set;
/**
* 独占模式
*/
@RestController
public class ExclusiveMessageController {
@Autowired
PulsarConf pulsarConf;
String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java
/**
* 生产消息
*
* @param msg
* @throws PulsarClientException
*/
@GetMapping("/exclusive/sendMsg")
public MessageId sendMsg(String msg) throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Producer<byte[]> producer1 = pulsarFactory.newProducer()
.topic(topic)
.create();
return producer1.send(msg.getBytes());
}
/**
* 手动执行获取消息
*
* @throws PulsarClientException
*/
@GetMapping("/exclusive/comsumer")
public void comsumerByArtificial() throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Consumer<byte[]> consumer = pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Exclusive)//指定模式
.subscribe();
Message<byte[]> receive = consumer.receive();
System.out.println(new String(receive.getData()));
consumer.acknowledge(receive);//确认消息被消费
consumer.close();
}
/**
* 手动执行获取消息
*
* @throws PulsarClientException
*/
@GetMapping("/exclusive/comsumer2")
public void comsumerByArtificial2() throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Consumer<byte[]> consumer = pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Exclusive)//指定模式
.subscribe();
Message<byte[]> receive = consumer.receive();
System.out.println(new String(receive.getData()));
consumer.acknowledge(receive);//确认消息被消费
consumer.close();
}
}
代码下载
代码见此delayMsgAndSubscriptionsModel包下ExclusiveMessageController.java
灾备Failover
在故障转移模式中,多个消费者可以附加到同一个订阅。为非分区主题或分区主题的每个分区挑选一个主消费者并接收消息。当主消费者断开连接时,所有(未确认的和后续的)消息都被传递给排在后面的消费者。对于分区主题,经纪人将按优先级和消费者名称的词汇顺序对消费者进行排序。对于非分区主题,经纪人将按照消费者订阅非分区主题的顺序挑选消费者。在下图中,消费者B-0是主消费者,而如果消费者B-0断开连接,消费者B-1将是排在后面的消费者接收消息。
Demo
package com.project.pulsar.delayMsgAndSubscriptionsModel;
import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 灾备模式
*/
@RestController
public class FailoverMessageController {
@Autowired
PulsarConf pulsarConf;
String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java
/**
* 生产消息
*
* @param msg
* @throws PulsarClientException
*/
@GetMapping("/failover/sendMsg")
public MessageId sendMsg(String msg) throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Producer<byte[]> producer1 = pulsarFactory.newProducer()
.topic(topic)
.create();
return producer1.send(msg.getBytes());
}
/**
* 手动执行获取消息
*
* @throws PulsarClientException
*/
@GetMapping("/failover/comsumer")
public void comsumerByArtificial() throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Consumer<byte[]> consumer = pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Failover)//指定模式
.subscribe();
Message<byte[]> receive = consumer.receive();
System.out.println(new String(receive.getData()));
consumer.acknowledge(receive);//确认消息被消费
consumer.close();
}
/**
* 手动执行获取消息
*
* @throws PulsarClientException
*/
@GetMapping("/failover/comsumer2")
public void comsumerByArtificial2() throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Consumer<byte[]> consumer = pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Failover)//指定模式
.subscribe();
Message<byte[]> receive = consumer.receive();
System.out.println(new String(receive.getData()));
consumer.acknowledge(receive);//确认消息被消费
consumer.close();
}
}
代码下载
代码见此delayMsgAndSubscriptionsModel包下FailoverMessageController.java
共享Shared
在共享或循环模式下,多个使用者可以附加到同一订阅。消息在消费者之间以循环分发的方式传递,任何给定的消息只传递给一个消费者。当消费者断开连接时,发送给它但未确认的所有消息将重新安排发送给其余消费者。
在下图中,Consumer-C-1和Consumer-C-2可以订阅该主题,但Consumer-C-3和其他人也可以订阅。
Demo
package com.project.pulsar.delayMsgAndSubscriptionsModel;
import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 共享模式
*/
@RestController
public class ShareMessageController {
@Autowired
PulsarConf pulsarConf;
String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java
/**
* 生产消息
*
* @param msg
* @throws PulsarClientException
*/
@GetMapping("/share/sendMsg")
public MessageId sendMsg(String msg) throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Producer<byte[]> producer1 = pulsarFactory.newProducer()
.topic(topic)
.create();
return producer1.send(msg.getBytes());
}
/**
* 手动执行获取消息
*
* @throws PulsarClientException
*/
@GetMapping("/share/comsumer")
public void comsumerByArtificial() throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Consumer<byte[]> consumer = pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Shared)//指定模式
.subscribe();
Message<byte[]> receive = consumer.receive();
System.out.println(new String(receive.getData()));
consumer.acknowledge(receive);//确认消息被消费
consumer.close();
}
/**
* 手动执行获取消息
*
* @throws PulsarClientException
*/
@GetMapping("/share/comsumer2")
public void comsumerByArtificial2() throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Consumer<byte[]> consumer = pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Shared)//指定模式
.subscribe();
Message<byte[]> receive = consumer.receive();
System.out.println(new String(receive.getData()));
consumer.acknowledge(receive);//确认消息被消费
consumer.close();
}
}
代码下载
代码见此delayMsgAndSubscriptionsModel包下ShareMessageController.java
按照Key共享Key_Shared
在Key_Shared模式下,多个消费者可以附加到同一个订阅。消息在消费者之间分布传递,具有相同密钥或相同订购密钥的消息只传递给一个消费者。无论消息被重新传递多少次,它都会被传递给同一个消费者。当一个消费者连接或断开连接时,将导致所服务的消费者对消息的某些键进行改变。
当您使用Key_Shared模式时,Key_Shared模式的局限性,应注意:您需要指定键或orderingKey的消息。你不能使用与Key_Shared模式的累积确认。你的生产者应禁用批处理或使用一个基于批处理生成器。
Demo
package com.project.pulsar.delayMsgAndSubscriptionsModel;
import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 共享模式
*/
@RestController
public class KeyShareMessageController {
@Autowired
PulsarConf pulsarConf;
String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java
/**
* 生产消息
*
* @param msg
* @throws PulsarClientException
*/
@GetMapping("/keyShare/sendMsg")
public MessageId sendMsg(String msg) throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
//持久化 租户 命名空间 主题
Producer<byte[]> producer1 = pulsarFactory.newProducer()
.topic(topic)
.create();
return producer1.send(msg.getBytes());
}
@Bean
public void comsumerByListener1() throws PulsarClientException {
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
MessageListener myMessageListener = (consumer, msg) -> {
try {
System.out.println("comsumerByListener1: " + new String(msg.getData()));
consumer.acknowledge(msg);
} catch (Exception e) {
consumer.negativeAcknowledge(msg);
}
};
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscriptionByListener")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Key_Shared)
.messageListener(myMessageListener)
.subscribe();
}
@Bean
public void comsumerByListener2() throws PulsarClientException {
String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
MessageListener myMessageListener = (consumer, msg) -> {
try {
System.out.println("comsumerByListener2: " + new String(msg.getData()));
consumer.acknowledge(msg);
} catch (Exception e) {
consumer.negativeAcknowledge(msg);
}
};
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscriptionByListener")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Key_Shared)
.messageListener(myMessageListener)
.subscribe();
}
}
代码下载
代码见此delayMsgAndSubscriptionsModel包下KeyShareMessageController.java
延时消息
仅对Share和KeyShare模式有效,其他模式均为立即发送
延时消息有两种模式:
-
指定时长后发送
deliverAfter(3L, TimeUnit.MINUTES)
-
指定时间发送
deliverAt(LocalDateTime.of(2021, 9, 20, 16, 20).toInstant(ZoneOffset.of("+8")).toEpochMilli() )
Demo
package com.project.pulsar.delayMsgAndSubscriptionsModel;
import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* 延时消息
*/
@RestController
public class DelayMessageController {
@Autowired
PulsarConf pulsarConf;
String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java
/**
* 生产消息
*
* @param msg
* @throws PulsarClientException
*/
@GetMapping("/delay/sendMsg")
public void sendMsg(String msg) throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-delay-topic";
//持久化 租户 命名空间 主题
Producer<byte[]> producer = pulsarFactory.newProducer()
.topic(topic)
.create();
producer.newMessage().deliverAfter(2L, TimeUnit.MINUTES).key("key-1").value("message-1-1".getBytes()).send();
// // MessageId send = producer1.newMessage().deliverAfter(3L, TimeUnit.MINUTES).value("Hello Pulsar!".getBytes()).send();//指定消息在X时分秒后执行
// MessageId send =producer1.newMessage().deliverAt(
// LocalDateTime.of(2021, 9, 20, 16, 20).toInstant(ZoneOffset.of("+8")).toEpochMilli()
// ).value(msg.getBytes()).send();//指定消息在2021/09/20 15:52分发送,如发送时日期已过,会立刻发送
}
/**
* 手动执行获取消息
*
* @throws PulsarClientException
*/
@GetMapping("/delay/comsumer")
public void comsumerByArtificial() throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-delay-topic";
//持久化 租户 命名空间 主题
Consumer<byte[]> consumer = pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Key_Shared)//指定模式
.subscribe();
Message<byte[]> receive = consumer.receive();
System.out.println(new String(receive.getData()));
consumer.acknowledge(receive);//确认消息被消费
consumer.close();
}
/**
* 手动执行获取消息
*
* @throws PulsarClientException
*/
@GetMapping("/delay/comsumer2")
public void comsumerByArtificial2() throws PulsarClientException {
PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
String topic = "persistent://" + tenantName + "/" + namespace + "/my-delay-topic";
//持久化 租户 命名空间 主题
Consumer<byte[]> consumer = pulsarFactory.newConsumer()
.topic(topic)
.subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
.subscriptionType(SubscriptionType.Key_Shared)//指定模式
.subscribe();
Message<byte[]> receive = consumer.receive();
System.out.println(new String(receive.getData()));
consumer.acknowledge(receive);//确认消息被消费
consumer.close();
}
}
代码下载
代码见此delayMsgAndSubscriptionsModel包下DelayMessageController.java
Pulsar还有很多特性,写不过来了,以后用到再总结,有问题就官网、Github、谷歌、百度