rabbitmq介绍

本文详细介绍了如何使用SpringAMQP框架在Java中实现消息中间件,包括生产者、服务端(消费者)和队列之间的交互,以及不同消息模式(如工作模式、广播、定向、主题和延迟)的配置和使用方法,还探讨了确保消息不丢失和防止重复消费的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2.框架结构

消息中间件组要有三部分组成:

1.生产者,生产消息发送给服务端

2.服务端,接收消息并通过配置规则发送给指定消费者,或者等消费者自己取。(即推和拉模式)

3.消费者,消费从队列过来的消息

注意:以上连线关系都是多对多的关系

3.功能及使用方法

主要介绍使用springmaqp框架来接入java中的使用

3.1引入springmaqp

使用文档地址:

Spring AMQP

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>
#rabbitmq
spring.rabbitmq.template.mandatory=true
spring.rabbitmq.host=49.235.142.29
spring.rabbitmq.port=5672
spring.rabbitmq.username=ajian
spring.rabbitmq.password=Unicom369
spring.rabbitmq.virtual-host=/sh
#消费完一条再取
spring.rabbitmq.listener.simple.prefetch=1

3.2创建交换机队列及其订阅关系

3.2.1代码配置
@Configuration
public class RabbitMqConfig {

    //配置交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("ajian.fanout");
    }
    //队列
    @Bean
    public Queue fanoutQueue(){
        return new Queue("ajian.queue");
    }
    //交换机和队列关系
    @Bean
    public Binding fanoutBinding(){
        return BindingBuilder.bind(fanoutQueue()).to(fanoutExchange());
    }
}
3.2.2注解配置
 @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ajian.queue"),
            exchange = @Exchange(name = "ajian.fanout",type = ExchangeTypes.FANOUT),
            key = {"red","bule"}
    ))
    public void getMessage2(String content){
        System.out.println("1接收到消息:"+content);
    }

3.3简单消息发送

 @Autowired
    private RabbitTemplate rabbitTemplate;


    public void send() {

        String name = "shtest1";
        for(int i=1;i<30;i++){
            String context = "hello world"+i;
            this.rabbitTemplate.convertAndSend(name, context);
        }

    }

  @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "shtest1")
    ))
    public void getMessage1(String content){
        System.out.println("1接收到消息:"+content);
    }

上代码是简单的从生产者——队列——消费者这过程,未经过交换机分配

注意:当发送map等实体时如不处理他会自动转为字节流传到队列中,这就可能导致消费失败,由于系列化方式不一样导致的,而且字节流占的空间也更大。

未处理时

转换方法:

  <dependencies>
            <dependency>
                <groupId>com.fasterxml.jackson.dataformat</groupId>
                <artifactId>jackson-dataformat-xml</artifactId>
            </dependency>
  </dependencies>

实例化转换器

    @Bean
    public MessageConverter JacksonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

其他代码不用做改变,因为发消息底层转换消息有该方法的使用,只要引入实现,就会在转换时自动使用该消息转换功能

3.4五种消息发送处理方式

3.4.1work工作模式

如图

即由多个消费者消费一个队列中的消息,适合一个消费者无法跟上生产者产生消息速度的情况下,实现方式和简单消息时相同,但消费者是多个。

注意:使用该模式时如果不配置的话默认是消费者轮询消费的,这就会出现性能好的消费者没完全利用

可在配置中配上属性,该值代表取几条消息并当这几条消息消费完成后再进行消费

spring.rabbitmq.listener.simple.prefetch=1
3.4.2Fanout广播模式

如图:

见名知意,就是创建广播交换机,拿到消息后会分发给每一台他绑定的队列

实现

//配置队列交换机关系
  //配置交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("ajian.fanout");
    }
    //队列
    @Bean
    public Queue fanoutQueue(){
        return new Queue("ajian.queue");
    }
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("ajian.queue1");
    }
    //交换机和队列关系
    @Bean
    public Binding fanoutBinding(){
        return BindingBuilder.bind(fanoutQueue()).to(fanoutExchange());
    }
    @Bean
    public Binding fanoutBinding1(){
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
    }



