IILeeのBlog

IILeeのBlog

Docker 搭建 RocketMQ Dledger 集群

531
2021-05-05
Docker 搭建 RocketMQ Dledger 集群

系统环境:

  • 系统版本:CentOS 7.7
  • RocketMQ 版本:20.10.6
  • Docker 版本:19.03.13

RocketMQ 简介

RocketMQ 是一个由 Java 语言编写的分布式高性能消息中间件,由阿里创建,后将其开源给 Apache 基金会,现在已经成为 Apache 开源项目中的顶级开源项目,具有高性能、高可靠、高实时、分布式特点,尤其内部封装了很多跟业务相关的功能模块,能让我们快速用其解决业务上消息处理的一些难点,所以如何使用与部署 RocketMQ 是这里要讲的重点。这里记录一下如果通过 Docker 部署 RocketMQ Dledger 集群模式这个过程。

RocketMQ 需部署的组件

这里介绍下 RocketMQ 相关的组件:

  • 名字服务(Name Server): 是一个几乎无状态节点,可集群部署,集群节点间相互独立没有信息交换。其功能主要为更新和发现 Broker 服务,生产者或消费者能够通过其查找到各主题相应的 Broker IP 列表。
  • 代理服务(Broker Server): 消息中转角色,负责存储消息,转发消息。分为 Master Broker 和 Slave Broker,一个 Master Broker 可以对应多个 Slave Broker,但是一个 Slave Broker 只能对应一个 Master Broker。Broker 启动后需要完成一次将自己注册至 Name Server 的操作,随后每隔 30s 定期 向Name Server 上报 Topic 路由信息。
  • 生产者: 与 Name Server 集群中的其中一个节点(随机)建立长链接(Keep-alive),定期从 Name Server 读取 Topic 路由信息,并向提供 Topic 服务的 Master Broker 建立长链接,且定时向 Master Broker 发送心跳。
  • 消费者: 与 Name Server 集群中的其中一个节点(随机)建立长连接,定期从 Name Server 拉取 Topic 路由信息,并向提供 Topic 服务的 Master Broker、Slave Broker建立长连接,且定时向 Master Broker、Slave Broker 发送心跳。Consumer 既可以从 Master Broker 订阅消息,也可以从 Slave Broker 订阅消息,订阅规则由 Broker 配置决定。
  • 控制台(console): RocketMQ 的 Web 可视化控制台,包含 RocketMQ 常用操作,可以用于简单管理 RocketMQ 平台。

RocketMQ 常用部署模式

RocketMQ 常用部署方案有以下几种:

  • 单机模式
  • 多主模式
  • 双主双从/多主多从模式(异步复制)
  • 双主双从/多主多从模式(同步双写)
  • Dledger 集群模式

单机模式

这种模式就如该名单机模式一样,就是部署单个 RocketMQ Broker 来使用,一般使用这种方式在生产中风险较大,一旦 Broker 重启或者宕机时,会导致整个服务不可用,所以常常在学习、开发过程中才会使用这种模式。

优缺点分析:

  • 优点: 本地开发测试,配置简单,同步刷盘消息不会丢失。
  • 缺点: 不可靠,如果宕机会导致服务不可用。

多主模式

全部由 Broker Master 节点组成(即可能部署两个或者更多 Broker),生产者发送的数据会分别存入不同的 Broker 中,这样能够避免某个 Broker 一直接收处理数据从而负载过高。

优缺点分析:

  • 优点: 性能高,配置简单,单个 Master 宕机或重启维护对应用无影响,在磁盘配置为 RAID10 时,即使机器宕机不可恢复,由于 RAID10 磁盘非常可靠,消息也不会丢(异步刷盘可能会丢失少量消息,同步刷盘能保证消息不丢)。
  • 缺点: 单台服务器宕机期间,不可订阅该服务器上未被消费者消费的消息,只有机器恢复后才可恢复订阅,所以可能会影响消息的实时性。

双主双从/多主多从模式(异步复制)

一般会部署多个 Broker Master,同时也会为各个 Broker Master 部署一个 Broker Slave,且 Master 和 Slave 之间采用"异步复制数据"方式进行数据同步(主从同步消息会有延迟,毫秒级),这样在生产者将消息发送到 Broker Master 后不必等待数据同步到 Slave 节点,就返回成功。

