消息中间件-RocketMQ (1)
对于消息中间件,大家应该都很熟悉吧,当然也与现在可供选择的有太多种类,用过rabbitMQ,RocketMq,其他的没有涉及,这篇主要是在工作之余,看了看相关介绍,简单做一下笔记,毕竟好记性不如烂烂烂烂烂烂笔头...
特此说明,本篇主要是观看视频及其他博文,侵删
MQ介绍
消息队列是一种先进先出的数据结构
优点:
1. 应用解耦:系统的耦合性越强,容错性就越低。
2.流量削峰:应用系统如果遇到系统请求流量的瞬间猛增,有可能会将系统压垮。有了消息队列可以将大量请求缓存起来,分散到很长一段时间处理,这样可以大大提到系统的稳定性和用户体验。
3.数据分发:通过消息队列可以让数据在多个系统更加之间进行流通。数据的产生方不需要关心谁来使用数据,只需要将数据发送到消息队列,数据使用方直接在消息队列中直接获取数据即可
缺点:
1. 系统可用性降低:系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响。【如何保证MQ高可用】
2.系统复杂度提高:MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调用。【如何保证消息没有重复消费?消息丢失问题?消息传递顺序性保证?】
3.一致性问题:A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统处理成功,D系统处理失败。【如何保证消息的一致性问题?】
MQ比较
详细可 参考这篇:https://blog.csdn.net/Same_Liu/article/details/89517131

RocketMQ
RocketMQ是阿里巴巴 MQ中间件,使用java语言开发,在阿里内部,承接了双十一等高并发场景的消息流转,能够处理万亿级的消息。
突然想到在没有MQ的时候,出现这种类似场景,会不会有技术leader提出:'咱们开发一个Notify吧'。这个或许就是当初开发MQ的初衷吧......
1. 角色介绍:

