Rabbitmq专题:springboot如何整合Rabbitmq?Rabbitmq有哪些工作模式?


1. Rabbitmq的安装

本例以centos7为例,还可以使用其他云服务器,如果使用云服务器,记得开放15672端口

# 1:安装rabbitmq所需要的依赖包
yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc- c++ kernel-devel m4 ncurses-devel tk tc xz

# 2:下载安装包(选择一个文件夹下载 cd /tuling/rebbitMQ)也可使用tfp上传
wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm 
wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm 
wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm

下载结果如下:
erlang­18.3­1.el7.centos.x86_64.rpm 
socat­1.7.3.2­5.el7.lux.x86_64.rpm 
rabbitmq­server­3.6.5­1.noarch.rpm

# 3. 安装rabbitMQ相关服务
第一步:安装erlang语言环境
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm

第二步:安装socat加解密软件
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm

第三步:最后安装rabbitmq
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

# 4. 执行管控台插件:(不然不能在浏览器登录)
rabbitmq-plugins enable rabbitmq_management

# 5. 修改默认配置信息
# 比如修改密码、配置等等,例如:loopback_users 中的 <<"guest">>,只保 留guest 
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app 

修改 本机系统文件
a:修改 vim /etc/rabbitmq/rabbitmq-env.conf  添加: NODENAME=rabbit
b:修改 vim /etc/hostname	添加: zhb
c:修改 vim /etc/hosts  	添加: 192.168.100.100 zhb   hostname和地址映射

6. 启动
service rabbitmq-server start # 启动服务 
service rabbitmq-server stop # 停止服务
service rabbitmq-server restart # 重启服务

7. 访问地址
http://ip地址:15672 

通过检查端口 lsof -i:5672、 ps -ef|grep rabbitmq 查看是否启动成功

访问时使用其自带的guest/guest的用户名和密码,进入rabblitMq界面后,如果需要创建自定义用户,在admin下配置 users 和 Virtual Hosts,其中 Virtual Hosts就相当于mysql的各种库的概念,并且可以指定用户对库和表等操作的权限。如下所示:
在这里插入图片描述


2. Rabbitmq的基本概念

常见的MQ产品如下:
在这里插入图片描述
单机吞吐量对比

  • rabbiutMQ:5w
  • rocketMQ:12w
  • Kafka:17w

综合上面的材料得出以下两点:

  1. 中小型软件公司,建议选RabbitMQ.一方面,erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。正所谓,成也萧何,败也萧何!他的弊端也在这里,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?所幸,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要。不考虑rocketmq和kafka的原因是,一方面中小型软件公司不如互联网公司,数据量没那么大,选消息中间件,应首选功能比较完备的,所以kafka排除。不考虑rocketmq的原因是,rocketmq是阿里出品,如果阿里放弃维护rocketmq,中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐。
  2. 大型软件公司,根据具体使用在rocketMq和kafka之间二选一。一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的。至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。

本文将介绍rabbitMq的使用,RabbitMQ 采用 Erlang 语言开发。Erlang 语言由 Ericson 设计,专门为开发高并发和分布式系统的一种语言。RabbitMQ 基础架构如下图
在这里插入图片描述

  1. Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
  2. Virtual host:虚拟分组,操作隔离,每个项目连接一个虚拟机。不同用户可在自己的虚拟分组中创建 exchange/queue 等,类似于Rabbitmq中的Topic主题,一个项目一个
  3. Connection:publisher/consumer 和 broker 之间的 TCP 连接
  4. Channel:Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel 进行通讯,不同channel之间完全隔离,Channel 作为轻量级的 Connection 极大减少了操作系统建立TCP connection 的开销
  5. Exchange:交换机,存在虚拟机中,虚拟机中不可有重复的交换机。message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去
  6. Queue:消息最终被送到这里等待 consumer 取走
  7. Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 Exchange 中的查询表中,用于 message 的分发依据。类似于rocketMQ中的Tags


3. RabbitMQ的工作模式

RabbitMQ官方提供了6中工作模式,第六种RPC远程调用模式,严格意义上不太算MQ,所以在这里不做介绍。

