传统单机部署
1. 下载:https:/ / archive. apache. org/dist/rocketmq/
2. unzip rocketmq-all-4. 5. 0-bin-release. zip
3. cd rocketmq-all-4. 5. 0-bin-release
4. 启动 NameServer:bin/mqnamesrv
5. 启动 Broker:bin/mqbroker - n 127. 0. 0. 1:9876
1. 异常信息:Java HotSpot( TM) 64-Bit Server VM warning: INFO: os::commit_memory( 0x00000005c0000000, 8589934592, 0) failed; error='Cannot allocate memory' ( errno=12)
2. 异常原因:内存不足,MQ 默认配置是生产环境的,初始 JVM 的内存大小为 4G
3. 解决方案:调整默认内存的大小
3. 1 设置 NameServer 的内存大小
vim bin/runserver. sh
JAVA_OPT="${JAVA_OPT} -server -Xms128m -Xmx128m -Xmn128m -XX:MetaspaceSize=128m - XX:MaxMetaspaceSize=128m"
3. 2 设置 Broker 的内存大小
vim bin/runbroker. sh
JAVA_OPT="${JAVA_OPT} -server -Xms128m -Xmx128m -Xmn128m"
1. 测试发送消息
export NAMESRV_ADDR=127. 0. 0. 1:9876
cd bin
sh tools. sh org. apache. rocketmq. example. quickstart. Producer
2. 测试结果
SendResult [sendStatus=SEND_OK, msgId=AC110001473C7D4991AD336AEA5703E0, offsetMsgId=AC11000100002A9F00000000000E8580, messageQueue=MessageQueue [topic=TopicTest, brokerName=itcast, queueId=3], queueOffset=1323]
SendResult [sendStatus=SEND_OK, msgId=AC110001473C7D4991AD336AEA5903E1, offsetMsgId=AC11000100002A9F00000000000E8634, messageQueue=MessageQueue [topic=TopicTest, brokerName=itcast, queueId=0], queueOffset=1323]
3. 测试发送消息
sh tools. sh org. apache. rocketmq. example. quickstart. Consumer
4. 测试结果
ConsumeMessageThread_7 Receive New Messages: [ MessageExt [ queueId=2, storeSize=180, queueOffset=1322, sysFlag=0, bornTimestamp=1544456244818, bornHost=/ 172. 16. 55. 185:33702, storeTimestamp=1544456244819, storeHost=/ 172. 17. 0. 1:10911, msgId=AC11000100002A9F00000000000E84CC, commitLogOffset=951500, bodyCRC=684865321, reconsumeTimes=0, preparedTransactionOffset=0, toString( ) =Message{ topic='TopicTest' , flag=0, properties={ MIN_OFFSET=0, MAX_OFFSET=1325, CONSUME_START_TIME=1544456445397, UNIQ_KEY=AC110001473C7D4991AD336AEA5203DF, WAIT=true, TAGS=TagA} , body=[ 72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 57, 57, 49] , transactionId='null' } ] ]
ConsumeMessageThread_6 Receive New Messages: [ MessageExt [ queueId=2, storeSize=180, queueOffset=1323, sysFlag=0, bornTimestamp=1544456244833, bornHost=/ 172. 16. 55. 185:33702, storeTimestamp=1544456244835, storeHost=/ 172. 17. 0. 1:10911, msgId=AC11000100002A9F00000000000E879C, commitLogOffset=952220, bodyCRC=801108784, reconsumeTimes=0, preparedTransactionOffset=0, toString( ) =Message{ topic='TopicTest' , flag=0, properties={ MIN_OFFSET=0, MAX_OFFSET=1325, CONSUME_START_TIME=1544456445397, UNIQ_KEY=AC110001473C7D4991AD336AEA6103E3, WAIT=true, TAGS=TagA} , body=[ 72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 57, 57, 53] , transactionId='null' } ] ]
Docker 单机部署
1. 拉取镜像
docker pull foxiswho/rocketmq:server-4. 5. 0
docker pull foxiswho/rocketmq:broker-4. 5. 0
2. 创建 NameServer 容器
docker create - p 9876:9876 -- name mqserver
- e "JAVA_OPT_EXT=-server -Xms128m -Xmx256m -Xmn128m"
- e "JAVA_OPTS=-Duser.home=/opt"
- v / usr/local/rocketmq/mqserver/logs:/ opt/logs
- v / usr/local/rocketmq/mqserver/store:/ opt/store
foxiswho/rocketmq:server-4. 5. 0
3. 创建 Broker 容器
docker create - p 10911:10911 - p 10909:10909 -- net host -- name mqbroker
- e "JAVA_OPTS=-Duser.home=/opt"
- e "JAVA_OPT_EXT=-server -Xms128m -Xmx256m -Xmn128m"
- v / usr/local/rocketmq/mqbroker/conf/broker. conf:/ etc/rocketmq/broker. conf
- v / usr/local/rocketmq/mqbroker/logs:/ opt/logs
- v / usr/local/rocketmq/mqbroker/store:/ opt/store
foxiswho/rocketmq:broker-4. 5. 0
4. 启动容器
docker start mqserver mqbroker
5. 停止容器
docker stop mqbroker mqserver
6. 删除容器
docker rm rmqbroker rmqserver
Docker 部署管理工具
RocketMQ 提供了 UI 管理工具,名为 [rocketmq-console](https://github.com/apache/rocketmq-exter nals/tree/master/rocketmq-console),该工具支持docker以及非docker安装,这里选择使用docker安装
1. 拉取镜像
docker pull styletang/rocketmq-console-ng:1. 0. 0
2. 创建并启动容器
docker create -- name mqconsole - p 7000:8080
- e "JAVA_OPTS=-Drocketmq.namesrv.addr=150.158.186.40:9871;150.158.186.40:9871 -Dcom.rocketmq.sendMessageWithVIPChannel=false"
styletang/rocketmq-console-ng:1. 0. 0
docker pull apacherocketmq/rocketmq-console:2. 0. 0
docker create -- name mqconsole - p 7000:8080
- e "JAVA_OPTS=-Drocketmq.namesrv.addr=150.158.186.40:9876;150.158.186.40:9877 -Dcom.rocketmq.sendMessageWithVIPChannel=false"
apacherocketmq/rocketmq-console:2. 0. 0
集群部署介绍
单 Master 模式
风险大,一旦 Broker 重启或者宕机,直接导致整个服务不可用,不建议线上环境使用。
多 Master 模式
一个集群中没有 Slave,全是 Master 单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会收到影响
多 Master 多 Slave 模式,异步复制
每个 Master 配置一个 Slave,HA 采用异步复制方式,主备有短暂消息延迟,毫秒级 优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,因为 Master 宕机后,消费者仍然可以从 Slave 消费,此过程对应用透明,不需要人工干预。性能同多 Master 模式几乎一样 缺点:Master 宕机,磁盘损坏情况下会丢失少量消息
多 Master 多 Slave 模式,同步双写
每个 Master 配置一个 Slave,HA 采用同步双写,主备都写成功,向应用返回成功 优点:数据与服务都无单点,Master 宕机情况下,消息无延迟,服务可用性与数据可用性都非常高 缺点:性能比异步复制模式略低,大约低 10% 左右
Docker 部署 2m2s 集群
创建2个 NameServer Master
1. 创建 NameServer01
docker create - p 9871:9876 -- name mqserver01
- e "JAVA_OPT_EXT=-server -Xms128m -Xmx128m -Xmn128m"
- e "JAVA_OPTS=-Duser.home=/opt"
- v / usr/local/rocketmq/cluster/mqserver01/logs:/ opt/logs
- v / usr/local/rocketmq/cluster/mqserver01/store:/ opt/store
foxiswho/rocketmq:server-4. 7. 0
2. 创建 NameServer02
docker create - p 9872:9876 -- name mqserver02
- e "JAVA_OPT_EXT=-server -Xms128m -Xmx128m -Xmn128m" - e "JAVA_OPTS=-Duser.home=/opt"
- v / usr/local/rocketmq/cluster/mqserver02/logs:/ opt/logs
- v / usr/local/rocketmq/cluster/mqserver02/store:/ opt/store foxiswho/rocketmq:server-4. 7. 0
创建第一组 Broker(Master - Slave)
1 . 创建 broker01-master
docker create --net host --name mqbroker01
-e "JAVA_OPTS=-Duser.home=/opt" -e "JAVA_OPT_EXT=-server -Xms128m -Xmx128m -Xmn128m"
-v /usr/local/rocketmq/cluster/mqbroker01/conf/broker.conf:/etc/rocketmq/broker.conf
-v /usr/local/rocketmq/cluster/mqbroker01/logs:/opt/logs
-v /usr/local/rocketmq/cluster/mqbroker01/store:/opt/store
foxiswho/rocketmq:broker-4.7.0
2 . 配置 broker01-master
namesrvAddr = 150.158 .186.40:9871; 150.158 .186.40:9872
brokerClusterName = zy-cluster
brokerName = broker01
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = SYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = 150.158 .186.40
listenPort = 10811
autoCreateTopicEnable = true
messageDelayLevel = 1s 1m 5m 15m 30m 1h 2h 3h 4h 5h 6h 7h 8h 12h 18h 1d 36h 3d
3 . 创建 broker01-slave
docker create --net host --name mqbroker02
-e "JAVA_OPTS=-Duser.home=/opt" -e "JAVA_OPT_EXT=-server -Xms128m -Xmx128m -Xmn128m"
-v /usr/local/rocketmq/cluster/mqbroker02/conf/broker.conf:/etc/rocketmq/broker.conf
-v /usr/local/rocketmq/cluster/mqbroker02/logs:/opt/logs
-v /usr/local/rocketmq/cluster/mqbroker02/store:/opt/store
foxiswho/rocketmq:broker-4.5.0
4 . 配置 broker01-slave
namesrvAddr = 150.158 .186.40:9871; 150.158 .186.40:9872
brokerClusterName = zy-cluster
brokerName = broker01
brokerId = 1
deleteWhen = 04
fileReservedTime = 48
brokerRole = SLAVE
flushDiskType = ASYNC_FLUSH
brokerIP1 = 150.158 .186.40
listenPort = 10812
autoCreateTopicEnable = true
messageDelayLevel = 1s 1m 5m 15m 30m 1h 2h 3h 4h 5h 6h 7h 8h 12h 18h 1d 36h 3d
创建第二组 Broker(Master - Slave)
1 . 创建 broker02-master
docker create --net host --name mqbroker03
-e "JAVA_OPTS=-Duser.home=/opt" -e "JAVA_OPT_EXT=-server -Xms128m -Xmx128m -Xmn128m"
-v /usr/local/rocketmq/cluster/mqbroker03/conf/broker.conf:/etc/rocketmq/broker.conf
-v /usr/local/rocketmq/cluster/mqbroker03/logs:/opt/logs
-v /usr/local/rocketmq/cluster/mqbroker03/store:/opt/store foxiswho/rocketmq:broker-4.5.0
2 . 配置 broker02-master
namesrvAddr = 127.0 .0.1:9871; 127.0 .0.1:9872
brokerClusterName = zy-cluster
brokerName = broker02
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = SYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = 127.0 .0.1
brokerIp2 = 127.0 .0.1
listenPort = 10813
3 . 创建 broker02-slave
docker create --net host --name mqbroker04
-e "JAVA_OPTS=-Duser.home=/opt" -e "JAVA_OPT_EXT=-server -Xms128m -Xmx128m -Xmn128m"
-v /usr/local/rocketmq/cluster/mqbroker04/conf/broker.conf:/etc/rocketmq/broker.conf
-v /usr/local/rocketmq/cluster/mqbroker04/logs:/opt/logs
-v /usr/local/rocketmq/cluster/mqbroker04/store:/opt/store foxiswho/rocketmq:broker-4.5.0
4 . 配置 broker02-slave
namesrvAddr = 127.0 .0.1:9871; 127.0 .0.1:9872
brokerClusterName = zy-cluster
brokerName = broker02
brokerId = 1
deleteWhen = 04
fileReservedTime = 48
brokerRole = SLAVE
flushDiskType = ASYNC_FLUSH
brokerIP1 = 127.0 .0.1
brokerIp2 = 127.0 .0.1
listenPort = 10814
启动
docker start mqserver01 mqserver02
docker start mqbroker01 mqbroker02 mqbroker03 mqbroker04
DLedger 模式
基于 raft 协议的 commitlog 存储库,是 RocketMQ 实现新的高可用多副本架构的关键
聊一聊 Master-Slave 模式的缺陷
针对于 RocketMQ 4.5 版本之前,RocketMQ 只有 Master-Slave 模式,一组 Broker 中有一个 Master,零到多个 Slave,Slave 通过同步复制或异步复制的方式去同步 Master 数据。这种模式提供了一定的高可用性。 Master-Slave 模式的缺陷:如果主节点挂了,需要人为手动进行重启或者切换,无法自动将一个从节点转换为主节点
缺陷解决的两种方式:也就是说要解决自动故障转移的问题,本质上也就是如何完成自动选主的问题
引入三方协调服务:如 zookeeper 或 etcd 等,重量级部署,增加了额外的维护成本 基于 Raft 协议完成自动选主:不需要引入外部组件,自动选主逻辑集成到各个节点的进程中,节点直接通过通信就可以完成选主
什么是 Raft 协议
Raft 协议可以使得一个集群的服务器组成复制状态机
什么是 复制状态机
一组服务器,每个服务器都是一个状态机,服务器的运行状态只能通过一行行的命令来改变,每一个状态机存储一个包含一系列指令的日志,要求每个状态机必须严格按照顺序执行,如果所有状态机都能按照相同的日志执行指令,那么它们最终将达到相同的状态。 因此,在复制状态机模型下,只要保证操作日志的一致性,就可以保证分布式系统状态的一致性 。
如何保证操作日志的一致性(Raft 一致性算法)
为了让一致性协议变得简单,Raft 协议主要采用了两种策略
复杂问题分解:raft 协议中,一致性问题被分解为:leader election、log replication、safety 三个简单问题 减少状态空间中的状态数目
Raft 一致性算法
在 Raft 体系中,有一个强 leader,负责全权接收客户端的请求命令,并将该请求命令作为日志条目复制给其他服务器,在确认安全的时候,将日志命令提交执行。当 leader 故障时,会通过选举产生一个新的 leader 复杂问题分解
leader election:leader 选举,当已有 leader 故障时必须选出一个新的 leader log replication:leader 接收来自客户端的命令,记录为日志,并复制给集群中的其他服务器,并强制其他节点的日志与 leader 保持一致 safety:通过一系列措施保证系统的安全性,如确保所有状态机按照相同顺序执行相同命令的措施 关于 leader 选举 :集群刚启动的时候,所有节点都是 follower ,之后在 time out 信号的驱使下,follower 会转换成 candidate 去拉取选票,获得大多数选票后就会成为 leader ,其他候选人发现了新的 leader 已经诞生,就会自动转变为 follower。如果另一个 time out 信号发出是,还没有选举出 leader,将会重新开始新一次的选举。time out 信号是促使角色转换的关键因素 ,类似于操作系统中的中断信号。关于日志处理
日志复制:一旦 leader 选举成功,就可以对客户端提供服务,客户端提交每一条命令都会被顺序记录到 leader 的日志中,每一条指令都包含一个编号和顺序索引,然后向其他节点并行发送指令用以发送指令(如果命令丢失会不断重发),当各个节点均复制成功后,leader 就会提交命令,执行该命令并将执行结果响应给客户端,raft 保证已经提交的命令最终也会被其他节点执行,leader 会保存当前已经提交的最高日志编号,顺序性确保了相同日志索引处的命令是相同的。leader 向各节点发送指令的时候,会包含 leader 上一条刚处理过的指令,接收节点如果发现上一条命令不匹配,就会拒绝执行。 日志复制中的异常情况:
leader 宕机,日志部分指令未被 follower 复制完毕,这时候会进行新的选举来产生 leader,在 raft 中 leader 会强制要求 follower 复制自己的日志来解决日志不一致的问题 ,也就必定会造成部分指令失败的情况。 日志压缩
系统的全部状态会写入一个 snapshot 保存起来,然后丢弃截止到 snapshot 时间点之前的所有日志,虽然每一个 server 都保存有自己的snapshot,但是当 follower 严重落后于 leader 时,leader 需要把自己的 snapshot 发送给 follower 加快同步,此时用到了一个新的 RPC:InstallSnapshot RPC。follower 收到 snapshot 时,需要决定如何处理自己的日志,如果收到的 snapshot 包含有更新的信息,它将丢弃自己已有的日志,按 snapshot 更新自己的状态,如果 snapshot 包含的信息更少,那么它会丢弃 snapshot 中的内容,但是自己之后的内容会保存下来。