优缺点分析:

  • 优点: 性能高,且磁盘损坏也不会丢失大量消息,消息实时性不会受影响,Master 宕机后,消费者仍然可以从 Slave 消费。
  • 缺点: 主备有短暂消息延迟,毫秒级,如果Master宕机,磁盘损坏情况,会丢失少量消息。

双主双从/多主多从模式(同步双写)

一般会部署多个 Broker Master,同时也会为各个 Broker Master 部署一个 Broker Slave,且 Master 和 Slave 之间采用"同步复制数据"方式进行数据同步,这样在生产者将消息发送到 Broker Master 后需要等待数据同步到 Slave 节点成功后,才返回成功。

优缺点分析:

  • 优点: 数据与服务都无单点故障,Master 宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
  • 缺点: 性能比异步复制模式略低(大约低10%左右),发送单个消息的 RT 会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。

Dledger 集群模式

RocketMQ-on-DLedger Group 是指一组相同名称的 Broker,至少需要 3 个节点,通过 Raft 自动选举出一个 Leader,其余节点 作为 Follower,并在 Leader 和 Follower 之间复制数据以保证高可用。当 Master 节点出现问题宕机后也能自动容灾切换,并保证数据一致性。该模式也支持 Broker 水平扩展,即可以部署任意多个 RocketMQ-on-DLedger Group 同时对外提供服务。

优缺点分析:

  • 优点:多节点(至少三个)组成集群,其中一个为 Leader 节点,其余为 Follower 节点组成高可用,能够自动容灾切换。
  • 缺点:需要 RocketMQ 4.5 及以后版本才支持。

RocketMQ Dledger 集群模式简介

###传统部署方式的不足

在 RocketMQ 4.5 之前的版本中,部署 RocketMQ 高可用方案一般都会采用多主多从方式,这种方式需要多个 Master 节点与实时备份 Master 节点数据的 Slave 节点,Slave 节点通过同步复制或异步复制的方式去同步 Master 节点数据。但这样的部署模式存在一定缺陷。比如故障转移方面,如果 Master 点挂了,还需要人为手动对 Master 节点进行重启或者切换,它无法自动的将 Slave 节点转换为 Master 节点。因此,我们希望能有一个新的多副本架构,去解决这个问题。

新技术解决的问题

新的多副本架构首先需要解决自动故障转移的问题,本质上来说问题关键点在于 Broker 如何自动推选主节点。这个问题的解决方案基本可以分为两种:

  • 利用第三方协调服务集群完成选主,比如 Zookeeper 或者 Etcd,但是这种方案会引入了重量级外部组件,使部署变得复杂,同时也会增加运维对组件的故障诊断成本,比如在维护 RocketMQ 集群还需要维护 Zookeeper 集群,保证 Zookeeper 集群如何高可用,不仅仅如此,如果 zookeeper 集群出现故障也会影响到 RocketMQ 集群。
  • 利用 raft 协议来完成一个自动选主,raft 协议相比前者的优点是不需要引入外部组件,自动选主逻辑集成到各个节点的进程中,节点之间通过通信就可以完成选主。

RocketMQ 最终选择使用 raft 协议来解决这个问题,而 DLedger 就是一个基于 raft 协议的 commitlog 存储库,也是 RocketMQ 实现新的高可用多副本架构的关键。

Dledger 简介

分布式算法中比较常常听到的是 Paxos 算法,但是由于 Paxos 算法难于理解,且实现比较苦难,所以不太受业界欢迎。然后出现新的分布式算法 Raft,其比 Paxos 更容易懂与实现,到如今在实际中运用的也已经很成熟,不同的语言都有对其的实现。Dledger 就是其中一个 Java 语言的实现,其将算法方面的内容全部抽象掉,这样开发人员只需要关系业务即可,大大降低使用难度。

Dledger 相关资料来源于网上搜索。

DLedger 定位

image.png

Raft 协议是复制状态机的实现,这种模型应用到消息系统中就会存在问题。对于消息系统来说,它本身是一个中间代理,commitlog 状态是系统最终状态,并不需要状态机再去完成一次状态构建。因此 DLedger 去掉了 raft 协议中状态机的部分,但基于raft协议保证commitlog 是一致的,并且是高可用的。

image.png