导入依赖

    <dependencies>
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
    </dependencies>

创建获取连接的工具类

public class RabbitUtils {

    // 初始化 publisher/consumer与RabbitMq的连接
    private static ConnectionFactory connectionFactory = new ConnectionFactory();
    static {
        connectionFactory.setHost("192.168.100.100");   //RabbitMQ的ip地址
        connectionFactory.setPort(15672);	//15672是RabbitMQ的默认端口号
        connectionFactory.setUsername("zhb");   //用户名
        connectionFactory.setPassword("zhb");   //密码
        connectionFactory.setVirtualHost("/test_Virtual");  //操作的虚拟机
    }
    
    //获取连接
    public static Connection getConnection(){
        Connection conn = null;
        try {
            conn = connectionFactory.newConnection();
            return conn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}


3.1 “Hello World!” 简单模式

"Hello World!"工作模式也是最简单的工作模式,使用默认的交换机,适用于普通的A生产-B消费,如下图
在这里插入图片描述

生产者代码

public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {

        //获取TCP长连接
        Connection conn = RabbitUtils.getConnection();
        //创建通信“通道”,相当于TCP中的虚拟连接
        Channel channel = conn.createChannel();

        //创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
        //第一个参数:队列名称ID
        //第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
        //第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
        //第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
        //其他额外的参数, null
        channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD,false, false, false, null);

        String message = "hello白起666";
        //四个参数
        //exchange 交换机,暂时用不到,在后面进行发布订阅时才会用到
        //队列名称
        //额外的设置属性
        //最后一个参数是要传递的消息字节数组
        channel.basicPublish("", RabbitConstant.QUEUE_HELLOWORLD, null,message.getBytes());
        channel.close();
        conn.close();
        System.out.println("===发送成功===");

    }
}

消费者代码

public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException {


        //获取TCP长连接
         Connection conn = RabbitUtils.getConnection();
        //创建通信“通道”,相当于TCP中的虚拟连接
        Channel channel = conn.createChannel();

        //创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
        //第一个参数:队列名称ID
        //第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
        //第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
        //第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
        //其他额外的参数, null
        channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD,false, false, false, null);

        //从MQ服务器中获取数据

        //创建一个消息消费者
        //第一个参数:队列名
        //第二个参数代表是否自动确认收到消息,false代表手动编程来确认消息,这是MQ的推荐做法
        //第三个参数要传入DefaultConsumer的实现类
        channel.basicConsume(RabbitConstant.QUEUE_HELLOWORLD, false, new Reciver(channel));

        channel.basicConsume(RabbitConstant.QUEUE_HELLOWORLD , false , new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                String message = new String(body);
                System.out.println("消费者接收到的消息:"+message);

                System.out.println("消息的TagId:"+envelope.getDeliveryTag());
                //false只确认签收当前的消息,设置为true的时候则代表签收该消费者所有未签收的消息
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });
    }
}


3.2 Work queues 工作队列

在这里插入图片描述

          Work queues:工作队列模式,也使用默认的交换机,但在"Hello World!"工作模式的基础上增加了多个消费端,性能较高,因为它相对于下面的两种交换机不需要字符串匹配,比如12306订票成功发送短信通知,步骤如下

  • ①:生产者把100个短信任务发到RabbitMQ中,就不管了,继续处理其他任务
  • ②:由多个消费者从RabbitMQ队列中取任务,给用户发送短信。

生产者代码

public class OrderSystem {

    public static void main(String[] args) throws IOException, TimeoutException {
    	//获取连接
        Connection connection = RabbitUtils.getConnection();
        //创建Channel
        Channel channel = connection.createChannel();
        //声明一个队列
        channel.queueDeclare(RabbitConstant.QUEUE_SMS, false, false, false, null);
		//发送100个任务
        for(int i = 1 ; i <= 100 ; i++) {
            SMS sms = new SMS("乘客" + i, "13900000" + i, "您的车票已预订成功");
            String jsonSMS = new Gson().toJson(sms);
            //发任务
            channel.basicPublish("" , RabbitConstant.QUEUE_SMS , null , jsonSMS.getBytes());
        }
        System.out.println("发送数据成功");
        channel.close();
        connection.close();
    }
}

