SpringBoot集成RabbitMQ如何使用多线程进行消费


前言

SpringBoot家族对rabbitmq提供了非常好了集成,今天主要是针对springboot中如何使用rabbitmq以及在业务中如何多线程处理消息


一、安装RabbitMQ

老规矩,安装MQ

1.安装erlang语言

https://www.erlang.org/downloads

erlang语言需要设置环境,和jdk环境设置一样

2.安装MQ

https://www.rabbitmq.com/download.html

启动sbin目录下的rabbitmq-server.bat

3.可视化界面

http://localhost:15672

默认账户密码 : guest

二、springboot集成RabbitMq简单使用

1.引入依赖

    <!--spirngboot集成rabbitmq-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>

2.yml配置

spring:
  rabbitmq:
    host: ${RABBIT_MQ_HOST:xxx.xxx.xxx}
    port: ${RABBIT_MQ_PORT:xxxx}
    username: ${RABBIT_MQ_USERNAME:xxxx}
    password: ${RABBIT_MQ_PASSWORD:xxxx}
    virtual-host: /    #类似于mysql的库
    listener:          
      direct:          #容器模型
        acknowledge-mode: MANUAL
      simple:
        acknowledge-mode: MANUAL
        retry:        #重试
          enabled: true
          max-attempts: 3
    publisher-confirms: true   #确认机制
    publisher-returns: true

如果紧使用简单的功能,那么可以省略virtual-host以下的部分

3.申明队列

    //声明队列:针对于转码的队列
    @Bean(QUEUE_WORKQUEUE_TRANSCODE)
    public Queue QUEUE_WORKQUEUE_TRANSCODE() {
        Queue queue = new Queue(QUEUE_WORKQUEUE_TRANSCODE,true,false,false);
        //参数为队列,持久化,排他性,自动删除
        return queue;
    }

使用默认交换机。所以不用申明交换机和绑定

4.发送消息和消息消费

rabbitTemplate.convertAndSend("交换机(不写为默认交换机)", "队列名", 
					"消息体",
					new CorrelationData(aiTask.getId())); // 可省略,确认机制使用

	@RabbitListener(queues = "队列名")
    public void handleMessage(byte[] message){
        System.out.println("消费消息");
        System.out.println(new String(message));
    }

三 rabbitmq高级使用

两种方式,使用container容器,使用注解@RabbitListener指定容器工厂

1.SimpleMessageListenerContainer

@Bean
    public SimpleMessageListenerContainer AiTaskListenerContainer() {
        SimpleMessageListenerContainer container = new  SimpleMessageListenerContainer(cachingConnectionFactory);
        // 监听队列名
        container.setQueueNames(AiMqConfig.QUEUE_WORK_AI_TASK);
        // 当前消费者数量 开启几个线程去处理数据 支持运行时动态修改
        container.setConcurrentConsumers(5);
        // 最大消费者数量  ,  消息堵塞太多的时候,会帮我自动扩展到我的最大消费者数量
        container.setMaxConcurrentConsumers(10);
        // 是否重回队列
        container.setDefaultRequeueRejected(true);
        // 手动确认
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        // 设置监听器
        container.setMessageListener(new ChannelAwareMessageListener(){
            @Override
            public void onMessage(Message message, Channel channel) throws Exception {
                // 消息的唯一性ID deliveryTag:该消息的index 自增长
                long deliveryTag = message.getMessageProperties().getDeliveryTag();
                String id  = "";
                try {
                    String msg = message.toString();
                    byte[] messageBody = message.getBody();
                    String s = new String(messageBody);
                    AiMqMessageVo mqMessageVo = JSONObject.parseObject(s, AiMqMessageVo.class);
                    System.out.println("消息: " + msg);
                    System.out.println("消息来自: "+message.getMessageProperties().getConsumerQueue());
                    System.out.println(mqMessageVo);
                    // 同一时刻服务器只会发一条消息给消费者,能者多劳模式
                    channel.basicQos(1);
                    // 手动确认
                    channel.basicAck(deliveryTag, true);
                } catch (Exception e) {
                    // 拒绝策略
                    //basicReject一次只能拒绝接收一个消息,而basicNack方法可以支持一次0个或多个消息的拒收,并且也可以设置是否requeue。
                    channel.basicReject(deliveryTag, false); //是否批量 false
                              System.out.println("签收失败");
                }
            }
        });
        return container;
    }

2.@RabbitListener

@Configuration
public class RabbitMQConfig {
    public static final String QUEUE_WORK_AI_TASK = "queue_work_ai_task";
    // 声明交换机
    // 声明队列
	@Bean("AIContainerFactory")
	public SimpleRabbitListenerContainerFactory containerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer, 
		ConnectionFactory connectionFactory) {
   		SimpleRabbitListenerContainerFactory container= new SimpleRabbitListenerContainerFactory();
   		container.setConcurrentConsumers(5);
   		container.setMaxConcurrentConsumers(10);
  		configurer.configure(factory, connectionFactory);
  		// 是否重回队列
        container.setDefaultRequeueRejected(true);
        // 手动确认
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
   		return factory;
 	}
 }
@Component
public class ReceiveHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(ReceiveHandler.class);
    // 在@RabbitListener注解中指定容器工厂
    @RabbitListener(queues = {RabbitmqConfig.QUEUE_WORK_AI_TASK },
                    containerFactory = "AIContainerFactory")
   //@RabbitListener(queues = {MqConstants.OA_TAB_REFRESH}, concurrency = "10") 也能实现十个并发
    public void aiTask(String msg,Message message,Channel channel){
        LOGGER.info("receive message is:"+msg);
    }
}

3.手动确认机制

在rabbitmqconfig中添加确认机制,所以在发送消息时,要添加消息的id作为唯一标识

//消息发送确认模式  ConfirmCallback确认模式
    @PostConstruct
    public void initRabbitTemplate(){
        /**
         * @param correlationData  当前消息的唯一关联数据  ,这个是消息的唯一id
         * @param ack 消息是否成功收到
         * @param cause 失败原因
         */
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (!ack){
                    //修改数据库
                    aiTaskService.update(new UpdateWrapper<AiTask>()
                            .eq("id",correlationData.getId())
                            .set("cause",cause)
                            .set("state", AiTask.AI_WORK_FAILURE));
                }
            }
        });
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 只要消息没有投递给指定的队列,就会触发这个失败回调
             * @param message   投递失败的消息
             * @param replyCode  回复的状态码
             * @param replyText  回复的文本内容
             * @param exchange  消息发送的交换机
             * @param routingKey  消息走的路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                //修改数据库
                String s = new String(message.getBody());
                AiMqMessageVo aiMqMessageVo = JSONObject.parseObject(s, AiMqMessageVo.class);
                aiTaskService.update(new UpdateWrapper<AiTask>()
                        .eq("id",aiMqMessageVo.getId())
                        .set("cause","未成功投递给指定队列。信息 - > 状态码:"+replyCode + " 文本内容:"+replyText  + "  交换机:"+exchange  + "  路由:"+routingKey)
                        .set("state", AiTask.AI_WORK_FAILURE));
            }
        });
    }

总结

写这些代码已经过去一两个月了,回看一下也有点蒙蔽,有很多不足的地方,代码也是以我目前做的项目来的,一些变量名可以换成你自己的变量名,主要是理清思路。一些难理解的地方,我已经做了详细的注释,不懂可以再次百度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值