过期时间TTL
过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了这个时间之后消息将自动被删除。
设置过期时间TTL的方式
1.可以在queue中设置
@Bean
public Queue directTtlQueue(){
//设置过期时间
Map<String, Object> args = new HashMap<>();
//一定是设置一个int类型;如果是字符串会报错
args.put("x-message-ttl",5000);
return new Queue("ttl.direct.queue",true,false,false,args);
}
2.在发送消息的时候配置
/**
*
* @param userId
* @param productId
* @param num
* ttl 发消息的时候设置ttl
*/
public void makeOrderTtlMSg(String userId,String productId,int num){
//1.根据商品id确认是否库存充足
//2.保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:"+orderId);
//3.通过消息队列来完成消息的分发
/**
* @param1 交换机
* @param2 路由key/队列名
* @param3 消息内容
*/
String exchangeName = "ttl_direct_exchange";
String routingkey = "ttlMsg";
//给消息设置过期时间
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置过期时间,这里是字符串
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
rabbitTemplate.convertAndSend(exchangeName,routingkey,orderId,messagePostProcessor);
}
当两边均设置了过期时间以最小的过期时间为准,如queue设置的3s,发消息设置的是5s,则过期时间为3s
死信队列
DLX,全称为Dead-Letter-Exchange,可以称之为死信交换器,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。
消息变成死信,可能由于以下的原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
##内存磁盘监控
一般内存设置为,自身内存的0.4 - 0.7,官网说的是设置在0.4~0.66之间
修改rabbitmq设置的内存
rabbitmqctl set_vm_memory_high_watermark < fraction>
rabbitmqctl set_vm_memory_high_watermark absolute 50MB
可在配置文件中配置 rabbitmq.conf
#默认
#vm_memory_high_watermark.relative = 0.4
#使用relative 相对值进行设置fraction建议数值在0.4到0.7之间,不超过0.7
vm_memory_high_watermark.relative = 0.6
#使用absolute的绝对值的方式,KB MB GB对应如下
vm_memory_high_watermark.absolute =2GB
内存换页
在RabbitMQ达到内存阈值并阻塞生产者之前,会尝试将内存中的消息换页到磁盘,以释放内存空间。内存换页由换页参数控制,默认为0.5,表示当内存使用量达到内存阈值的50%时会进行换页,也就是0.4*0.5=0.2。
vm_memory_high_watermark_paging_ratio=0.5
当换页阈值大于1时,相当于禁用了换页功能
集群
单机多实例搭建
1.一主多从
启动主节点
#停止应用
sudo rabbitmqctl -n rabbit-1 stop_app
#目的是清除节点上的历史数据(如不清楚 无法将节点加入到集群)
sudo rabbitmqctl -n rabbit-1 reset
#启动应用
sudo rabbitmqctl -n rabbit-1 start_app
绑定从节点
#如果是单独的机器@‘Server-node’,就是ip;或者虚拟映射ip的别名
sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@‘Server-node’
#停止应用
sudo rabbitmqctl -n rabbit-2 stop_app
#目的是清除节点上的历史数据(如不清楚 无法将节点加入到集群)
sudo rabbitmqctl -n rabbit-2 reset
#将rabbit2节点加入到rabbit1(主节点)集群当中【server-node服务器的主机名】
#如果是单独的机器@'Server-node',就是ip;或者虚拟映射ip的别名
sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@'Server-node'
#启动应用
sudo rabbitmqctl -n rabbit-2 start_app
验证集群状态
sudo rabbitmqctl cluster_status -n rabbit-1
主从集群里,任一个节点新加队列或交换机都会在所有节点中展示
当主节点从节点均挂掉时,先启从节点无法重启;
主节点挂掉后无法正常使用
#停止一个节点
rabbitmqctl -n rabbit-2 stop_app
分布式事务
在不同系统之前如何保证数据完整性的一种方案
分布式事务的方式
一、 两阶段提交(2PC)需要数据库厂商的支持,java组件有atomikos等
两阶段提交(Two-phase Commit,2PC)通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
存在的问题:
1.同步阻塞,所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作
2.单点问题,协调者在2PC中起到非常大的作用,发生故障将会造成很大的影响,特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其他操作
3.数据不一致,在阶段二,如果协调者只发送了部分Commit消息,此时网络发生异常,那么只有部分参与者接收到Commit消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
4.太过保守,任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
二、补偿事务(TCC)严选、阿里、蚂蚁金服
TCC其实就是采用补偿机制,其核心思想是:针对每个操作,要注册一个与其对应的确认和补偿(撤销)操作,它分为三个阶段:
- Try 阶段主要对业务系统做检测及资源预留
- Confirm阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行Confirm阶段时,默认—Confirm阶段是不会出错的,即只要Try成功,Confirm一定成功
- Cancel阶段主要是在Try执行错误,需要回滚状态下执行的业务取消,预留资源释放
优点:
比2PC实现以及流程相对简单一些,但数据的一致性比2PC也要差一些
缺点:
在2 、3步中可能失败,TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能不太好定义和处理。
三、本地消息表(异步确保)比如:支付宝、微信支付主动查询支付状态,对账单的形式
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
- 分布式事务操作的一方完成业务数据操作之后,向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
- 之后将本地消息表中的消息转发到Kafka等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
- 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
优点:
一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:
消息表会耦合到业务系统中,如果没有封装号的解决方案,会有很多杂活需要处理
TCC事务问题:
无论何种方式也无法完全避免,只能让更大比例数据一致
使用RabbitMQ消费消息失败可能出现的问题:
死循环一直重试消费,导致rabbitmq磁盘慢;
解决消息重试的几种方案:
1.控制重发的次数
2.try+catch+手动ack
3.try+catch +手动ack+死信队列+人工干预