消费者

public class SMSSender1 {

    public static void main(String[] args) throws IOException {
        Connection connection = RabbitUtils.getConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare(RabbitConstant.QUEUE_SMS, false, false, false, null);

        //如果不写basicQos(1),则自动MQ会将所有请求平均发送给所有消费者
        //basicQos,MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),在从队列中获取一个新的
        channel.basicQos(1);//处理完一个取一个

        channel.basicConsume(RabbitConstant.QUEUE_SMS , false , new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String jsonSMS = new String(body);
                System.out.println("SMSSender1-短信发送成功:" + jsonSMS);

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                channel.basicAck(envelope.getDeliveryTag() , false);
            }
        });
    }
}

          注意:如果不设置 channel.basicQos(1),则100个任务会均分到3个消费上,如果某个消费者网络不好或其他原因,消费的比较慢,那系统整体速度则会变慢。所以推荐使用channel.basicQos(1),目的是使MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),在从队列中获取一个新的,这样消费快的多消费点,慢的少消费点,可有效提升系统性能!

SMSSender2、SMSSender3代码和SMSSender1一样,不过改了名字,代码省略。


3.3 Publish/Subscribe 发布/订阅

在这里插入图片描述
在订阅模型中,多了一个 Exchange 角色,而且过程略有变化:

  1. P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
  2. C:消费者,消息的接收者,会一直等待消息到来
  3. Queue:消息队列,接收消息、缓存消息
  4. Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型

Exchange有常见以下3种类型:

  1. Fanout:广播,将消息交给所有绑定到交换机的队列
  2. Direct:定向,把消息交给符合指定routing key 的队列
  3. Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

场景:气象局把天气信息放入交换机,新浪、百度、搜狐等通过各自的队列与交换机绑定,就可以获取到交换机中的天气数据!

交换机各参数如下:

   /**
    * 声明一个交换机
    * exchange:交换机的名称
    * type:交换机的类型 常见的有direct,fanout,topic等
    * durable:设置是否持久化。durable设置为true时表示持久化,反之非持久化.持久化可以将交换器存入磁盘,在服务器重启的时候不会丢失相关信息
    * autodelete:设置是否自动删除。autoDelete设置为true时,则表示自动删除。
    * 自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后,所有与这个交换器绑定的队列或者交换器都与此解绑。
    * 不能错误的理解—当与此交换器连接的客户端都断开连接时,RabbitMq会自动删除本交换机
    * arguments:其它一些结构化的参数,比如:alternate-exchange
    */
   channel.exchangeDeclare(exchangeName,exchangeType,true,false,null);

注意:Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失!

发布订阅模式与工作队列模式的区别如下:

  1. 工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机
  2. 发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)
  3. 发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑定到默认的交换机


3.4 Routing 路由模式

在这里插入图片描述
图解:

  1. P:生产者,向 Exchange 发送消息,发送消息时,会指定一个routing key
  2. X:Exchange(交换机),接收生产者的消息,然后把消息递交给与 routing key 完全匹配的队列
  3. C1:消费者,其所在队列指定了需要 routing key 为 error 的消息
  4. C2:消费者,其所在队列指定了需要 routing key 为 info、error、warning 的消息

说明:

  1. 队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)
  2. 消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey
  3. Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的Routingkey
    与消息的 Routing key 完全一致,才会接收到消息

代码示例

生产者发消息:

  //第一个参数交换机名字   第二个参数作为 消息的routing key
  //第三个消息属性  第四个消息体
  channel.basicPublish(“my_exchange”,"routing.key.A", null , me.getValue().getBytes());

交换机在与队列绑定关系时要加上routing key

   //指定队列与交换机以及routing key之间的关系
   //三个参数分别是:队列名、交换机、routing key
   channel.queueBind(“queue1”, “my_exchange”, "routing.key.A"); //只有这个会受到生产者发来的消息
   channel.queueBind(“queue1”, “my_exchange”, "routing.key.B");//routing key不匹配,收不到消息


