消息队列理论
作用
- 流量削峰:削峰填谷
- 异步处理:将同步请求转换为异步,并且请求放入队列后,可以有多个消费者消费,提升整体的响应时间
- 服务解耦:服务方只需要把消息放入消息队列,下游谁需要就由谁订阅,下游系统的增减,不影响服务方。(openStack整个服务间的交互全都采用mq,默认rabbitMq)
基本概念
两种模型
- 队列模型(Quene):一个队列可以有多个生产者和消费者。但消费者之间是竞争关系,每条消息只能被一个消费者所消费。RabbitMq采用队列的方式解决一条消息被多个消费者接收,是通过Exchange模块将消息发送至多个队列
- 主题模型(Topic,发布/订阅模型):生产者将消息发往一个topic主题中,所有订阅了这个topic的订阅者都能消费这条消息。RabbitMq采用队列模型,RocketMq和kafka采用发布订阅模型,activeMq支持两种模型
producer/broker/consumer
- 为了提高并发度,Topic模式往往还会引入队列或者分区的概念,即消息发往Topic下的某个队列或分区中。RocketMq叫队列,kafka中叫分区。如某个Topic有5个队列,那么该主题的并发度就提高为5,可以有五个消费者并行消费该主题的消息。一般可以采用轮询或者hash取余的方式将消息分配到不同的队列当中
- 为了提高消费者的并发度,消费者通常都有消费者组(consumer group)的概念,消费者都是属于某个消费者组的。一个消息会发往多个订阅了这个topic的消费者组。这个消息实际上是写到了topic下的某个队列中,消费者组中的某个消费者对应消费一个队列的消息,同一消费者组下的应用,可以认为是同一应用的集群,一个topic下的一个队列,只会对应消费组下的一个实例(或一个线程),如果实例数大于队列数,则有的实例是不会分配消息的。
- 物理上一条消息处理副本拷贝以外,一条消息在broker中只会有一份,每个消费组有自己的消费点位(offset)来标识消费到的位置。offset之前的消息是被这个消费组消费过的,这个offset是队列级的,每个消费组都会维护订阅的topic下的每个队列的offset
rocketmq offset的维护
[参考文章](https://blog.csdn.net/agonie201218/article/details/120242146)
- **本地管理模式:**消费者广播模式下,每个消费者将消费点位以json的方式存储在本地文件中,每个消费者管理自己的消费点位,各消费者间不存在消费进度的交集
- **远程管理模式:**消费者集群消费模式下,topic下每个quene的消费点位以json文件的方式持久化在broker的本地文件中,broker内存中会形成一个双层map,[topic, [queneId, offset]]。正常情况下topic下的一个quene只会被一个消费者消费,使用远程管理的模式,主要是为保证rebalance的机制
- 消费者启动时,要消费的第一条消息的起始位置有三种:1. CONSUME_FROM_LAST_OFFSET从queue的当前最后一条消息开始消费;2. CONSUME_FROM_FIRST_OFFSET从queue的第一条消息开始消费;3.CONSUME_FROM_TIMESTAMP从指定的具体时间戳位置的消息开始消费。
- offset提交broker支持同步和异步的方式,同步提交offset时,会等待broker响应后再进行下一条消息的消费;异步提交offset时,不用等待就继续消费消息。默认异步提交的方式,但如果发生rebance,则因为消费者消费的消息offset可能大于broker存的offsetid,下一个消费者会从broker 存储的offset的位置继续消费,可能会有消息重复消费的问题
怎么保证消息不丢失
- 生产者:可靠消息,本地消息表与本地事务同一个线程,消息表记录消息发送状态,失败定时重试
- broker:刷盘后,或者至少有两个节点都写入成功后再返回生产者消息发送成功
- 消费者:业务处理完成后再更新offset,为缩短时间可批量处理,或先做消息持久化再异步处理
处理重复消息
幂等机制: 版本号、业务流水号、业务状态等
如何保证消息有序性
- 全局有序: 一个topic下一个队列,性能低,实际诉求少。
- 部分有序: 一个topic下多个队列,可以按照一定的规则,把需要局部保持顺序的消息路由到一个队列,由一个消费者消费。生产者自定义消息发往topic下某个quene的规则
处理消息堆积
原因: 生产者的生产速率与消费者的消费速率不匹配
优化:1. 优化消费者的处理性能;2. 增加topic队列和消费者数量,即水平扩容,注意一个topic下的队列只会分配给一个消费者。
rocketMq消息消费
- 消费者和broker的消息获取,有推拉两种模式,activeMq用推的模式。RocketMq和kafka均采用拉的模式,通过长轮询的方式,通过消费者等待消息,当有消息时broker会直接返回消息,如果没有消息,则会采用延迟处理的方式,并且为了保证消息实时性,当有新消息到来时,会及时返回消息
- rocketMq消费者和broker间TCP长连接。
RocketMq事务消息
- rocketmq事务消息事务开始时小发起一个半消息,本地事务执行后,根据执行结果发送消息的提交或者回滚,半消息对消费者是不可见的,消息提交后才放入正常队列供消费者消费。
- 生产者发送的提交或回滚有可能会失败,因此需要producer暴露一个接口,供broker定时查询事务状态。
RocketMq
原理
特点
- 吞吐量高:单机吞吐量可达十万级
- 可用性高:分布式架构
- 消息可靠性高:经过参数优化配置,可以做到消息零丢失
- 功能较为完善,分布式架构易于扩展
- 支持10亿级别的消息堆积,不会因为堆积导致性能下降
- 稳定性高,经过阿里双11考验
- Java语言实现
部署
RocketMq由四部分构成:Producer、Consumer、Broker、NameServer
- producer(生产者):可集群部署,先与nameServer中的一台建立长链接,获取当前要发送的topic存放在哪个Broker Master上,再与其建立长连接,支持多种负载均衡模式发送消息。producer会与满足条件的多个broker建立长连接,轮询他们来发送消息,同步发送消息失败的情况下,默认(retryTimesWhenSendFailed = 2)会重试其他节点两次,异步发送失败也会重试两次(retryTimesWhenSendAsyncFailed = 2),但只会重试当前失败的节点。
- consumer(消费者):可集群部署,也会先与nameServer中的一台建立长链接,获取当前要的消息topic存放在哪个Broker master 、slaver上,然后与他们建立长连接,支持集群消费和广播消费。消息消费失败,rocketmq会给每个消费者组设置一个重试队列,%retry%+consumerGroup,设置了很多重试级别来延迟重试时间。超过一定重试次数都失败,会移入死信队列Topic %DLQ%" + ConsumerGroup,人工处理死信队列里的消息
- Broker: 负责消息的存储、查询消费,支持主从部署,一个master对应多个slave,master支持读写,slave只支持读。**broker会向集群中的每一台nameServer注册自己的路由信息。**生产上不建议开启自动创建主题的配置,可能会导致消息发送不均衡。多Master多Slave模式,同步双写部署模式,会避免丢消息
- nameServer: 是一个简单的Topic路由注册中心,支持Broker的动态注册发现,保存Topic和Broker的关系。通常也集群部署,但各nameServer间不会相互通信,每个nameServer都有完整的路由信息,即无状态。维护集群中存活的broker列表,broker信息,主题和队列信息
工作过程:
先启动nameServer集群,各nameServer间不会有信息交互。Broker启动后,会向所有nameServer定时发送心跳包(默认30s),包括:ip/port/topicInfo,nameServer会定期扫描broker存活列表,如果超过120s没有心跳,则移除此broker信息,代表下线。
每个nameServer就知道集群中所有broker的信息,producer上线,从某个nameserver中可以得知他要发送的topic位于哪个broker上,与对应broker(master角色)建立长连接,发送消息。
consumer也从nameserver中获取broker master 、slave的信息,并与他们建立长连接,接收消息
使用建议:一个生产者应用集群使用一个Topic(生产者只关注消息生产,而不关心由谁来消费消息,消费者的增减对生产者无感),通过tags标记业务类型。不同的消费者设置不同的消费者组,对于不是当前消费者能消费的消息,跳过即可
RocketMq性能高的原因:RocketMq采用文件系统存储消息,采用顺序写的方式写入消息,使用零拷贝发送消息,这三者的结合极大地保证了RocketMq的性能
RocketMq消息清理
- Broker消息被顺序写入commitLog文件,清理不是以消息的粒度来清理的(消息大小不定,文件顺序写,按消息级清理性能低),而是commitLog文件为单位进行清理
- 清理commitLog文件,可以手工清理,或者满足一定条件下自动清理(commitlog文件存在一个过期时间,默认为72小时,即三天):
- 文件过期,到达清理时间点(默认凌晨4点)
- 文件过期,磁盘占用率已达过期清理警戒线(默认75%)
- 磁盘占用率达清理警戒线(默认85%),按预定规则清理文件,无论是否过期。默认会从最老的文件开始清理
- 磁盘占用率达到系统危险警戒线(默认90%)后,Broker将拒绝消息写入
- 对于RocketMQ系统来说,删除一个1G大小的文件,是一个压力巨大的IO操作。在删除过程中,系统性能会骤然下降。默认在访问量最小的时候删除;我们应保障磁盘空间的空闲率,不要使系统出现在其它时间点删除commitlog文件的情况。建议linux文件系统采用ext4(删除操作ext4比ext3高)
rocketMq高可用部署
4种高可用部署方法
推荐:
rocketMq基于** Dledger **的高可用部署方案,每个master对应两个slave(三台组成一个Dledger Group),主宕机两个从可以基于raft算法自动选主切换。
四种高可用部署方案分别为:
- 多master
- 多Master多Slave模式-异步复制
- 多Master多Slave模式-同步双写
- Dledger部署
rabbitMq
参考文章
镜像集群模式
kafka
kafka的客户端身份认证机制
- SASL(Simple Authentication and Security Layer)提供一种用户身份认证机制。参考文章
- kafka支持的认证方式很多,支持SSL或者SASL,下面四种SASL认证方式是官方支持的。参考文章
kafaka配置SASL/SCRAM安全认证
- kafka版本:kafka_2.13-3.6.0
- 所有相关配置和启动脚本均拷贝一份修改,使用SCRAM-SHA-512
kafka动态认证SCRAM配置参考
参考文章2
参考文章3
- 创建SCRAM证书
- broker通信账户:
./kafka-configs.sh --zookeeper localhost:2181 --alter --add-config 'SCRAM-SHA-256=[password=admin-sec],SCRAM-SHA-512=[password=admin-sec]' --entity-type users --entity-name admin
* 客户端通信用户(生产者和消费者共用,可分开)
./kafka-configs.sh --zookeeper localhost:2181 --alter --add-config 'SCRAM-SHA-512=[password=password]' --entity-type users --entity-name app
* 证书查看和维护
# 查看
./kafka-configs.sh --zookeeper localhost:2181 --describe --entity-type users --entity-name app
# 删除
./kafka-configs.sh --zookeeper localhost:2181 --alter --delete-config 'SCRAM-SHA-512' --entity-type users --entity-name app
- 服务端(broker)配置
- 新增kafka_server_jaas.conf
KafkaServer {
org.apache.kafka.common.security.scram.ScramLoginModule required
username="admin"
password="admin-sec";
};
* broker启动脚本kafka-server-start-saal.sh(copy from kafka-server-start.sh)
# 启动脚本添加认证文件配置
exec $base_dir/kafka-run-class.sh $EXTRA_ARGS -Djava.security.auth.login.config=/opt/kafka/kafka_2.13-3.6.0/config/kafka_server_jaas.conf kafka.Kafka "$@"
* 配置server-sasl.properties (copy from server.properties)
#认证配置
listeners=SASL_PLAINTEXT://192.168.195.129:9092
security.inter.broker.protocol=SASL_PLAINTEXT
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
sasl.enabled.mechanisms=SCRAM-SHA-512
#ACL配置
# 黑名单模式,特殊指定的名单是不允许访问的名单,没指定的是可以访问的;配置为false为白名单模式,即需要还给用户分配topic的发送和接收权限
allow.everyone.if.no.acl.found=true
super.users=User:admin
# authorizer.class.name=kafka.security.auth.SimpleAclAuthorizer
* 启动kafka broker
./bin/kafka-server-start-saal.sh ./config/server-sasl.properties
- 客户端配置(包含admin/producer/consumer)
- admin配置:kafka_client_scram_admin_jaas.conf
KafkaClient { org.apache.kafka.common.security.scram.ScramLoginModule required username="admin" password="admin-sec"; };
* producer/consumer配置:kafka_client_scram_client_jaas.conf
KafkaClient { org.apache.kafka.common.security.scram.ScramLoginModule required username="app" password="passw0rd"; };
* producer/consumer启动脚本sh:kafka-console-producer-saal.sh(copy from kafka-console-producer.sh);kafka-console-consumer-saal.sh(copy from kafka-console-consumer.sh)
# 启动脚本添加参数
-Djava.security.auth.login.config=/opt/kafka/kafka_2.13-3.6.0/config/kafka_client_scram_client_jaas.conf
* consumer-saal.properties和producer-saal.properties (copy from consumer.properties/producer.properties)
# 要追加的配置项目
security.protocol=SASL_PLAINTEXT
sasl.mechanism=SCRAM-SHA-512
* 命令行版本的producer/consumer 使用
# 生产者命令行
./kafka-console-producer-saal.sh --broker-list localhost:9092 --topic test --producer.config ../config/producer-saal.properties
# 消费者命令行
./kafka-console-consumer-saal.sh --bootstrap-server localhost:9092 --topic test --from-beginning --consumer.config ../config/consumer-saal.properties
* java 连接kafka,通过SCRAM模式