StringBoot集成Rabbit Redis和ack机制双重保险,保障消息一定能够正确的消费
转: StringBoot集成Rabbit,根据业务返回ACK
为了维护消息的有效性,当消费消息时候处理失败时候,不进行消费,需要我们根据业务区返回ACK,本项目我使用Redis和ack机制双重保险,保障消息一定能够正确的消费
首先,接着上部分内容,使用Topic,机制(不明白的,可以回顾上部分内容)
上部分内容,我们使用SpringBoot注解,去实现,但是控制权不完全账务,当进行大规模项目时候,不太建议使用
@RabbitListener(queues = TopicRabbitConfig.USER_QUEUE)
@RabbitHandler
public void processUser(String message) {
threadPool.execute(new Runnable() {
@Override
public void run() {
logger.info("用户侧流水:{}",message);
}
});
}
根据源码分析,当然这里不分析源码,有兴趣的可以多失败几次就ok明白了
在配置类中定义监听器,监听这个序列(
AcknowledgeMode.MANUAL是必须的哦)
/**
* 接受消息的监听,这个监听客户交易流水的消息
* 针对消费者配置
* @return
*/
@Bean
public SimpleMessageListenerContainer messageContainer1(ConnectionFactory connectionFactory, TransactionConsumeImpl transactionConsume) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setQueues(queueMessage());
container.setExposeListenerChannel(true);
container.setMaxConcurrentConsumers(1);
container.setConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认
container.setMessageListener(transactionConsume);
return container;
}
这个 TransactionConsumeImpl 要继承ChannelAwareMessageListener,主要说的手动返回ACK就是channel。调用
@Component
public class TransactionConsumeImpl implements ChannelAwareMessageListener {
private static final Logger logger = LoggerFactory.getLogger(TransactionConsumeImpl.class);
private static final Gson gson = new Gson();
@Autowired
JedisShardInfo jedisShardInfo;
@Autowired
ExecutorService threadPool;
@Autowired
BoluomeFlowService boluomeFlowService;
@Override
public void onMessage(Message message, Channel channel) throws Exception {
String boby = new String(message.getBody(), "utf-8");//转换消息,我们是使用json数据格式
threadPool.execute(new Runnable() { //多线程处理
@Override
public void run() {
Jedis jedis = jedisShardInfo.createResource();
jedis.sadd(TopicRabbitConfig.TRANSACTION_QUEUE, boby);//添加到key为当前消息类型的集合里面,防止丢失消息
BoluomeFlow flow = gson.fromJson(boby, BoluomeFlow.class);
String json = gson.toJson(flow);
if (boluomeFlowService.insert(flow)) { //当添加成功时候返回成功
logger.info("客户交易流水添加1条记录:{}", json);
jedis.srem(TopicRabbitConfig.TRANSACTION_QUEUE, boby);//从当前消息类型集合中移除已经消费过的消息
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);//手工返回ACK,通知此消息已经争取消费
} catch (IOException ie) {
logger.error("消费成功回调成功,io操作异常");
}
} else {
logger.info("客户交易流水添加失败记录:{}", json);
}
}
});
}
}
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); // 消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); // ack返回false,并重新回到队列,api里面解释得很清楚
- channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); // 拒绝消息
- true 发送给下一个消费者
- false 谁都不接受,从队列中删除
Rabbitmq进阶
)))); factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); return factory; }
//消费者配置 @RabbitListener 和 @Bean SimpleMessageListenerContainer 方式
/** * 针对消费者配置 * 方式一 每一个Queue 对应一个SimpleMessageListenerContainer * 指定消息接受监听器 MessageListener implements ChannelAwareMessageListener 接口 自己实现onMessage方法 * * 作用: 接受消息的监听,这个监听指定Queue(payQueue)客户交易流水的消息 * @return */ /* @Bean public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory) { SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); container.setQueues(payQueue()); container.setExposeListenerChannel(true); container.setMaxConcurrentConsumers(1); container.setConcurrentConsumers(1); //设置确认模式手工确认 //设置ack方式为手动,增加对应队列的监听器。acknowledge="manual" 则开启ack机制 container.setAcknowledgeMode(AcknowledgeMode.MANUAL); container.setMessageListener(transactionConsumer); return container; } */ /** * 方式二 @RabbitListener * SimpleRabbitListenerContainerFactory 可以生成 RabbitListenerContainer */ //factory.setAcknowledgeMode(AcknowledgeMode.AUTO); // 自动响应时 简单配置即可 不需要手动确认是否消费 @RabbitListener(queues = "payQueue") public void displayMail(Mail mail) throws Exception { System.out.println("队列监听器1号收到消息"+mail.toString()); }
//factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); //需要手动确认消息是否消费时 这样处理 @RabbitListener(queues = "payQueue") public void process(@Payload Mail mail, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel)throws Exception{ logger.info("rabbit receiver message:{}",mail); try { channel.basicAck(deliveryTag, false); }catch (Exception e){ logger.error("process message error: {}",e); } }
}
方式二 的消息接受MessageListener 实现类 TransactionConsumerImpl
import com.alibaba.fastjson.JSONObject;import com.rabbitmq.client.Channel;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.amqp.core.Message;import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;import org.springframework.amqp.support.converter.SimpleMessageConverter;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import po.Mail; import java.io.IOException;import java.util.concurrent.ExecutorService; @Service("transactionConsumerImpl")public class TransactionConsumerImpl implements ChannelAwareMessageListener { private static final Logger logger = LoggerFactory.getLogger(TransactionConsumerImpl.class); private SimpleMessageConverter converter = new SimpleMessageConverter(); @Autowired ExecutorService threadPool; // @Autowired //BoluomeFlowService boluomeFlowService; //只有在消息处理成功后发送ack确认,或失败后发送nack使信息重新投递 public void onMessage(final Message message, final Channel channel) throws Exception { final String boby = new String(message.getBody(), "utf-8");//转换消息,我们是使用json数据格式 Object msg = converter.fromMessage(message); // todo mail chuli System.out.println(JSONObject.toJSONString(msg)); try { //手工返回ACK,通知此消息已经争取消费 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (IOException e1) { e1.printStackTrace(); System.out.println("消息已重复处理失败,拒绝再次接收 失败..."); } /* threadPool.execute(new Runnable() { //多线程处理 public void run() { Jedis jedis = jedisShardInfo.createResource(); jedis.sadd(TopicRabbitConfig.TRANSACTION_QUEUE, boby);//添加到key为当前消息类型的集合里面,防止丢失消息 BoluomeFlow flow = gson.fromJson(boby, BoluomeFlow.class); String json = gson.toJson(flow); if (boluomeFlowService.insert(flow)) { //当添加成功时候返回成功 logger.info("客户交易流水添加1条记录:{}", json); jedis.srem(TopicRabbitConfig.TRANSACTION_QUEUE, boby);//从当前消息类型集合中移除已经消费过的消息 try { channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);//手工返回ACK,通知此消息已经争取消费 } catch (IOException ie) { logger.error("消费成功回调成功,io操作异常"); } } else { logger.info("客户交易流水添加失败记录:{}", json); } try { System.out.println("consumer--:" + message.getMessageProperties() + ":" + new String(message.getBody())); // deliveryTag是消息传送的次数,我这里是为了让消息队列的第一个消息到达的时候抛出异常,处理异常让消息重新回到队列,然后再次抛出异常,处理异常拒绝让消息重回队列 if (message.getMessageProperties().getDeliveryTag() == 1 || message.getMessageProperties().getDeliveryTag() == 2) { throw new Exception(); } channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); // false只确认当前一个消息收到,true确认所有consumer获得的消息 } catch (Exception e){ e.printStackTrace(); if (message.getMessageProperties().getRedelivered()) { System.out.println("消息已重复处理失败,拒绝再次接收..."); try { channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); // 拒绝消息 } catch (IOException e1) { e1.printStackTrace(); System.out.println("消息已重复处理失败,拒绝再次接收 失败..."); } } else { System.out.println("消息即将再次返回队列处理..."); try { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); // requeue为是否重新回到队列 } catch (IOException e1) { e1.printStackTrace(); System.out.println("requeue为是否重新回到队列 失败..."); } } } } });*/ }}
本地测试代码地址: xxx
StringBoot集成Rabbit Redis和ack机制双重保险,保障消息一定能够正确的消费的更多相关文章
- (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】
[本文章是否对你有用以及是否有好的建议,请留言] 本文章牵涉到的技术点比较多:Spring Data JPA.Redis.Spring MVC,Spirng Cache,所以在看这篇文章的时候,需要对 ...
- Spring Boot从入门到精通(六)集成Redis实现缓存机制
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言 ...
- RabbitMq + Spring 实现ACK机制
概念性解读(Ack的灵活) 首先啊,有的人不是太理解这个Ack是什么,讲的接地气一点,其实就是一个通知,怎么说呢,当我监听消费者,正常情况下,不会出异常,但是如果是出现了异常,甚至是没有获取的异常,那 ...
- Storm的ack机制在项目应用中的坑
正在学习storm的大兄弟们,我又来传道授业解惑了,是不是觉得自己会用ack了.好吧,那就让我开始啪啪打你们脸吧. 先说一下ACK机制: 为了保证数据能正确的被处理, 对于spout产生的每一个tup ...
- Spring boot集成Rabbit MQ使用初体验
Spring boot集成Rabbit MQ使用初体验 1.rabbit mq基本特性 首先介绍一下rabbitMQ的几个特性 Asynchronous Messaging Supports mult ...
- RabbitMQ的消息确认ACK机制
1.什么是消息确认ACK. 答:如果在处理消息的过程中,消费者的服务器在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失.为了确保数据不会丢失,RabbitMQ支持消 ...
- Redis键通知机制
Redis键通知机制 一.概念 自从redis2.8.0以后出了一个新特性,Keyspace Notifications 称为“键空间通知”. 这个特性大概是,凡是实现了Redis的Pub/Sub的客 ...
- Redis的主从复制与Redis Sentinel哨兵机制
1 Redis的主从复制 1.1 什么是主从复制 持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损 ...
- kafkaspot在ack机制下如何保证内存不溢
新浪微博:intsmaze刘洋洋哥. storm框架中的kafkaspout类实现的是BaseRichSpout,它里面已经重写了fail和ack方法,所以我们的bolt必须实现ack机制,就可以 ...
随机推荐
- 微软BI 之SSAS 系列 - 多维数据集中度量值设计时的聚合函数 (累加性_半累加性和非累加性)
在 SSAS 系列 - 实现第一个 Cube 以及角色扮演维度,度量值格式化和计算成员的创建 中主要是通过已存在的维度和事实数据创建了一个多维数据集,并同时解释了 Role-Playing Dimen ...
- HDS TrueCopy-数据远程容灾白皮书-IOPS数据
http://wenku.it168.com/d_000767925.shtml Truecopy 安装实施-包含图 http://www.docin.com/p-261693079.html 来自: ...
- spring的jdbc
Spring将替我们完成所有使用JDBC API进行开发的单调乏味的.底层细节处理工作. 操作JDBC时Spring可以帮我们做这些事情: 定义数据库连接参数,打开数据库连接,处理异常,关闭数据库连接 ...
- Keepalived系列一:安装Keepalived
1:简介 它是一个基于VRRP协议来实现的WEB服务高可用方案,可以利用其来避免单点故障.一个WEB服务至少会有2台服务器运行Keepalived,一台为主服务器(MASTER),一台为备份服务器(B ...
- Ulua_toLua_基本案例(二)_ScriptsFromFile
在Untiy中用Lua.必需要LuaInterface. LuaInterface的介绍请看:点击打开链接 能够先光写Lua,生成.lua的纯文件. 再Unity中通过,luaState.DoFile ...
- The cast to value type 'System.Decimal' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
CurrentStock = db.BillEntry.Where(b => b.GoodsId == item.GoodsId).Sum(b => (decimal?)b.Qty) ?? ...
- cocos2d-js 3.0 屏幕适配方案 分辨率适应
首先介绍一个api和相应的参数: cc.view.setDesignResolutionSize(1024, 768, cc.ResolutionPolicy.FIXED_WIDTH); 这里设置游戏 ...
- countif
countif(range,criteria) range:表示要计算其中非空单元格数目的区域 criteria:表示统计条件 eg:求得分大于等于80分小于90分的学生数
- C++比较特殊的构造函数和初始化语法
C++的构造函数 看Qt创建的示例函数, 第一个构造函数就没看懂. 是这样的 Notepad::Notepad(QWidget *parent) : QMainWindow(parent), ui(n ...
- Ubuntu下的init.d管理update-rc.d
计算机在启动的时候会自动执行一些脚本,用于启动一些应用程序服务,update-rc.d 是管理这些脚本的常用命令之一. 首先这是 LinuxQuestions 中对 update-rc.d 的定义: ...