3.5 Topics 主题模式

在这里插入图片描述
模式说明

  1. Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
  2. Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
  3. 通配符规则:# 匹配一个或多个词,* 只匹配一个词
    例如:
    item.# 能够匹配 item.insert.abc 或者item.insert
    item.* 只能匹配 item.insert

Topic 主题模式可以实现 Pub/Sub 发布与订阅模式和 Routing 路由模式的功能,只是 Topic 在配置routing key 的时候可以使用通配符,显得更加灵活


4. springboot整合RabbitMQ

引入依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>

 

①:生产者发送消息

 
1. ymal 配置

spring:
  rabbitmq:
    host: 192.168.100.100
    port: 5672
    virtual-host: tuling
    username: zhb
    password: zhb
    publisher-confirms: true  //开启confirm确认机制(是否到交换机)
    publisher-returns: true	  //开启returns确认机制(是否到队列)
    template:
      mandatory: true	//指定消息在没有被队列接收时是否强行退回(true)还是直接丢弃(false),如果需要return模式,则必须设置为true
    connection-timeout: 1000000

 
2. 配置交换机、队列、绑定关系

@Configuration
public class RabbitmqConfig {
	
	//交换机
    @Bean
    public DirectExchange orderToProductExchange() {
        DirectExchange directExchange = new DirectExchange(MqConst.ORDER_TO_PRODUCT_EXCHANGE_NAME,true,false);
        return directExchange;
    }
	
	//队列
    @Bean
    public Queue orderToProductQueue() {
        Queue queue = new Queue(MqConst.ORDER_TO_PRODUCT_QUEUE_NAME,true,false,false);
        return queue;
    }

	//绑定关系
    @Bean
    public Binding orderToProductBinding() {
        return BindingBuilder.bind(orderToProductQueue()).to(orderToProductExchange()).with(MqConst.ORDER_TO_PRODUCT_ROUTING_KEY);
    }
}

 
3. 消息发送及参数回调

注意MsgSender 类实现了 InitializingBean。 在MsgSender初始化完毕后,会调用 afterPropertiesSet 方法,完成mq服务器的参数回调配置

rabbitmq 整个消息投递的路径为:

  • producer —> rabbitmq broker —> exchange —> queue —> consumer
  • 消息从 producer 到 exchange 则会返回一个 confirmCallback
  • 消息从 exchange–>queue 投递失败则会返回一个 returnCallback

我们将利用这两个 callback 控制消息的可靠性投递,但是注意,使用这两种回调时,需要在spring boot生产端配置:

  • 开启confirm确认机制(是否到交换机)
    • spring.rabbitmq.publisher-confirms: true : 开启confirm 确认机制
  • 开启return确认机制(是否到队列)
    • spring.rabbitmq.publisher-returns: true :开启return 确认机制
    • spring.rabbitmq.template.mandatory: true :指定消息在没有被队列接收时是否强行退回(true)还是直接丢弃(false),return回调特有的配置!
@Component
@Slf4j
public class MsgSender implements InitializingBean {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private TulingMsgComfirm tulingMsgComfirm;

    @Autowired
    private TulingMsgRetrunListener tulingMsgRetrunListener;


    /**
     * 方法实现说明:真正的发送消息
     * @param msgTxtBo:发送的消息对象
     * @return:
     * @exception:
     */
    public  void senderMsg(MsgTxtBo msgTxtBo){

        log.info("发送的消息ID:{}",msgTxtBo.getMsgId());
		
		//mq确认的服务端id
        CorrelationData correlationData = new CorrelationData(msgTxtBo.getMsgId());

        rabbitTemplate.convertAndSend(MqConst.ORDER_TO_PRODUCT_EXCHANGE_NAME,MqConst.ORDER_TO_PRODUCT_ROUTING_KEY,msgTxtBo,correlationData);
    }

