专业术语
Producer
消息生产者,负责产生消息,一般由业务系统负责产生消息。
Consumer
消息消费者,负责消费消息,一般是后台系统负责异步消费。
Push Consumer
Consumer 的一种,应用通常吐 Consumer 对象注册一个 Listener 接口,一旦收到消息,Consumer 对象立
刻回调 Listener 接口方法。
Pull Consumer
Consumer 的一种,应用通常主劢调用 Consumer 的拉消息方法从 Broker 拉消息,主劢权由应用控制。
Producer Group
一类 Producer 的集合名称,返类 Producer 通常収送一类消息,丏収送逡辑一致。
Consumer Group
一类 Consumer 的集合名称,返类 Consumer 通常消费一类消息,丏消费逡辑一致。
Broker
消息中转角色,负责存储消息,转収消息,一般也称为 Server。在 JMS 规范中称为 Provider。
广播消费
一条消息被多个 Consumer 消费,即使返些 Consumer 属亍同一个 Consumer Group,消息也会被 Consumer
Group 中的每个 Consumer 都消费一次,的 Consumer Group 概念可以为在消息划分方面无意 。
在 CORBA Notification 规范中,消费方式都属亍广播消费。
在 JMS 规范中,相当亍 JMS publish/subscribe model
集群消费
一个 Consumer Group 中的 Consumer 实例平均分摊消费消息。例如某个 Topic 有 9 条消息,其中一个
Consumer Group 有 3 个实例(可能是 3 个进程,或者 3 台机器),那么每个实例只消费其中的 3 条消息。
在 CORBA Notification 规范中,无此消费方式。
在 JMS 规范中,JMS point-to-point model 与之类似,但是 RocketMQ 的集群消费功能大等亍 PTP 模型。
因为 RocketMQ 单个 Consumer Group 内的消费者类似于 PTP,但是一个 Topic/Queue 可以被多个 Consumer
Group 消费。
顺序消息
消费消息的顺序要同发送消息的顺序一致,在 RocketMQ 中,主要指的是局部顺序,即一类消息为满足顺序性,必须 Producer 单线程顺序发送,且发送到同一个队列,这样 Consumer 就可以顺序 Producer 发送的顺序去消费消息。
普通顺序消息
顺序消息的一种,正常情况下可以保证完全的顺序消息,但是一旦发生通信异常,Broker 重启,由于队列总数发生发化,哈希取模后定位的队列会发化,产生短暂的消息顺序不一致。
如果业务能容忍在集群异常情冴(如某个 Broker 宕机或者重启)下,消息短暂的乱序,使用普通顺序方式比较合适。
严格顺序消息
顺序消息的一种,无论正常异常情况都能保证顺序,但是牺牲了分布式 Failover 特性,即 Broker 集群中只要有一台机器丌可用,则整个集群都不可用,服务可用性大大降低。
如果服务器部署为同步双写模式,此缺陷可通过备机自劢切换为主避免,不过仍然会存在几分钟的服务不可用。(依赖同步双写,主备自动切换,自动切换功能目前还未实现)
目前已知的应用只有数据库 binlog 同步强依赖严格顺序消息,其他应用绝大部分都可以容忍短暂乱序,推荐使用普通的顺序消息。
架构模型
什么是RocketMQ?
创建一个topic (逻辑概念)默认创建4个消息队列queue(物理的)
MQ物理部署的结构图
Name server 是一个几乎无状态节点,可集群部署,节点之间无任何信息同步,只是单纯的做服务发现和路由。
每个broker于name server中的所有节点建立的长链接,定时注册topic信息到所有的name service中
Producer 与 Name Server 集群中的其中一个节点(随机选择)建立长连接,定期从 Name Server 取 Topic 路由信息,并向提供 Topic 服务的 Master 建立长连接,且定时向 Master 収送心跳。 Producer 完全无状态,可集群部署
Consumer 与 Name Server 集群中的其中一个节点(随机选择)建立长连接,定期从 Name Server 取Topic 路由信息,并向提供 Topic 服务的 Master、 Slave 建立长连接,且定时向 Master、 Slave 収送心跳。 Consumer既可以从 Master 订阅消息,也可以从 Slave 订阅消息,订阅规则由 Broker 配置决定。
其实name server就是管理topic的,broker创建的topic都会去nameserver上注册,并带有地址信息,所有的生产者和消费者都去nameserver上找对应的topic 获取对应的broke的地址信息,通过这个地址和broker创建长链接,生产和消费消息。
整体架构详解
9大模块
• rocketmq-common:通用的枚举、基类方法、或者数据结构,包名有admin、consumer、filter、hook、message
• rocketmq-remoting:使用netty的客户端、服务端,使用fastjson序列化,自定义二进制协议
• rocketmq-srvutil:只有一个ServerUtil类,只提供Server程序依赖,尽可能减少客户端依赖
• rocketmq-store:消息存储,索引,consumerLog,commitLog等
• rocketmq-client:消息发送和接收,包含consumer和producer
• rocketmq-filtersrv:消息过滤器
• rocketmq-broker:服务端,接受消息,存储消息,consumer拉取消息
• rocketmq-tools:命令行工具
• rocketmq-namesrv:NameServer,类似服务注册中心,broker在这里注册,consumer和producer在这里找到broker地址
层次图:
最上面是组建,拿出来单独能用,
例子
消息存储
RocketMQ的消息存储是由consume queue和commit log配合完成的
Consume Queue
consume queue是消息的逻辑队列,相当于字典的目录,用来指定消息在物理文
件commit log上的位置。
顺序消费机制详解
这个模型也仅仅是理论上可以保证消息的顺序,在实际场景中可能会遇到下面的问题:
样的模型就严格保证消息的顺序
RocketMQ通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。比如下面的示例中,订单号相同的消息会被先后发送到同一个队列中:
// RocketMQ通过MessageQueueSelector中实现的算法来确定消息发送到哪一个队列上
// RocketMQ默认提供了两种MessageQueueSelector实现:随机/Hash
// 当然你可以根据业务实现自己的MessageQueueSelector来决定消息按照何种策略发送到消息队列中
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
}, orderId);
在获取到路由信息以后,会根据MessageQueueSelector
实现的算法来选择一个队列,同一个OrderId获取到的肯定是同一个队列。
局部顺序消费,,就是一个订单消息发送到一个队列中,才能顺序消费。
consumer.registerMessageListener(new MessageListenerOrderly() {
AtomicLong consumeTimes = new AtomicLong(0);
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
// 设置自动提交
context.setAutoCommit(true);
for (MessageExt msg : msgs) {
System.out.println(msg + ",内容:" + new String(msg.getBody()));
}
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
;
return ConsumeOrderlyStatus.SUCCESS;
}
});
事物消息机制
当用户增长到一定程度,Bob和Smith的账户及余额信息已经不在同一台服务器上了,那么上面的流程就变成了这样:
这时候你会发现,同样是一个转账的业务,在集群环境下,耗时居然成倍的增长,这显然是不能够接受的。那我们如何来规避这个问题?
大事务 = 小事务 + 异步
将大事务拆分成多个小事务异步执行。这样基本上能够将跨机事务的执行效率优化到与单机一致。转账的事务就可以分解成如下两个小事务:
首先我们看下,先发送消息,大致的示意图如下:
可能大家会有很多的方法来解决这个问题,比如:直接将发消息放到Bob扣款的事务中去,如果发送失败,抛出异常,事务回滚。这样的处理方式也符合“恰好”不需要解决的原则。RocketMQ支持事务消息,下面我们来看看RocketMQ是怎样来实现的。
RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。细心的你可能又发现问题了,如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事物消息,这时候发现了Prepared消息,它会向消息发送者确认,Bob的钱到底是减了还是没减呢?如果减了是回滚还是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
总结:
本质就是两阶段提交:
简单说就是 消息发到mq 但是对消费者不可见,本机继续处理事务,处理成功会给mq发个ack确认信息,成功则对消费者可见,不成功则回滚,如果ack失败,mq会轮询这些不可见消息去获取状态。
这样基本上可以解决超时问题,但是如果消费失败怎么办?阿里提供给我们的解决方法是:人工解决。
消息重试机制
生产者 : 消息重投重试(保证数据的高可靠性)
消费者:消息处理异常(borker端到consumer端各种问题,比如网络原因闪断,消费端处理失败,ACK返回失败等等)
MessageDekayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
幂等去重策略详解
消息的高可靠投递就必然导致消息的重复
去重原则:幂等性,业务去重
去重策略:去重机制,业务拼接去重策略(比如唯一流水号,指纹码,版本号)
高并发下去重:采用redis去重(key天然支持原子性并且要求不可重复)
幂等性定义:
无论业务请求被执行多少次,我们结果都是唯一的不可变的。
天然幂等性,就是我们不做任何控制 消息无论执行多少次结构都是一样的。
用主键做去重,用唯一流水号做主键,在并发情况下,消息被发送多次,在入库时只有一条数据正常入库。
去重表,或者版本号
FilterServer机制及使用详解
简单消息过滤
consumer.subscribe("TopicTest1", "TagA || TagC || TagD");
如以上代码所示,简单消息过滤通过指定多个 Tag 来过滤消息。
1.Broker 所在的机器会启动多个 FilterServer 过滤进程
2. Consumer 启动后,会向 FilterServer 上传一个过滤的 Java 类
3. Consumer 从 FilterServer 拉消息,FilterServer 将请求转发给 Broker,FilterServer 从 Broker 收到消息后,按照Consumer 上传的 Java 过滤程序做过滤,过滤完成后返回给 Consumer。
项目实战(交易系统)
提交订单----》校验----使用优惠券------扣库存----创建交易单 (黄色区域内容在同一事务中,这是单机单模块系统)
支付流程
Producer 配置
PushConsumer 配置
PullConsumer 配置
Message 数据结构
共同学习,写下你的评论
评论加载中...
作者其他优质文章