springboot-rabbitmq

 

 https://www.cnblogs.com/free-wings/p/10254998.html

1.服务端(消息提供者)

springboot amqp   默认非持久化

 

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target> 
    </properties>
    
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 配置文件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        
        
        <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
        
        <dependency>
            <!-- https://www.hutool.cn/docs -->
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.5.10</version>
        </dependency>
        
        <!--rabbitmq-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        
        
    </dependencies>
        <repositories>
            <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

 topic  消息多个客户端消费,默认公平分发

 如果手动确认,消息不会重复消费,例如客户端1消费了A消息,客户端2就接收不到A消息了

new Queue(TOPIC_QUEUE, true, false, false);

第二个参数持久化,如果不持久话,消息发送完就没了,不管客户端收没收到

 

@Configuration
public class RabbitConfig {
    

    private static final String TOPIC_QUEUE = "topic.message";
    
    @Bean
    public Queue queueMessage() {
//        //是否持久化
//        durable;
//        //是否声明该队列是否为连接独占,若为独占,连接关闭后队列即被删除
//        exclusive;
//        //是否自动删除,若没有消费者订阅该队列,队列将被删除
//        autoDelete;
//        //参数,可以指定队列长度,消息生存时间等队列的设置
//        java.util.Map<java.lang.String, java.lang.Object> arguments;
//        #开启重试 
//        spring: 
//            rabbitmq: 
//                listener: 
//                    retry: 
//                        enabled:true
//         
//        #重试次数,默认为3次
//        spring:
//            rabbitmq:
//                listener:
//                    retry:
//                        max-attempts: 5
        
        return new Queue(TOPIC_QUEUE, true, false, false);
    }

    @Bean
    TopicExchange exchange() {
        return new TopicExchange("topicExchange");
    }

  
    @Bean
    Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessage).to(exchange).with("topic.*");
    }

}

 

server.port=8081
server.servlet.context-path=/test

spring.rabbitmq.host=192.168.80.110
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest




 

@Component
public class Send1 {

    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    
    public boolean send() {
        rabbitTemplate.convertAndSend("topicExchange", "topic.add", "topic消息测试");
        System.out.println("发送消息");
        return true;
    }
}

 

客户端(消息消费者)

 spring.rabbitmq.listener.simple.acknowledge-mode=manual

可户端开启手动确认,如果不进行确认,消息不会删除,重启客户端的时候还会重新消费

如果客户端spring.rabbitmq.listener.simple.prefetch = 1并且没有确认,会卡第一条消息,不会继续调用,如果去掉,有多少条消息就会消费多少次,下次重启时还会重新消费

默认自动确认,确认后就消息就删除了

 

#设置消费端手动 ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual

#消费者一次消费的数量
#消费者数量
spring.rabbitmq.listener.simple.concurrency=2
#最大消费者数量
spring.rabbitmq.listener.simple.max-concurrency=2

 

客户端如果加spring.rabbitmq.listener.simple.prefetch = 1

一次消费一个,如果手动消息没有确认成功,会一直卡在这条消息中

channel.basicNack(tag,false,false);

如果将消息丢掉,会接着往下消费都会丢掉

 

 

 

import java.io.IOException;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

import com.rabbitmq.client.Channel;

@Component
public class Recv1 {
    
    
//    @RabbitListener(
//            bindings = @QueueBinding(
//                    value = @Queue(value = "boot.queue1", durable = "true"),
//                    exchange = @Exchange(value = "BOOT-EXCHANGE-1", type = "topic", durable = "true", ignoreDeclarationExceptions = "true"),
//                    key = "boot.*"
//            )
//    )
        