	//项目启动时,添加 confirm 和 return 回调
	// confirm回调:消息到达 交换机 exchange时的回调
	// return 回调:消息从交换机 到达队列时的回调
    @Override
    public void afterPropertiesSet() throws Exception {
    
    	//confirm回调
        rabbitTemplate.setConfirmCallback(tulingMsgComfirm);
        
        //return回调
        rabbitTemplate.setReturnCallback(tulingMsgRetrunListener);
        
        //设置消息转换器
        Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
    }
}

confirm 回调的实现

@Component
@Slf4j
public class TulingMsgComfirm implements RabbitTemplate.ConfirmCallback{

    @Autowired
    private MsgContentMapper msgContentMapper;

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String msgId = correlationData.getId();

        if(ack) {
            log.info("消息Id:{}对应的消息被broker签收成功",msgId);
            updateMsgStatusWithAck(msgId);
        }else{
            log.warn("消息Id:{}对应的消息被broker签收失败:{}",msgId,cause);
            updateMsgStatusWithNack(msgId,cause);
        }
    }

return 回调的实现

@Component
@Slf4j
public class TulingMsgRetrunListener implements RabbitTemplate.ReturnCallback {

    @Autowired
    private MsgContentMapper msgContentMapper;

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        try{
            ObjectMapper objectMapper = new ObjectMapper();
            MsgTxtBo msgTxtBo = objectMapper.readValue(message.getBody(),MsgTxtBo.class);
            log.info("无法路由消息内容:{},cause:{}",msgTxtBo,replyText);
			。。。。。
        }catch (Exception e) {
            log.error("更新消息表异常:{}",e);
        }
    }
}

 

②:消费端监听消息

1. ymal 配置

spring:
  rabbitmq:
    host: 192.168.100.100
    port: 5672
    virtual-host: tuling
    username: zhb
    password: zhb
    listener:
      simple:
        concurrency: 5
        max-concurrency: 10
        acknowledge-mode: manual  //消费者消费完毕,手动返回ack给borker
        prefetch: 1
        default-requeue-rejected: false

 
2. 消费者监听消息

消费端收到消息后有三种确认方式:

  • 自动确认:acknowledge="none"
  • 手动确认:acknowledge="manual"
  • 根据异常情况确认:acknowledge="auto"

         其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 messageRabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失,所以一般情况下会使用 手动确认acknowledge="manual"

         如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

    @RabbitListener(queues = {ORDER_TO_PRODUCT_QUEUE_NAME})
    @RabbitHandler
    public void consumerMsgWithLock(Message message, Channel channel) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        //获取消息体
        MsgTxtBo msgTxtBo = objectMapper.readValue(message.getBody(), MsgTxtBo.class);
        //获取ack标记
        Long deliveryTag = (Long) message.getMessageProperties().getDeliveryTag();

        if (redisTemplate.opsForValue().setIfAbsent(LOCK_KEY + msgTxtBo.getMsgId(), msgTxtBo.getMsgId())) {
            log.info("消费消息:{}", msgTxtBo);
            try {
                //更新消息表也业务表
                productService.updateProductStore(msgTxtBo);
                // 模拟消息签收异常
				// System.out.println(1/0);

				//消费成功的ack
                channel.basicAck(deliveryTag, false);
            } catch (Exception e) {
                /**
                 * 更新数据库异常说明业务没有操作成功需要删除分布式锁
                 */
                if (e instanceof BizExp) {
                    BizExp bizExp = (BizExp) e;
                    log.info("数据业务异常:{},即将删除分布式锁", bizExp.getErrMsg());
                    //删除分布式锁
                    redisTemplate.delete(LOCK_KEY);
                }

                //更新消息表状态
                MessageContent messageContent = new MessageContent();
                messageContent.setMsgStatus(MsgStatusEnum.CONSUMER_FAIL.getCode());
                messageContent.setUpdateTime(new Date());
                messageContent.setErrCause(e.getMessage());
                messageContent.setMsgId(msgTxtBo.getMsgId());
                msgContentMapper.updateMsgStatus(messageContent);
                //消费失败,拒绝消息
                channel.basicReject(deliveryTag,false);
            }

        } else {
            log.warn("请不要重复消费消息{}", msgTxtBo);
            channel.basicReject(deliveryTag,false);
        }

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值