消息生产者Producer
负责生产消息,一般由业务系统负责生产消息。
生产者的作用就是将消息发送到 MQ,生产者本身既可以产生消息,也可以对外提供接口,由外部应用来调用接口,再由生产者将收到的消息发送到 MQ。
消息消费者Consumer
负责消费消息,一般是后台系统负责异步消费。
消费者,从Broker拉取消息进行消费。从应用角度来说有两类消费者:
①、PullConsumer:应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。
②、PushConsumer:该模式下Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。
Broker
broker主要负责消息的存储、投递和查询以及服务高可用保证。--类似邮局
主要包含以下几个重要模块:
①、Remoting Module:整个broker的实体,负责处理来自clients端的请求。
②、Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的topic订阅信息
③、Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。
④、HA Service:高可用服务,提供master broker 和 slave broker之间的数据同步功能。
⑤、Index Service:根据特定的Message key对投递到broker的消息进行索引服务,以提供消息的快速查询。
Name Server
NameServer其角色类似dubbo中的zookeeper--类似邮局管理者。支持Broker的动态注册与发现。主要包括两个功能:
①、Broker管理。
NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活。
②、路由信息管理。
每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。
NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Produce,Consumer仍然可以动态感知Broker的路由的信息。
2. Meessage结构:
message是RocketMq消息传递的载体,其数据结构如下:
public class Message implements Serializable {
private static final long serialVersionUID = -1;
private String topic; -------------------------------------- topic我们可以理解为第一级消息类型,类比于书的标题。Topic是生产者发送消息和消费者拉取消息时的消息的类别
private int flag; ------------------------------------------- tag我们可以理解为第二级消息类型,类比于书的目录。便于检索和使用消息。 Tag,换句话的意思就是子主题,为用户提供了额外的灵活性。有了标签,来自同一业务模块的具有不同目的的消息可以具有相同的主题和不同的标记。标签有助于保持代码的清晰和连贯,同时标签也方便RocketMQ提供的查询功能。
private Map<String, String> properties; ------------ 该字段是一个K-V结构。RockerMq预定义了一组内置属性,Properties中存储了Message其余各项属性。除了内置属性之外,用户还可以设定任意自定义属性。(属性的大小也是有大小限制的)
private byte[] body; ---------------------------------------- producer要发送的消息内容体,以字节数组的形式进行存储。Message消息会有一定的大小限制。
private String transactionId; ---------------------------- TransactionId是当消息是事务消息的时候,相关消息的事务编号。
}
3. groupName:
RocketMq也有组的概念。代表具有相同角色的生产者组合或消费者组合,称为生产者组或消费者组。
作用:
在集群中,当一个生产者producer宕机,本地事务回滚后,可以继续联系该group下的另外一个生产者实例,不至于导致业务走不下去。
在消费者group中,可以实现消费消息的负载均衡和消息容错。
此外,有了groupName后,集群的动态扩容就非常方便了。只需要在新加入的机器中配置相同的groupName,该机器启动后,即可立即加入到所在的group中,参与消息的生产和消费。
4. MessageExt结构:
public class MessageExt extends Message {
private static final long serialVersionUID = -1L;
private int queueId; -------------------------------------- 记录MessageQueue编号,消息会被发送到Topic下的MessageQueue
private int storeSize; ------------------------------------ 记录消息在Broker存盘大小
private long queueOffset; ----------------------------- 记录在ConsumeQueue中的偏移
private int sysFlag; -------------------------------------- 记录一些系统标志的开关状态,MessageSysFlag中定义了系统标识
private long bornTimestamp; ------------------------ 消息创建时间,在Producer发送消息时设置
private SocketAddress bornHost; ------------------
private long storeTimestamp; ------------------------ 消息存储时间
private SocketAddress storeHost; ----------------- 记录存储该消息的Broker地址
private String msgId; ----------------------------------- 消息Id
private long commitLogOffset; ---------------------- 记录在Broker中存储偏移
private int bodyCRC; ------------------------------------ 消息内容CRC校验值
private int reconsumeTimes; ------------------------ 消息重试消费次数
private long preparedTransactionOffset; -------- 事务详细相关字段
}
参考MQ的结构图,操作流程如下介绍:
1. 启动Namesrv,Namesrv起来后监听端口,等待Broker、Produer、Consumer连上来,相当于一个路由控制中心。
2. Broker启动,跟所有的Namesrv保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有topic信息。注册成功后,namesrv集群中就有Topic跟Broker的映射关系。
3. 收发消息前,先创建topic,创建topic时需要指定该topic要存储在哪些Broker上。也可以在发送消息时自动创建Topic。
4. Producer发送消息,启动时先跟Namesrv集群中的其中一台建立长连接,并从Namesrv中获取当前发送的Topic存在哪些Broker上,然后跟对应的Broker建长连接,直接向Broker发消息。
5. Consumer跟Producer类似。跟其中一台Namesrv建立长连接,获取当前订阅Topic存在哪些Broker,然后直接跟Broker建立连接通道,开始消费消息。
RocketMQ消息发送与消费
使用RocketMQ可以发送普通消息、顺序消息、事务消息,顺序消息能实现有序消费,事务消息可以解决分布式事务实现数据最终一致。
RocketMQ有2种常见的消费模式,分别是DefaultMQPushConsumer和DefaultMQPullConsumer模式,这2种模式字面理解一个是推送消息,一个是拉取消息。这里有个误区,其实无论是Push还是Pull,其本质都是拉取消息,只是实现机制不一样。
1. DefaultMQPushConsumer其实并不是broker主动向consumer推送消息,而是consumer向broker发出请求,保持了一种长链接,broker会每5秒会检测一次是否有消息,如果有消息,则将消息推送给consumer。使用DefaultMQPushConsumer实现消息消费,broker会主动记录消息消费的偏移量。
2. DefaultMQPullConsumer是消费方主动去broker拉取数据,一般会在本地使用定时任务实现,使用它获得消息状态方便、负载均衡性能可控 ,但消息的及时性差,而且需要手动记录消息消费的偏移量信息 ,所以在工作中多数情况推荐使用Push模式。
RocketMQ作为MQ消息中间件,ack机制必不可少,在RocketMQ中常见的应答状态如下:
public enum LocalTransactionState { // 主要针对事务消息的应答状态
COMMIT_MESSAGE, //消息提交
ROLLBACK_MESSAGE, //消息回滚
UNKNOW, //未知状态,一般用于处理超时等现象
}
public enum ConsumeConcurrentlyStatus { // 主要针对消息消费的应答状态
CONSUME_SUCCESS, //消息消费成功
RECONSUME_LATER; //消息重试,一般消息消费失败后,RocketMQ为了保证数据的可靠性,具有重试机制
}
RocketMQ 目前只支持固定精度时间的延时消息发送(配置 Message.setDelayTimeLevel 延时精度),默认有18个时间精度的 level,分别是:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h, (初始化 Message 消息对象之后,调用 Message.setDelayTimeLevel(int level) 方法来设置延迟级别,按照序列取相应的延迟级别,例如 level=3,则延迟为 10s 再发送消息。)
1. MQ普通消息
SendResult result = producer.send(message);
DefaultMQPushConsumer -- MessageListenerConcurrently
2. MQ顺序消息
SendResult result = producer.send(
message, //要发送的消息
new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return mqs.get((Integer) arg);
}
}, 1);//设置存入第几个队列中,这里是下标,从0开始
DefaultMQProducer -- MessageListenerOrderly
在MQ的模型中,顺序需要由3个阶段去保障:消息被发送时保持顺序;消息被存储时保持和发送的顺序一致;消息被消费时保持和存储的顺序一致
发送时保持顺序意味着对于有顺序要求的消息,用户应该在同一个线程中采用同步的方式发送。存储保持和发送的顺序一致则要求在同一线程中被发送出来的消息A和B,存储时在空间上A一定在B之前。而消费保持和存储一致则要求消息A、B到达Consumer之后必须按照先A后B的顺序被处理。