//生产者
   public void send1() {
        String exchange = "ajian.fanout";
        String context = "hello world";
        this.rabbitTemplate.convertAndSend(exchange,null,context);
    }


//消费者
    @RabbitListener(queues = "ajian.queue1")
    public void getMessage(String content){
        System.out.println("接收到消息:"+content);
    }

    @RabbitListener(queues = "ajian.queue" )
    public void getMessage1(String content){
        System.out.println("1接收到消息:"+content);
    }
3.4.3Direct定向模式

如图:

生产者发消息时添加一个key标识如red,交换机合队列绑定也添加一个key,当消息key与队列的key相同时就传入该队列进行消费

该模式为指定发送到那个队列,实现方式如下

//消费者创建定向交换机并绑定对应的队列
@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "queue"),
            exchange = @Exchange(name = "exchange",type = ExchangeTypes.DIRECT),
            key = {"red","bule"}
    ))
    public void getMessage2(String content){
        System.out.println("1接收到消息:"+content);
    }
@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "queue1"),
            exchange = @Exchange(name = "exchange",type = ExchangeTypes.DIRECT),
            key = {"red","bul"}
    ))
    public void getMessage1(String content){
        System.out.println("1接收到消息:"+content);
    }

//生产者
public void send1() {
        String exchange = "shtest1";
        String context = "hello world";
        this.rabbitTemplate.convertAndSend(exchange,"bul",context);
    }
3.4.4Topic话题模式

如图

该模式是定向模式的升级版,把key的唯一匹配改成了相当于有规则的模糊匹配,如图中队列queue1的key=china.#,则发消息的key=china.news/china.weather/china.nanchang.news等都会传到该队列中

使用方法:

//配置topic交换机和key关系,消费消息
@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topicQueue"),
            exchange = @Exchange(name = "topicExchange",type = ExchangeTypes.TOPIC),
            key = "china.#"
    ))
    public void topicMessage(String content){
        System.out.println("1接收到消息:"+content);
    }


//生产消息
    public void send1() {
        String exchange = "topicExchange";
        String context = "hello world";
        this.rabbitTemplate.convertAndSend(exchange,"china.news",context);
    }
3.4.5延迟消息模式
1.前置

死信是什么:

队列中消息消费失败,且没配置其他消费方式,即消息被放弃了

消息是过期消息,没人消费

队列消息堆积满了,最早的消息

死信交换机:指专门用来处理死信消息的交换机

2.延迟消息的死信实现

如图队列一中消息没人消费,设置队列死信交换机属性,并设置过期时间,当队列1中的消息过期后变为死信,就会转发到死信交换机中,死信交换机收到消息把消息转发到队列中再进行消费。

使用场景:如用户下订单,订单消息进入队列1中,当用户30分钟内还不支付时,该信息变为死信,转发到死信交换机,再发到队列中,消费者进行取消订单操作。

使用

//按图配好交换机和队列,在队列1上如上图关联上死信交换机
//消费者
    @RabbitListener(queues = "dlx.direct")
    public void getMessage(String content){
        System.out.println("接收到消息:"+content);
    }

//生产者
    public void send2() {
        String exchange = "shtest1";
        String context = "hello world";
        this.rabbitTemplate.convertAndSend(exchange, "hi","hello world" ,new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //设置过期时间
                message.getMessageProperties().setExpiration("1000");
                return message;
            }
        });
    }

3.插件实现

实现原理是创建一个能暂存数据的延时交换机,在消息没过期时保持在交换机中,超时后再发送到队列中消费

下载插件放到rabbitmq目录下

//durable = "true"表消息持久化。delayed = "true"表创建延时交换机
@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "",durable = "true"),
            exchange = @Exchange(name = "",type = ExchangeTypes.DIRECT ,delayed = "true"),
            key = "delayed"
    ))
    public void getMessage3(String content){
        System.out.println("1接收到消息:"+content);
    }

//生产者
 public void send3() {
        String exchange = "shtest1";
        String context = "hello world";
        this.rabbitTemplate.convertAndSend(exchange, "hi","hello world" ,new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //设置延迟时间
                message.getMessageProperties().setDelay(1000);
                return message;
            }
        });
    }

