原文地址
前言
这里简单介绍下Pulsar
作为消息的的使用,不涉及原理和高可用集群的部署,原理以及比较重要的服务端/客户端参数,会在后续《消息队列Pulsar详解》中聊到,这里只是作为一个Pulsar
入门的快速开始。关于不使用网络上其他热门消息队列Kafka
,Rabbitmq
的原因,一个是理由是平常用吐了,第二个是相对于老式消息队列,Pulsar
在设计的时候解决了他们的一些痛点,并且在一定程度上集成了他们都优点,博采众长,但是由于是一个相对较新的中间件,有些功能不完善也是不可避免的,看大家自己选择。如果是生产用,那肯定是稳定性会更重要一些
服务端部署
详细的部署文档参考Pulsar Get started,这里我们仅介绍Docker
容器的单例部署,基本的docker-compose.yml
如下
version: "3"
services:
pulsar:
image: 'apachepulsar/pulsar:3.1.1'
restart: always
container_name: 'pulsar'
hostname: 'pulsar'
ports:
- '9527:6650'
- '9528:8080'
privileged: true
volumes:
- pulsardata:/pulsar/data
- pulsarconf:/pulsar/conf
command: bin/pulsar standalone
volumes:
pulsardata:
pulsarconf:
由于是单机本地调用,这里不配置数据加密,身份证验证以及功能授权相关功能,如果有需要的小伙伴可以参考Pulsar security overview
客户端使用
我们这里仍然使用的是Java17
+Spring3
+Cloud4
架构,同时引用Pulsar
的方式是使用Spring
的封装包,但是替换Pulsar
核心为最高版本。之后可能会有一个大的更新,参考1.0.0-SNAPSHOT,但是目前还是以0.2.0为可用,可用随时关心最新版本以便升级
<dependency>
<groupId>org.springframework.pulsar</groupId>
<artifactId>spring-pulsar-spring-boot-starter</artifactId>
<version>0.2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-all</artifactId>
<version>3.1.1</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>javax.activation</artifactId>
<groupId>com.sun.activation</groupId>
</exclusion>
<exclusion>
<artifactId>validation-api</artifactId>
<groupId>javax.validation</groupId>
</exclusion>
</exclusions>
</dependency>
使用高版本的Java
会有一个问题,Pulsar
的DnsResolverUtil
需要深度反射sun.net.InetAddressCachePolicy (in module java.base)
的时候会没有权限,导致报错。在低版本,比如JDK8
的的使用中则没有这个限制,所以高版本的在使用Pulsar
时,在目前版本3.1.X
还是需要增加运行参数--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/sun.net=ALL-UNNAMED
,有关于--add-opens
更详细的解释参考–add-opens,关于Pulsar
的解释同时可以参考DnsResolverUtil “Cannot get DNS TTL settings from sun.net.InetAddressCachePolicy class” in Java 17
首先在启动类增加启用注解
@someelse
@EnablePulsar
public class YuiApplication {
public static void main(String[] args) {
SpringApplication.run(YuiApplication.class, args);
}
}
然后添加配置文件
spring:
pulsar:
client:
service-url: pulsar://your_ip:9527
这里我们简单聊下Pulsar
的几个核心概念,基本在其他消息中间件都能找到差不多对应的处理
Tenant
Pulsar
中租户的概念和我们一般Saas
场景中的租户一样,可以拥有自己的鉴权和命名空间等功能,用于完成一整个消息中间件的逻辑。目的是为了大型分布式系统对于不同应用场景的分割,更多内容参考Multi Tenancy
Namespaces
租户可以创建多个命名空间,用于区隔自己的业务范围,层级化管理,避免由于不同业务Topic
重复,操作失误导致的消息界面混乱问题
Topic
消息传递的通道,链接的生产者和消费者需要拥有相同的Topic
Subscription
消费者获取生产者消息的流程
Subscription types
目前支持的订阅的类型有:
- Exclusive:相同订阅名只有一个消费者可用获得消息
- Failover:容灾处理,相同订阅名只有一个获得消息,但是会根据可用性选择消费者
- Shared:即使是相同订阅名,所有消费者可以轮询获得消息
- Key_Shared:根据消息
Key
进行Hash
处理,从而选择消费者保证相同的Key
的消息不会被传到不同消费者
Schema
用于对象的序列化,我们可以指定/自定义序列化方式,用于简化数据传递步骤,增加消息通知的兼容性。具体参考schema-get-started
示例
一般情况下,我们主要的消息中间件有两个功能,一个是希望所有订阅的对象都可以收到消息,此时我们只需要给予每个消费者不同的订阅名称,并且设置Exclusive
订阅类型即可
public class MessageServiceImpl implements IMessageService {
@Resource
private PulsarTemplate<UserSendMsgDto> messagePulsarTemplate;
private static final String PULSAR_TOPIC_MSG_DATA = "msg-data";
@Override
public void sendMsg(String userId, UserSendMsgParam param) {
try {
UserSendMsgDto sendObj = ConversionUtils.convertObj(param, UserSendMsgDto.class);
sendObj.setSender(userId);
messagePulsarTemplate.send(PULSAR_TOPIC_MSG_DATA, sendObj);
} catch (Exception ex) {
log.error("[op:sendMsg] sendMsg fail");
ex.printStackTrace();
}
}
@PulsarListener(
subscriptionName = "msg-data-subscription-1",
topics = PULSAR_TOPIC_MSG_DATA,
subscriptionType = SubscriptionType.Exclusive,
schemaType = SchemaType.JSON
)
public void topicListener1(UserSendMsgDto msg) {
log.info("Received 1 String message: {}", JSON.toJSONString(msg));
}
@PulsarListener(
subscriptionName = "msg-data-subscription-2",
topics = PULSAR_TOPIC_MSG_DATA,
subscriptionType = SubscriptionType.Exclusive,
schemaType = SchemaType.JSON
)
public void topicListener2(UserSendMsgDto msg) {
log.info("Received 2 String message: {}", JSON.toJSONString(msg));
}
}
另一种是希望任意消费者收到消息处理即可,用于平衡负载,此时可以给予所有消费者相同的订阅名,并且设置Shared
订阅类型即可
public class MessageServiceImpl implements IMessageService {
@Resource
private PulsarTemplate<UserSendMsgDto> messagePulsarTemplate;
private static final String PULSAR_TOPIC_MSG_DATA = "msg-data";
@Override
public void sendMsg(String userId, UserSendMsgParam param) {
try {
UserSendMsgDto sendObj = ConversionUtils.convertObj(param, UserSendMsgDto.class);
sendObj.setSender(userId);
messagePulsarTemplate.send(PULSAR_TOPIC_MSG_DATA, sendObj);
} catch (Exception ex) {
log.error("[op:sendMsg] sendMsg fail");
ex.printStackTrace();
}
}
@PulsarListener(
subscriptionName = "msg-data-subscription-1",
topics = PULSAR_TOPIC_MSG_DATA,
subscriptionType = SubscriptionType.Shared,
schemaType = SchemaType.JSON
)
public void topicListener1(UserSendMsgDto msg) {
log.info("Received 1 String message: {}", JSON.toJSONString(msg));
}
@PulsarListener(
subscriptionName = "msg-data-subscription-1",
topics = PULSAR_TOPIC_MSG_DATA,
subscriptionType = SubscriptionType.Shared,
schemaType = SchemaType.JSON
)
public void topicListener2(UserSendMsgDto msg) {
log.info("Received 2 String message: {}", JSON.toJSONString(msg));
}
}