另一方面 DLedger 又是一个轻量级的 java library。它对外提供的 API 非常简单,append 和 get。Append 向 DLedger 添加数据,并且添加的数据会对应一个递增的索引,而 get 可以根据索引去获得相应的数据。因此 DLedger 是一个 append only 的日志系统。

DLedger 应用场景

image.png

DLedger 其中一个应用就是在分布式消息系统中,RocketMQ 4.5 版本发布后,可以采用 RocketMQ on DLedger 方式进行部署。DLedger commitlog 代替了原来的 commitlog,使得 commitlog 拥有了选举复制能力,然后通过角色透传的方式,raft 角色透传给外部 broker 角色,leader 对应原来的 master,follower 和 candidate 对应原来的 slave。

因此 RocketMQ 的 broker 拥有了自动故障转移的能力,在一组 broker 中如果 Master 挂了,能够依靠 DLedger 自动选主能力重新选出一个 leader,然后通过角色透传变成新的 Master。

image.png

DLedger 还可以构建高可用的嵌入式 KV 存储。我们把对一些数据的操作记录到 DLedger 中,然后根据数据量或者实际需求,恢复到hashmap 或者 rocksdb 中,从而构建一致的、高可用的 KV 存储系统,应用到元信息管理等场景。

RocketMQ Dledger 的方案简介

image.png

RocketMQ-on-DLedger Group 是指一组相同名称的 Broker,组中至少需要 3 个 Broker 节点来保证集群能够运行,在 Broker 启动时候,通过 raft 算法能够自动选举出一个 Broker 为 Leader 节点,其余为 Follower 节点。这种模式下 Leader 和 Follower 之间复制数据以保证高可用,如果 Leader 节点出现问题是可以自动进行容灾切换并保证数据一致性。且不仅仅如此,该模式也支持 Broker 节点水平扩展来增加吞吐量。所以该模式将会是部署 RocketMQ 常用模式之一。

Dledger 集群单个节点示例配置

brokerIP1=192.168.0.231
listenPort=30911
brokerClusterName=RaftCluster
brokerName=RaftNode00
namesrvAddr=192.168.0.231:9876;192.168.0.232:9876;192.168.0.233:9876
## DLeger
dLegerSelfId=n0
dLegerGroup=RaftNode00
enableDLegerCommitLog=true
## must be unique
dLegerPeers=n0-192.168.0.231:40911;n1-192.168.0.232:40911;n2-192.168.0.233:40911
sendMessageThreadPoolNums=2

基本配置参数说明:

参数名称参数描述参数示例
brokerClusterNameBroker 集群名称RaftCluster
brokerNameBroker 名称RaftNode00
listenPortBroker 监听端口30911
namesrvAddrBroker Namesrv 地址192.168.0.231:9876
storePathRootDirBroker 存储目录/tmp/rmqstore/node00
storePathCommitLogCommitlog 存储目录/tmp/rmqstore/node00/commitlog

Dledger 配置参数说明:

参数名称参数描述参数示例
enableDLegerCommitLog是否启动 DLedgertrue
dLegerGroupDLedger Raft Group 的名字,建议和 brokerName 保持一致RaftNode00
dLegerPeersDLedger Group 内各节点的地址与端口信息(同一个 Group 内的各个节点配置必须要保证一致)n0-192.168.1.1:40911;n1-192.168.1.2:40911;n2-192.168.1.3:40911
dLegerSelfId节点 id, 必须属于 dLegerPeers 中的一个;同 Group 内各个节点要唯一例如:第一个节点配置为"n0"第一个节点配置为"n1"第一个节点配置为"n2"
sendMessageThreadPoolNums发送线程个数(建议配置成 CPU 核数)8

部署 RcoketMQ Dledger 集群模式

部署服务器安排

服务器部署的应用物理资源存储挂载目录
192.168.0.231Namesrv Server、Broker4C && 8G/mnt/rocketmq
192.168.0.232Namesrv Server、Broker4C && 8G/mnt/rocketmq
192.168.0.233Namesrv Server、Broker、Console4C && 8G/mnt/rocketmq

创建存储数据的目录

mkdir -p /mnt/rocketmq/broker/conf && \
mkdir -p /mnt/rocketmq/broker/logs && \
mkdir -p /mnt/rocketmq/broker/store && \
mkdir -p /mnt/rocketmq/server/logs

