转: 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机制双重保险,保障消息一定能够正确的消费的更多相关文章

  1. (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】

    [本文章是否对你有用以及是否有好的建议,请留言] 本文章牵涉到的技术点比较多:Spring Data JPA.Redis.Spring MVC,Spirng Cache,所以在看这篇文章的时候,需要对 ...

  2. Spring Boot从入门到精通(六)集成Redis实现缓存机制

    Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言 ...

  3. RabbitMq + Spring 实现ACK机制

    概念性解读(Ack的灵活) 首先啊,有的人不是太理解这个Ack是什么,讲的接地气一点,其实就是一个通知,怎么说呢,当我监听消费者,正常情况下,不会出异常,但是如果是出现了异常,甚至是没有获取的异常,那 ...

  4. Storm的ack机制在项目应用中的坑

    正在学习storm的大兄弟们,我又来传道授业解惑了,是不是觉得自己会用ack了.好吧,那就让我开始啪啪打你们脸吧. 先说一下ACK机制: 为了保证数据能正确的被处理, 对于spout产生的每一个tup ...

  5. Spring boot集成Rabbit MQ使用初体验

    Spring boot集成Rabbit MQ使用初体验 1.rabbit mq基本特性 首先介绍一下rabbitMQ的几个特性 Asynchronous Messaging Supports mult ...

  6. RabbitMQ的消息确认ACK机制

    1.什么是消息确认ACK. 答:如果在处理消息的过程中,消费者的服务器在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失.为了确保数据不会丢失,RabbitMQ支持消 ...

  7. Redis键通知机制

    Redis键通知机制 一.概念 自从redis2.8.0以后出了一个新特性,Keyspace Notifications 称为“键空间通知”. 这个特性大概是,凡是实现了Redis的Pub/Sub的客 ...

  8. Redis的主从复制与Redis Sentinel哨兵机制

    1    Redis的主从复制 1.1   什么是主从复制 持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损 ...

  9. kafkaspot在ack机制下如何保证内存不溢

    新浪微博:intsmaze刘洋洋哥.   storm框架中的kafkaspout类实现的是BaseRichSpout,它里面已经重写了fail和ack方法,所以我们的bolt必须实现ack机制,就可以 ...

随机推荐

  1. 微信小程序 - setData:key的几种用法

    1. 常量key渲染   2. 变量key渲染(字符串和变量先拼接) 3.对象key渲染

  2. 使用Phantomjs和ChromeDriver添加Cookies的方法

    一.查看代码 : namespace ToutiaoSpider { class Program { static void Main(string[] args) { var db = Db.Get ...

  3. MySql 错误代码 1045

    错误代码 1045Access denied for user 'root'@'localhost' (using password:YES)解决办法是重新设置root用户密码,在Windows平台下 ...

  4. Redis问题MISCONF Redis is configured to save RDB snapshots....

    Redis问题MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on di ...

  5. Aerospike系列:8:集群宕机演练

    1:初始的集群状态 2:关掉192.168.91.133:3000 3:再关掉192.168.91.135:3000 3:再关掉192.168.91.144:3000 5:恢复192.168.91.1 ...

  6. infobright系列一:源码安装infobright

    1:下载infobright http://www.infobright.org/downloads/ice/infobright-4.0.7-0-src-ice.tar.gz 2:查看环境 rpm ...

  7. 字符串匹配算法——BF、KMP、Sunday

    一:Brute force 从源串的第一个字符开始扫描,逐一与模式串的对应字符进行匹配,若该组字符匹配,则检测下一组字符,如遇失配,则退回到源串的第二个字符,重复上述步骤,直到整个模式串在源串中找到匹 ...

  8. 腾讯云ubuntu搭建jdk

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6377878.html 在购买了腾讯云ubuntu主机后,需要手动搭建java环境.安装tomcat等.ubun ...

  9. kettle实现数据库迁移----多表复制向导

    kettle实现数据库迁移----多表复制向导 需求: 做数据仓库时,需要将业务系统CRM抽取到数据仓库的缓冲层,业务系统使用的是SqlServer数据库,数据仓库的缓冲层使用的是mysql数据库,为 ...

  10. 奥比中光3D视觉传感器--OpenNI 2配置

    PrimeSense是Kinect一代的芯片供应商,位于以色列,也是开源体感开发包OpenNI 的维护者.自从被 Apple 收购后,销声匿迹,OpenNI 也停止更新.现在可以从网站http://s ...