3.5如何确保消息不丢失

3.5.1生产者
1.网络波动没连上mq服务

采用重连方式

#生产者配置
#rabbitmq
spring.rabbitmq.template.mandatory=true
spring.rabbitmq.host=49.235.142.29
spring.rabbitmq.port=5672
spring.rabbitmq.username=ajian
spring.rabbitmq.password=Unicom369
spring.rabbitmq.virtual-host=/sh
#连接超时时间
spring.rabbitmq.connection-timeout=1s
#开启重试
spring.rabbitmq.retry.enabled=true
#失败后初始等待时间
spring.rabbitmq.retry.initial-interval=1000ms
#失败后下次等待时长倍数
spring.rabbitmq.retry.multiplier=1
#最大重试次数
spring.rabbitmq.retry.max-attempts=3
2.生产者确认机制

持久化队列,建队列时durable = "true"表消息持久化

添加配置

#开启publisher-confirm并设置类型correlated异步回掉
spring.rabbitmq.publisher-confirm-type=correlated
#开启publisher-returns机制
spring.rabbitmq.publisher-returns=true

发送代码

@Configuration
//实例化容器后调用实现了通知接口的类,把上线文传过来做处理,统一指定
public class CommonConfig implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            System.out.println("失败消息:"+message);
        });
    }
}



//发送代码
 public void send4() {
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
            @Override
            public void onFailure(Throwable throwable) {
                System.out.println("消息回调失败"+throwable);
            }

            @Override
            public void onSuccess(CorrelationData.Confirm confirm) {
                if(confirm.isAck()){
                    System.out.println("成功收到ACK");
                }else{
                    System.out.println("失败收到NACK");
                }

            }
        });
        String exchange = "shtest1";
        this.rabbitTemplate.convertAndSend(exchange, "hi","hello world" ,correlationData);
    }
3.5.2服务端
1.持久化

即配置持久属性,队列持久,发的消息持久

2.Lazy Queue(3.12版以后都是这种方式)

消息放入磁盘中,内存放一部分。

@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "",durable = "true",arguments = @Argument(name="x-queue-mode",value = "lazy")),
            exchange = @Exchange(name = "",type = ExchangeTypes.DIRECT ,delayed = "true"),
            key = "delayed"
    ))
    public void getMessage3(String content){
        System.out.println("1接收到消息:"+content);
    }

3.5.3消费者
1.消费者信息确认机制

与生产者确认机制相同:ack (服务端收到直接删除)。nack(重投) 。rejeck(失败删除)

配置

#消息消费确认回掉功能设置 auto自动调 none不调 手动调manual
spring.rabbitmq.listener.simple.acknowledge-mode=auto


#开启重试消息本地重试
spring.rabbitmq.listener.simple.retry.enabled=true
#失败后初始等待时间
spring.rabbitmq.listener.simple.retry.initial-interval=1000ms
#失败后下次等待时长倍数
spring.rabbitmq.listener.simple.retry.multiplier=1
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=3
#事物消息false表有状态
spring.rabbitmq.listener.simple.retry.stateless=true

新建错误信息队列交换机

@Configuration
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry",name = "enabled",havingValue = "true")
public class RabbitMqConfig {

    //配置交换机
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("ajian.fanout");
    }
    //队列
    @Bean
    public Queue fanoutQueue(){
        return new Queue("ajian.queue");
    }
    //交换机和队列关系
    @Bean
    public Binding fanoutBinding(Queue fanoutQueue,DirectExchange directExchange){
        return BindingBuilder.bind(fanoutQueue).to(directExchange).with("error");
    }
    
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate,"ajian.fanout","error");
    }
}

该配置执行方式是消费者消费消费时,出错本地重新执行配置的次数,还是失败的话会把消息传到上面配置的错误信息队列中,通知开发人员关注错误信息。

3.6如何防止消息被重复消费

幂等性

一个id放redis中,消费了就删除,即不存在就不能消费

生产一个id,消费了就存起来,存在id即为已经消耗了

延迟计时的实现是每条消息都维护了一个时钟,与radis实现的区别

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值