创建 Broker 配置文件

在三台服务器上,分别创建如下对应的文件:

vim /mnt/rocketmq/broker/conf/broker.conf
  • 服务器一 192.168.0.231: 创建 RaftNode00 配置文件。
  • 服务器二 192.168.0.232: 创建 RaftNode01 配置文件。
  • 服务器三 192.168.0.233: 创建 RaftNode02 配置文件。

服务器一创建 RaftNode00 配置:

brokerIP1=192.168.0.231
listenPort=30911
brokerClusterName=RaftCluster
brokerName=RaftNode00
namesrvAddr=192.168.0.231:9876;192.168.0.232:9876;192.168.0.233:9876
## DLeger
dLegerSelfId=n0
dLegerGroup=RaftNode00
enableDLegerCommitLog=true
## must be unique
dLegerPeers=n0-192.168.0.231:40911;n1-192.168.0.232:40911;n2-192.168.0.233:40911
sendMessageThreadPoolNums=2

服务器二创建 RaftNode01 配置:

brokerIP1=192.168.0.232
listenPort=30911
brokerClusterName=RaftCluster
brokerName=RaftNode01
namesrvAddr=192.168.0.231:9876;192.168.0.232:9876;192.168.0.233:9876
## DLeger
dLegerSelfId=n1
dLegerGroup=RaftNode00
enableDLegerCommitLog=true
## must be unique
dLegerPeers=n0-192.168.0.231:40911;n1-192.168.0.232:40911;n2-192.168.0.233:40911
sendMessageThreadPoolNums=2

服务器三创建 RaftNode02 配置:

brokerIP1=192.168.0.233
listenPort=30911
brokerClusterName=RaftCluster
brokerName=RaftNode02
namesrvAddr=192.168.0.231:9876;192.168.0.232:9876;192.168.0.233:9876
## DLeger
dLegerSelfId=n2
dLegerGroup=RaftNode00
enableDLegerCommitLog=true
## must be unique
dLegerPeers=n0-192.168.0.231:40911;n1-192.168.0.232:40911;n2-192.168.0.233:40911
sendMessageThreadPoolNums=2

下载相关镜像

在三台服务器上,分别创建拉取对应的镜像:

  • 服务器一 192.168.0.231: 拉取 rocketmq 镜像。
  • 服务器二 192.168.0.232: 拉取 rocketmq 镜像。
  • 服务器三 192.168.0.233: 拉取 rocketmq 与 rocketmq-console 镜像。

三台服务器分别拉取 RocketMQ 镜像:

docker pull foxiswho/rocketmq:4.7.0

第三台服务器拉取 RocketMQ Console 镜像:

docker pull apacherocketmq/rocketmq-console:2.0.0

查看镜像设置的用户与组的配置

任意一台服务器上查看镜像 rocketmq 的组成,分析其中用户与组的信息:

$ docker history foxiswho/rocketmq:4.7.0

IMAGE               CREATED             CREATED BY                                      SIZE 
1cf46e8f03d0        7 months ago        /bin/sh -c #(nop) WORKDIR /home/rocketmq/roc…   0B                  
<missing>           7 months ago        /bin/sh -c #(nop)  USER rocketmq                0B                  
<missing>           7 months ago        |5 gid=3000 group=rocketmq uid=3000 user=roc…   1.92kB              
<missing>           7 months ago        |5 gid=3000 group=rocketmq uid=3000 user=roc…   0B                  
<missing>           7 months ago        |5 gid=3000 group=rocketmq uid=3000 user=roc…   11.3kB              
<missing>           7 months ago        /bin/sh -c #(nop)  EXPOSE 10909 10911 10912     0B                  
<missing>           7 months ago        |5 gid=3000 group=rocketmq uid=3000 user=roc…   10.1kB              
<missing>           7 months ago        /bin/sh -c #(nop)  EXPOSE 9876                  0B                  
<missing>           7 months ago        |5 gid=3000 group=rocketmq uid=3000 user=roc…   15.1MB              
<missing>           7 months ago        /bin/sh -c #(nop) COPY dir:bdc4a8518539da6ce…   11.4kB              
<missing>           7 months ago        |5 gid=3000 group=rocketmq uid=3000 user=roc…   15.1MB              
<missing>           7 months ago        /bin/sh -c #(nop) WORKDIR /home/rocketmq/roc…   0B                  
<missing>           7 months ago        /bin/sh -c #(nop)  ENV ROCKETMQ_HOME=/home/r…   0B                  
<missing>           7 months ago        /bin/sh -c #(nop)  ENV ROCKETMQ_VERSION=4.7.0   0B                  
<missing>           7 months ago        /bin/sh -c #(nop)  ARG version=4.7.0            0B                  
<missing>           7 months ago        |4 gid=3000 group=rocketmq uid=3000 user=roc…   1.07MB              
<missing>           7 months ago        /bin/sh -c #(nop)  ARG gid=3000                 0B                  
<missing>           7 months ago        /bin/sh -c #(nop)  ARG uid=3000                 0B                  
<missing>           7 months ago        /bin/sh -c #(nop)  ARG group=rocketmq           0B                  
<missing>           7 months ago        /bin/sh -c #(nop)  ARG user=rocketmq            0B                  
<missing>           7 months ago        /bin/sh -c yum install -y java-1.8.0-openjdk…   264MB               
<missing>           11 months ago       /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           11 months ago       /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B                  
<missing>           11 months ago       /bin/sh -c #(nop) ADD file:45a381049c52b5664…   203MB