    @RabbitListener(queues = "topic.message")
    @RabbitHandler
    public void recevier(Message message, Channel channel,@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException{
        System.out.println("topic接收消息:" + new String(message.getBody()));
        //long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //手工ack
        //channel.basicAck(deliveryTag,true);
        try {

            /**
                                    * 无异常就确认消息
             * basicAck(long deliveryTag, boolean multiple)
             * deliveryTag:取出来当前消息在队列中的的索引;
             * multiple:为true的话就是批量确认,如果当前deliveryTag为5,那么就会确认
             * deliveryTag为5及其以下的消息;一般设置为false
             */
            channel.basicAck(tag, false);
        }catch (Exception e){
            /**
                                   * 有异常就绝收消息
             * basicNack(long deliveryTag, boolean multiple, boolean requeue)
             * requeue:true为将消息重返当前消息队列,还可以重新发送给消费者;
             *         false:将消息丢弃
             */
            channel.basicNack(tag,false,true);
        }
    }
    
    
    
}

 

 

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import xy.study.rabbitmq.conf.TopicRabbitConfig;

@Component
@Slf4j
public class HelloReceiver {

    /**
     * 下面四个消费者,exchange和RoutingKey都相同,最后两个消费者队列名都相同
     * @param msg
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = TopicRabbitConfig.QUEUE_NAME, durable = "true"),
            exchange = @Exchange(value = TopicRabbitConfig.TEST_EXCHANGE_HAHA, type = ExchangeTypes.TOPIC),
            key = TopicRabbitConfig.TEST_TOPIC_ROUTINGKEY)
    )
    public void queueName(@Payload String msg) {
        log.info("{}-----HelloReceiver msg : {}",TopicRabbitConfig.QUEUE_NAME,msg);
    }


    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = TopicRabbitConfig.QUEUE_NAME+".test", durable = "true"),
            exchange = @Exchange(value = TopicRabbitConfig.TEST_EXCHANGE_HAHA, type = ExchangeTypes.TOPIC),
            key = TopicRabbitConfig.TEST_TOPIC_ROUTINGKEY)
    )
    public void queueNameTest(@Payload String msg) {
        log.info("{}-----HelloReceiver msg : {}",TopicRabbitConfig.QUEUE_NAME+".test",msg);
    }

    /**
     * 这里我的消费者队列名"123445",是乱写的,也能够接受
     * @param msg
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = 123445+"", durable = "true"),
            exchange = @Exchange(value = TopicRabbitConfig.TEST_EXCHANGE_HAHA, type = ExchangeTypes.TOPIC),
            key = TopicRabbitConfig.TEST_TOPIC_ROUTINGKEY)
    )
    public void queueNameNumber(@Payload String msg) {
        log.info("{}-----HelloReceiver msg : {}",123445+""+".test",msg);
    }

    /**
     * 由于这个和上面的Exchange、RoutingKey、queue完全相同,所以这两个消费者,一条消息,只有一个能消费(随机)
     * @param msg
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = 123445+"", durable = "true"),
            exchange = @Exchange(value = TopicRabbitConfig.TEST_EXCHANGE_HAHA, type = ExchangeTypes.TOPIC),
            key = TopicRabbitConfig.TEST_TOPIC_ROUTINGKEY)
    )
    public void queueNameNumberSame(@Payload String msg) {
        log.info("same+{}-----HelloReceiver msg : {}",123445+""+".test",msg);
    }
}

 

 

多个消费端

根据结果可知,当Exchange和RoutingKey相同、queue不同时,所有消费者都能消费同样的信息;

Exchange和RoutingKey、queue都相同时(最后两个消费者),消费者中只有一个能消费信息,其他消费者都不能消费该信息。

 

 

 

springboot amqp  一些配置

 

 

#---------------------服务端配置
spring.rabbitmq.connection-timeout=15000

#开启 confirm 确认机制
spring.rabbitmq.publisher-confirms=true
#开启 return 确认机制
spring.rabbitmq.publisher-returns=true
#设置为 true 后 消费者在消息没有被路由到合适队列情况下会被return监听,而不会自动删除
spring.rabbitmq.template.mandatory=true
#---------------------服务端配置


#---------------------客户端配置
#设置消费端手动 ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#消费者最小数量
spring.rabbitmq.listener.simple.concurrency=1
#消费之最大数量
spring.rabbitmq.listener.simple.max-concurrency=10
#在单个请求中处理的消息个数,他应该大于等于事务数量(unack的最大数量)
spring.rabbitmq.listener.simple.prefetch=2
#---------------------客户端配置

 

 

 

 

#消费者数量
spring.rabbitmq.listener.simple.concurrency=10
#最大消费者数量
spring.rabbitmq.listener.simple.max-concurrency=10
#消费者每次从队列获取的消息数量。写多了,如果长时间得不到消费,数据就一直得不到处理
spring.rabbitmq.listener.simple.prefetch=1
#消费者自动启动
spring.rabbitmq.listener.simple.auto-startup=true
#消费者消费失败,自动重新入队
spring.rabbitmq.listener.simple.default-requeue-rejected=true
#启用发送重试 队列满了发不进去时启动重试
spring.rabbitmq.template.retry.enabled=true 
#1秒钟后重试一次
spring.rabbitmq.template.retry.initial-interval=1000 
#最大重试次数 3次
spring.rabbitmq.template.retry.max-attempts=3
#最大间隔 10秒钟
spring.rabbitmq.template.retry.max-interval=10000
#等待间隔 的倍数。如果为2  第一次 乘以2 等1秒, 第二次 乘以2 等2秒 ,第三次 乘以2 等4秒
spring.rabbitmq.template.retry.multiplier=1.0

 

#消费者数量

spring.rabbitmq.listener.simple.concurrency=10

#最大消费者数量

spring.rabbitmq.listener.simple.max-concurrency=10

#消费者每次从队列获取的消息数量

spring.rabbitmq.listener.simple.prefetch=1

#消费者自动启动

spring.rabbitmq.listener.simple.auto-startup=true

#消费失败,自动重新入队

#重试次数超过最大限制之后是否丢弃(true不丢弃时需要写相应代码将该消息加入死信队列)

#true,自动重新入队,要写相应代码将该消息加入死信队列

#false,丢弃

spring.rabbitmq.listener.simple.default-requeue-rejected=false

#是否开启消费者重试(为false时关闭消费者重试,这时消费端代码异常会一直重复收到消息)

spring.rabbitmq.listener.simple.retry.enabled=true

spring.rabbitmq.listener.simple.retry.initial-interval=1000

spring.rabbitmq.listener.simple.retry.max-attempts=3

spring.rabbitmq.listener.simple.retry.multiplier=1.0

spring.rabbitmq.listener.simple.retry.max-interval=10000

 

 消费端重试

spring.rabbitmq.listener.simple.retry.max-attempts=5  最大重试次数
spring.rabbitmq.listener.simple.retry.enabled=true 是否开启消费者重试(为false时关闭消费者重试,这时消费端代码异常会一直重复收到消息)
spring.rabbitmq.listener.simple.retry.initial-interval=5000 重试间隔时间(单位毫秒)
spring.rabbitmq.listener.simple.default-requeue-rejected=false 重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列)

 

 

 

 

# base
spring.rabbitmq.host: 服务Host
spring.rabbitmq.port: 服务端口
spring.rabbitmq.username: 登陆用户名
spring.rabbitmq.password: 登陆密码
spring.rabbitmq.virtual-host: 连接到rabbitMQ的vhost
spring.rabbitmq.addresses: 指定client连接到的server的地址,多个以逗号分隔(优先取addresses,然后再取host)
spring.rabbitmq.requested-heartbeat: 指定心跳超时,单位秒,0为不指定;默认60s
spring.rabbitmq.publisher-confirms: 是否启用【发布确认】
spring.rabbitmq.publisher-returns: 是否启用【发布返回】
spring.rabbitmq.connection-timeout: 连接超时,单位毫秒,0表示无穷大,不超时
spring.rabbitmq.parsed-addresses:


# ssl
spring.rabbitmq.ssl.enabled: 是否支持ssl
spring.rabbitmq.ssl.key-store: 指定持有SSL certificate的key store的路径
spring.rabbitmq.ssl.key-store-password: 指定访问key store的密码
spring.rabbitmq.ssl.trust-store: 指定持有SSL certificates的Trust store
spring.rabbitmq.ssl.trust-store-password: 指定访问trust store的密码
spring.rabbitmq.ssl.algorithm: ssl使用的算法,例如,TLSv1.1


# cache
spring.rabbitmq.cache.channel.size: 缓存中保持的channel数量
spring.rabbitmq.cache.channel.checkout-timeout: 当缓存数量被设置时,从缓存中获取一个channel的超时时间,单位毫秒;如果为0,则总是创建一个新channel
spring.rabbitmq.cache.connection.size: 缓存的连接数,只有是CONNECTION模式时生效
spring.rabbitmq.cache.connection.mode: 连接工厂缓存模式:CHANNEL 和 CONNECTION


# listener
spring.rabbitmq.listener.simple.auto-startup: 是否启动时自动启动容器
spring.rabbitmq.listener.simple.acknowledge-mode: 表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto
spring.rabbitmq.listener.simple.concurrency: 最小的消费者数量
spring.rabbitmq.listener.simple.max-concurrency: 最大的消费者数量
spring.rabbitmq.listener.simple.prefetch: 指定一个请求能处理多少个消息,如果有事务的话,必须大于等于transaction数量.
spring.rabbitmq.listener.simple.transaction-size: 指定一个事务处理的消息数量,最好是小于等于prefetch的数量.
spring.rabbitmq.listener.simple.default-requeue-rejected: 决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系)
spring.rabbitmq.listener.simple.idle-event-interval: 多少长时间发布空闲容器时间,单位毫秒

spring.rabbitmq.listener.simple.retry.enabled: 监听重试是否可用
spring.rabbitmq.listener.simple.retry.max-attempts: 最大重试次数
spring.rabbitmq.listener.simple.retry.initial-interval: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.listener.simple.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.listener.simple.retry.max-interval: 最大重试时间间隔
spring.rabbitmq.listener.simple.retry.stateless: 重试是有状态or无状态


# template
spring.rabbitmq.template.mandatory: 启用强制信息;默认false
spring.rabbitmq.template.receive-timeout: receive() 操作的超时时间
spring.rabbitmq.template.reply-timeout: sendAndReceive() 操作的超时时间
spring.rabbitmq.template.retry.enabled: 发送重试是否可用
spring.rabbitmq.template.retry.max-attempts: 最大重试次数
spring.rabbitmq.template.retry.initial-interval: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.template.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.template.retry.max-interval: 最大重试时间间隔

 

#开启 confirm 确认机制
spring.rabbitmq.publisher-confirms=true

消息发送到交换器Exchange后触发回调


#开启 return 确认机制
spring.rabbitmq.publisher-returns=true

通过实现ReturnCallback接口,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发)

 

如果消息没有到exchange,则confirm回调,ack=false

如果消息到达exchange,则confirm回调,ack=true

exchange到queue成功,则不回调return

exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)

 

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;

//@Component
public class MQSender {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    final RabbitTemplate.ConfirmCallback confirmCallback= new RabbitTemplate.ConfirmCallback() {

        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            System.out.println("correlationData: " + correlationData);
            System.out.println("ack: " + ack);
            if(!ack){
                System.out.println("异常处理....");
            }
        }

    };

    final RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {

        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
            System.out.println("return exchange: " + exchange + ", routingKey: "
                    + routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText);
        }
    };

    //发送消息方法调用: 构建Message消息
    public void send(Object message, Map<String, Object> properties) throws Exception {
        MessageProperties mp = new MessageProperties();
        //在生产环境中这里不用Message,而是使用 fastJson 等工具将对象转换为 json 格式发送
        Message msg = new Message(message.toString().getBytes(),mp);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(confirmCallback);
        rabbitTemplate.setReturnCallback(returnCallback);
        //id + 时间戳 全局唯一
        CorrelationData correlationData = new CorrelationData("1234567890"+new Date());
        rabbitTemplate.convertAndSend("BOOT-EXCHANGE-1", "boot.save", msg, correlationData);
    }
    
    //发送消息方法调用: 构建Message消息
    public void sendUser() throws Exception {
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(confirmCallback);
        rabbitTemplate.setReturnCallback(returnCallback);
        //id + 时间戳 全局唯一
        CorrelationData correlationData = new CorrelationData("1234567890"+new Date());
        rabbitTemplate.convertAndSend("BOOT-EXCHANGE-1", "boot.save", "发送消息", correlationData);
    }
}

 

 

https://www.cnblogs.com/tinyj/p/9977205.html

 

posted @ 2020-09-02 19:05  jentary  阅读(264)  评论(0编辑  收藏  举报