前言
在RabbitMQ系列文章的最终篇,荔枝将会梳理有关RabbitMQ的幂等性、优先级队列、惰性队列和集群实现的相关知识,同时也梳理了Federation和Shovel插件来辅助消息数据的备份和持久化的知识及其相关的配置。
文章目录
[1.1 解决方案](#1.1 解决方案)
[1.2 消费端的幂等性保障](#1.2 消费端的幂等性保障)
[2.1 应用场景](#2.1 应用场景)
[2.2 优先级队列的实现](#2.2 优先级队列的实现)
[3.1 惰性队列的应用场景:](#3.1 惰性队列的应用场景:)
[3.2 惰性队列的实现](#3.2 惰性队列的实现)
[4.1 搭建RabbitMQ集群环境](#4.1 搭建RabbitMQ集群环境)
[4.2 镜像队列](#4.2 镜像队列)
[4.3 Haproxy+Keepalive实现负载均衡](#4.3 Haproxy+Keepalive实现负载均衡)
[5.1 Federation Exchange联邦交换机](#5.1 Federation Exchange联邦交换机)
[5.2 Federation Queue 联邦队列](#5.2 Federation Queue 联邦队列)
一、幂等性
幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而造成数据的不可靠修改。举个最简单的例子,那就是用户支付,支付扣款成功后但是返回结果的时候可能因为网络异常的原因,实际在数据库中已经执行扣费的操作,但是用户并没有获得反馈,此时用户再次点击按钮就会开始第二次扣款,导致重复付费的问题。在以前的单应用系统中,我们只需要把数据操作放入事务中即可,利用事务的原子性回滚失败或异常的操作,但是这种方式在响应客户端的时候依旧可能因为网络中断或者异常而导致用户认为未付款成功。
换句话来简单理解幂等性问题的求解就是:在支付场景中我们为了避免用户的重复扣费而提出的一种确认机制。
1.1 解决方案
MQ消费者的幂等性的解决一般使用全局ID或者写个唯一标识,比如时间戳、UUID、订单消费者消费MQ中的消息也可利用MQ的id来判断,或者自行生成全局唯一id,每次消费消息时用该id先判断该消息是否已消费过。
1.2 消费端的幂等性保障
在海量订单生成的业务高峰期,生产端有可能就会重复发生了消息,这时候消费端就要实现幂等性,这就意味着我们的消息永远不会被消费多次,即使我们收到了一样的消息。业界主流的幂等性有两种操作:
- 唯一ID+指纹码机制,利用数据库主键去重;
- 利用redis的原子性去实现
唯一ID+指纹码机制
指纹码:我们的一些规则或者时间戳加别的服务给到的唯一信息码,它并不一定是我们系统生成的,基本都是由我们的业务规则拼接而来,但是一定要保证唯一性,然后就利用查询语句进行判断这个id是否存在数据库中。
- **优势:**就是实现简单就一个拼接,然后查询判断是否重复;
- **劣势:**在高并发时,如果是单个数据库就会有写入性能瓶颈,当然也可以采用分库分表提升性能来解决。
Redis原子性(最佳)
利用redis执行setnx命令,天然具有幂等性,从而实现不重复消费。
二、优先级队列
2.1 应用场景
对于商城平台来说,我们在未用户推送未支付消息提醒的时候是会根据加盟商家的体量大小和一年为平台吸引的流量或者是创造的利润来分配消息提醒的优先级,比如像华为、小米这种体量的大商家。理应当然,他们的订单必须得到优先处理。当所有的消息堆积在队列中,我们需要根据消息来源来获得相应的优先级大小以便优先处理一些重要客户的需求。这一场景仅仅通过redis中的List那种简单的消息队列明显不够,因此我们需要使用RabbitMQ中的消息队列来优化。
2.2 优先级队列的实现
第一种方式
直接在RabbitMQ提供操作平台中配置Maximum priority参数来设置队列中的优先级的范围。队列的优先级设置有0-255的参数范围数值填入,一般设置10。
第二种方式
队列声明的时候在生产者处传入参数的时候设置 x-max-priority 这个参数的大小,该参数设置的就是队列中的优先级的最大值。
java
Map<String,Object> arguments= new HashMap();
arguments.put ("x-max-priority",10);
channel.queueDeclare("Queue1",true,false,false,params);
发送消息的时候设置消息对应的优先级 AMQP.BasicProperties().builder()中的priority() 方法来设置优先级的大小。
java
for(int i=1;i<11;i++){
String message = "info"+i;
if(i==5){
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(5).build();
channel.basicPublish("",QUEUE_NAME,properties,message.getBytes());
}else{
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
}
}
三、惰性队列
RabbitMQ从3.6.0版本开始引入了惰性队列的慨念。惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储 。默认情况下,当生产者将消息发送到RabbitMQ的时候,队列中的消息会尽可能的存储在内存之中,这样可以更加快速的将消息发送给消费者。即使是持久化的消息,在被写入磁盘的同时也会在内存中驻留一份备份。而在惰性队列中,消息是持久化在磁盘上的。因此惰性队列相比于一般的队列来说消息的消费速度比较慢,一般情况下不会使用惰性队列。
3.1 惰性队列的应用场景:
当消费者由于各种各样的原因(比如消费者下线 、宕机、由于维护而关闭等)而致使长时间内不能消费消息造成堆积时,惰性队列就很有必要了。
当RabbitMQ需要释放内存的时候,会将内存中的消息换页至磁盘中,这个操作会耗费较长的时间,也会阻塞队列的操作,进而无法接收新的消息。虽然RabbitMQ的开发者们一直在升级相关的算法,但是效果始终不太理想,尤其是在消息量特别大的时候。
3.2 惰性队列的实现
RabbitMQ的队列有两种模式default和lazy,其中可以通过在queueDeclare中声明 x-queue-mode 参数来设置惰性模式。
java
Map<String,Object> args = new HashMap<String,Object>();
args.put("x-queue-mode","lazy");
channel.queueDeclare("myqueue",false,false,false,args);
四、RabbitMQ集群
4.1 搭建RabbitMQ集群环境
首先修改各个机器的主机名称
java
vim /etc/hostname
配置各个节点的hosts文件
java
vim /etc/hosts
java
RabbitMQ服务器ip 节点名称
确保各个节点的cookie文件使用的是同一个值,需要在主节点中配置远程操作命令
java
scp /var/lib/rabbitmq/.erlang.cookie root@节点名称:/var/lib/rabbitmq/.erlang.cookie
重启RabbitMQ服务
java
rabbitmq-server -deached
从节点配置rabbitmq服务
java
//关闭rabbitmq服务
rabbitmgctl stop_app
rabbitmqctl reset
rabbitmgctl join_cluster rabbit@主节点名称
启动rabbiymq应用服务
rabbitmqctl start_app
查看集群的状态
java
rabbitmqctl cluster_status
重新设置用户
任意选择集群里面的一台机器来设置用户和用户配置即可。
java
//创建账号
rabbitmqctl add_user root 123456
//设置用户超级管理员角色
rabbitmqctl set_user_tags root administrator
//设置用户最高级权限
rabbitmqctl set_permissions -p "/" admin ".*"".*"".*"
4.2 镜像队列
我们已经搭建好集群环境,但是任意一个RabbitMQ的主机中创建的队列是不共享的。也就是说,当生产者往A主机中的一个a队列发消息,此时不启动消费者并挂掉A主机的服务。此时在B主机中查看队列的状态无疑是宕机的,这就会导致消息丢失。为了避免因为集群中某一台主机宕机导致其上的队列不可用,我们采用镜像队列的机制来避免消息丢失。
配置镜像队列来备份数据也比较简单,首先我们需要在管理平台上去配置镜像队列。
在平台上配置好镜像队列的相关参数后,之后在集群中创建符合备份规则的队列就会自动备份到集群中的RabbitMQ服务器中,这里的备份指向是随机的。
4.3 Haproxy+Keepalive实现负载均衡
在前面文章中,生产者必须在代码中指定要连接的RabbitMQ服务器的ip,但是如果连接的对象宕机,即使我们加入了集群的机制也无法保证消息不丢失,因此我们需要一种机制来保证生产者能够同时连接集群中的所有机器。HaProxy提供高可用性、负载均衡及基于TCP、HTTP应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。HaProxy实现了一种事件驱动、单一进程模型,支持非常大的并发连接数。
基于Haproxy和Keepalive,生产者就能够直接跟这两款软件连接,同时由Keepalive来间断询问主机下的Haproxy,如果宕机自动开启备机接管;而Haproxy则是进行负载均衡的重要环节,极好地解决了生产者只能连接一台机器这一问题并自动代替生产者决策消息转发。
五、Federation
5.1 Federation Exchange联邦交换机
随着用户群体的增加或者是软件使用范围的扩大,一些公司在全球都会由服务器机房,当用户跨地域范围交换机中数据时由于距离的问题导致了很长的延时,同时也需要经过很长时间的等待,造成了该线程一定程度上的性能下降。为了避免这种性能消耗,我们总是将服务器中的数据进行同步和容灾处理,这里Federation插件就可以比较好的解决这个需求。
开启步骤:
在集群中的每一台服务器中都开启联邦插件。
java
rabbitmq-plugins enable rabbitmq_federation
rabbitmq-plugins enable rabbitmq_federation_management
在这里有一个上下游的概念,消息是从上游同步到下游的,因此我们在开启上下游关系前必须创建下游的交换机,之后我们需要配置上游节点。
之后需要添加一个交换机策略,配置好之后就可以在联邦状态里面查看了。
这样我们就配置完成。
5.2 Federation Queue 联邦队列
联邦队列的功能和联邦交换机的功能类似,联邦队列可以在多个Broker节点(或者集群)之间为单个队列提供均衡负载的功能。一个联邦队列可以连接一个或者多个上游队列(upstream queue),并从这些上游队列中获取消息以满足本地消费者消费消息的需求。
**配置:**这里只需要添加一个联邦队列的策略即可!!!
六、Shovel
Shovel和Federation的功能类似,Shovel能可靠持续地从一个Broker中的队列(作为源端source)拉取数据并转发至另一个Broker中的交换器(作为目的端destination)。作为源端的队列和作为目的端的交换器可以同时位于同一个Broker,也可以位于不同的Broker上。Shovel能够负责连接源和目的地、负责消息的读写及负责连接失败问题的处理。
搭建环境
java
rabbitmq-plugins enable rabbitmq_shovel
rabbitmq-plugins enable rabbitmq_shovel_management
在Shovel中只需要指定源端和目的端的地址和队列即可,操作比联邦更加简便直接。
总结
终章!!!RabbitMQ的学习就告一段落啦!圆满完成一周学一个中间件的计划,接下来荔枝会将这七篇文章复盘一下收录在专栏里面,大家有需要的可以看看我的中间件专栏,后续荔枝也会结合项目实战进行技术栈知识的复盘的。最后,任重道远,一起加油找到好工作哈哈哈~~~
今朝已然成为过去,明日依然向往未来!我是荔枝,在技术成长之路上与您相伴~~~
如果博文对您有帮助的话,可以给荔枝一键三连嘿,您的支持和鼓励是荔枝最大的动力!
如果博文内容有误,也欢迎各位大佬在下方评论区批评指正!!!