可以观察到:

  • 组名:rocketmq,组ID:3000
  • 用户名:rocketmq,用户ID:3000

更改目录归属组与用户与容器的配置一致

三台服务器上分别创建组与用户:

## 创建组
$ groupadd rocketmq

## 增加用户并加入组
$ useradd -g rocketmq rocketmq

## 设置用户密码
$ passwd rocketmq

## 更改组的 gid
$ groupmod -g 3000 rocketmq

## 更改用户的 uid
$ usermod -u 3000 rocketmq

## 查看是否更改成功
$ id rocketmq

三台服务器上分别更改上面创建的目录的权限为上面创建的组与用户:

chown -R rocketmq:rocketmq /mnt/rocketmq

安装 RocketMQ NameServer

三台服务器上分别部署 NameServer,操作如下:

/mnt/rocketmq/server 目录下新建 docker-compose.yml 文件

version: "3"
services:
  rmqnamesr:
    container_name: rmqnamesr
    image: foxiswho/rocketmq:4.7.0
    restart: always
    ports:
      - 9876:9876
    volumes:
      - ./logs:/home/rocketmq/logs
    environment:
      - "JAVA_OPT_EXT=-Xms512M -Xmx512M -Xmn128m"
    command: mqnamesrv

执行命令 : docker-compose up -d

安装 RocketMQ Broker

三台服务器分别部署 RocketMQ Broker,操作如下:

/mnt/rocketmq/broker 目录下新建 docker-compose.yml 文件

version: "3"
services:
  rmqbroker:
    container_name: rmqbroker
    image: foxiswho/rocketmq:4.7.0
    restart: always
    ports:
      - 30909:30909
      - 30911:30911
      - 30912:30912
      - 40911:40911
    volumes:
      - ./logs:/home/rocketmq/logs
      - ./store:/home/rocketmq/store
      - ./conf:/home/rocketmq/conf
    environment:
      - "JAVA_OPT_EXT=-Xmx2048m -Xms2048m -Xmn1024m"
    command: mqbroker -c /home/rocketmq/conf/broker.conf

执行命令 : docker-compose up -d

部署控制台

服务器三部署 RocketMQ 控制台:

/mnt/rocketmq/console 目录下新建 docker-compose.yml 文件

version: "3"
services:
  rmqconsole:
    container_name: rmqconsole
    image: apacherocketmq/rocketmq-console:2.0.0
    restart: always
    ports:
      - 9875:8080
    environment:
      - "JAVA_OPTS=-Drocketmq.namesrv.addr=192.168.0.231:9876;192.168.0.232:9876;192.168.0.233:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false"

执行命令 : docker-compose up -d

访问控制台

输入 http://192.168.0.233:9875/ 访问在服务器一部署的 RocketMQ 控制台,进入到如下界面:

image.png

然后我们可以通过该控制台进行发送消息来验证 RocketMQ 是否正常,这里就交由大家自行验证了,文档到此结束。

后记

参考地址 : 通过 Docker 部署 RocketMQ Dledger 集群模式( 版本v4.7.0)