5.集群
RabbitMQ这款消息队列中间件产品本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现)。因此,RabbitMQ天然支持CIustering。这使得RabbitMQ本身不需要像ActiveMQ、Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。集群是保证可靠性的一种方式,同时可以通过水平广展以达到增加消息吞吐量能力的目的。
在实际便用过程中多采取多机多实例部署方式,为了便于同学们练习搭建,有时候你不得不在一台电脑上去搭建一个rabbitmq集群,本章主要针对单机多实例多种方式来进行开层。
(1).集群搭建
配置的前提是你的 rabbitmq可以运行起来,比如ps aux|grep rabbitmq
你能看到相关进程,又比如运行你可以看到类似如下信息而不报错:
- 查看状态
查看状态
ps aux|grep rabbitmq
正在运行中...
或者用下面的命令查看
systemctl status rabbitmq-server
2.关闭服务
因为集群不需要我们再用传统的方式去 开启服务
systemctl stop rabbitmq-server
注意:确保RabbitMQ可以运行的,确保完成之后,把单机版的RabbitMQ服务停止,后台看不到RabbitMQ的进程为止
(2).单机多实例搭建
场景: 假设有两个rabbitmq节点,分别为: rabbitmq-1、rabbitmq-2. rabbitmq-2作为从节点。
启动命令:RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit-1 rabbitmq-server -detached
结束命令:rabbitmqctl -n rabbit-1 stop
1、启动第一个节点rabbitmq-1
> sudo RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit-1 rabbitmq-server start & ...............省略................... ########## Logs: /var/log/rabbitmq/rabbit-1.log ###### ## /var/log/rabbitmq/rabbit-1-sasl.log ########## Starting broker... completed with 7 plugins.
至此节点rabbit-1启动完成。
2、启动第二个节点rabbit-2
注意:web管理插件端口占用,所以还要指定其web插件占用的端口号
RABBITMQ_SERVER_START_ARGS=”-rabbitmq_management listener [{port,15673}]”
sudo RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" RABBITMQ_NODENAME=rabbit-2 rabbitmq-server start & ..............省略.................. ########## Logs: /var/log/rabbitmq/rabbit-2.log ###### ## /var/log/rabbitmq/rabbit-2-sasl.log ########## Starting broker... completed with 7 plugins.
至此节点rabbit-2启动完成
3、验证启动 “ps aux|grep rabbitmq”
ps aux|grep rabbitmq
4、rabbit-1操作作为主节点
#停止应用 > sudo rabbitmqctl -n rabbit-1 stop_app #目的是清除节点上的历史数据(如果不清除,无法将节点加入到集群) > sudo rabbitmqctl -n rabbit-1 reset #启动应用 > sudo rabbitmqctl -n rabbit-1 start_app
5、rabbit2操作为从节点
# 停止应用 > sudo rabbitmqctl -n rabbit-2 stop_app # 目的是清除节点上的历史数据(如果不清除,无法将节点加入到集群) > sudo rabbitmqctl -n rabbit-2 reset # 将rabbit2节点加入到rabbit1(主节点)集群当中【Server-node服务器的主机名】 > sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@Jsxs # 启动应用 > sudo rabbitmqctl -n rabbit-2 start_app
6、验证集群状态
> sudo rabbitmqctl cluster_status -n rabbit-1 //集群有两个节点:rabbit-1@Server-node、rabbit-2@Server-node [{nodes,[{disc,['rabbit-1@Server-node','rabbit-2@Server-node']}]}, {running_nodes,['rabbit-2@Server-node','rabbit-1@Server-node']}, {cluster_name,<<"rabbit-1@Server-node.localdomain">>}, {partitions,[]}, {alarms,[{'rabbit-2@Server-node',[]},{'rabbit-1@Server-node',[]}]}]
(3).Web监控
默认是关闭的web界面,我们需要打开.
rabbitmq-plugins enable rabbitmq_management
注意在访问的时候:web结面的管理需要给15672 node-1 和15673的node-2 设置用户名和密码。如下:
主人认证了 从人不用认证了
# 15672 端口 rabbitmqctl -n rabbit-1 add_user admin admin rabbitmqctl -n rabbit-1 set_user_tags admin administrator rabbitmqctl -n rabbit-1 set_permissions -p / admin ".*" ".*" ".*" # 15673 端口 rabbitmqctl -n rabbit-2 add_user admin admin rabbitmqctl -n rabbit-2 set_user_tags admin administrator rabbitmqctl -n rabbit-2 set_permissions -p / admin ".*" ".*" ".*"
我们在主机中添加一个队列,发现从机里面的也跟着被添加了。
# 停止掉 从机服务2 rabbitmqctl -n rabbit-2 stop_app # 停止掉 主机服务1 rabbitmqctl -n rabbit-1 stop_app # 开启掉 从机服务2 rabbitmqctl -n rabbit-2 start_app
我们停掉从机2,就会发现我们的15673web页面访问不到了,而且主机那边会爆红。队列和集群依然存在。如果主机挂了,那么从节点就无法启动和运行了了。除非主节点重新复活....
(4).小结
Tips: 如果采用多机部署方式,需读取其中一个节点的cookie, 并复制到其他节点(节点之间通过cookie确定相互是否可通信)。cookie存放在/var/lib/rabbitmq/.erlang.cookie。 例如:主机名分别为rabbit-1、rabbit-2 1、逐个启动各节点 2、配置各节点的hosts文件( vim /etc/hosts) ip1:rabbit-1 ip2:rabbit-2 其它步骤雷同单机部署方式
(六).分布式事务💘
1.基本概述
分布式事务指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。
例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
分布式事务的方式
在分布式系统中,要实现分布式事务,无外乎那几种解决方案。
(1).两阶段提交(2PC)需要数据库产商的支持,java组件有atomikos等。
两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
1.准备阶段
协调者询问参与者事务是否执行成功
,参与者发回事务执行结果
。
2 提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交
。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
存在的问题
- 2.1 同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
- 2.2 单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
- 2.3 数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
- 2.4 太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
(2).补偿事务(TCC) 严选,阿里,蚂蚁金服。
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作
。它分为三个阶段:
- Try 阶段主要是对业务系统做检测及资源预留.
- Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 - - - Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
- Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
举个例子,假入 Bob 要向 Smith 转账,思路大概是: 我们有一个本地方法,里面依次调用
1:首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
2:在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
3:如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些。
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理(代码冗余!)。
(3).本地消息表(异步确保)比如:支付宝、微信支付主动查询支付状态,对账单的形式
本地消息表与业务数据表处于同一个数据库中
,这样就能利用本地事务来保证在对这两个表的操作满足事务特性
,并且使用了消息队列来保证最终一致性。
- 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,
本地事务能保证这个消息一定会被写入本地消息表中
。 - 之后将本地消息表中的消息转发到 Kafka 等消息队列中,
如果转发成功则将消息从本地消息表中删除,否则继续重新转发
。 - 在分布式事务操作的另一方从
消息队列中读取一个消息,并执行消息中的操作
。
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
(4).MQ 事务消息 异步场景,通用性较强,拓展性较高。💚
有一些第三方的MQ是支持事务消息的
,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 Kafka 不支持。
以阿里的 RabbitMQ 中间件为例,其思路大致为:
第一阶段Prepared消息
,会拿到消息的地址。第二阶段
执行本地事务,第三阶段
通过第一阶段拿到的地址去访问消息,并修改状态。- 也就是说在业务方法内要想消息队列提交两次请求,
一次发送消息和一次确认消息
。如果确认消息发送失败了RabbitMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RabbitMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。
(5).总结
通过本文我们总结并对比了几种分布式分解方案的优缺点,分布式事务本身是一个技术难题,是没有一种完美的方案应对所有场景的,具体还是要根据业务场景去抉择吧。阿里RocketMQ去实现的分布式事务,现在也有除了很多分布式事务的协调器,比如LCN等,大家可以多去尝试。