3. MQ事物消息
在RocketMQ4.3.0版本后,开放了事务消息这一特性,对于分布式事务而言,最常说的还是二阶段提交协议。

上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。
1.事务消息发送及提交:
(1) 发送消息(half消息:发送正式消息之前的前置准备消息,称为半消息)。
(2) 服务端响应消息写入结果。
(3) 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。
(4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
2.补偿流程:
(1) 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
(2) Producer收到回查消息,检查回查消息对应的本地事务的状态
(3) 根据本地事务状态,重新Commit或者Rollback
其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。
3.事务消息状态(事物消息共有三种状态):
(1)TransctionStatus.CommitTransaction 提交事务,允许消费者消费消息
(2)TransctionStatus.RollbackTransaction 回滚事务,允许消息将被删除,不允许消费
(3)TransctionStatus.Unknown 中间态,需通过检查消息队列来确定状态
MQ 发送事物消息时,需创建一个事务消息生产者TransactionProducer,事务消息发送消息对象是TransactionMQProducer,为了实现本地事务操作和回查,需要创建一个监听器,监听器需要实现TransactionListener接口
发送消息时,producer需要setTransactionListener()设置监听器,方便事物回查
RocketMQ实现分布式事务流程???(这个东西可以研究下,rocketMQ利用事物消息实现分布式事物,类似于二阶段提交)
RocketMQ高级知识了解
跳转链接:https://www.cnblogs.com/fn-f/p/12804334.html
【参考文献】:
https://blog.csdn.net/itanping/article/details/100919270
https://blog.csdn.net/Same_Liu/article/details/89517131
https://blog.csdn.net/Same_Liu/article/details/89517571
(同为十年寒窗,怎可甘拜下风 ---- 共勉 )
浙公网安备 33010602011771号