RabbitMQ

目录

安装

卸载

启动RabbitMQ容器

RabbitMQ分为三种:

        1.Direct Exchange

        2.Fanout Exchange

         3.Topic Exchange

 idea使用RabbitMQ

Direct Exchange        直流式交换机

 多个消费者消耗同一条队列

 多个队列连接同一个交换机

队列传递对象

消费者应答模式

Fanout Exchange 扇形交换机

 Topic Exchange  主题交换机

死信队列 

延迟队列


安装

docker下载指令两种方式,建议第一种

 docker run -d --restart=always --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.10-management
docker run -it  --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.10-management

 下载后直接在网址输入ip端口:15672就能进去管理界面如果进不去可以尝试关闭防火墙,指令为

systemctl stop firewalld

查看防火强状态

systemctl status firewalld

查看所有下载好的指令

docker ps -a

查看下载好的镜像

docker images

 查IP端口有没有被占用指令为

netsta -anp |grep 5672

卸载

删除下载的MQ

 先查看所有下载好的指令,删除关于mq的文件指令为

docker rm -f 需要卸载的CONTAINER ID 

查看下载好的镜像,删除关于mq的镜像

docker rmi -f  需要删除的IMAGE ID 

启动RabbitMQ容器

 关闭容器

docker stop 容器名

 打开容器

docker start  容器名

查看是否能访问容器

cmd里面输入

telnet ip地址 

RabbitMQ分为三种:

        1.Direct Exchange

                直流式交换机,根据消息携带的路由键将消息投递给对应队列

        2.Fanout Exchange

                扇形交换机,这个交换机没有路由概念,就算绑了路由也是无视的(没有routingket)。这个交换机再接收到消息后会直接转发到绑定到他的所有队列

         3.Topic Exchange

                主题交换机,这个交换机其实跟直连交换机流程差不多。一般来说都是用主题交换机,因为上面两个交换机能做的主题交换机都能做。但是他的特点就是在他的路由键与绑定键之间是有规则的。

规则:

* (星号) 用来表示一个单词 (必须出现的)

# (井号) 用来表示任意数量(零个或多个)单词

 * 代表两点之间一个占位单词

 # 代表后面所有,匹配所有 


 idea使用RabbitMQ

MQ依赖

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

创建springboot项目,创建子项目,输入配置信息


#??RabbitMQ?IP??
spring.rabbitmq.host=你的IP地址
#??rabbitmq???????
spring.rabbitmq.port=5672
#??rabbitmq???????
spring.rabbitmq.virtual-host=/
#??rabbitmq??????
spring.rabbitmq.username=guest
#??rabbitmq?????
spring.rabbitmq.password=guest

spring.rabbitmq.listener.simple.prefetch=1
#手动应答
#spring.rabbitmq.listener.simple.acknowledge-mode=manual
#自动应答             z
spring.rabbitmq.listener.simple.acknowledge-mode=auto
# 开启自动重试
spring.rabbitmq.listener.simple.retry.enabled=true
# 最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#最大间隔时间
spring.rabbitmq.listener.simple.retry.max-interval=20000ms
#重试间隔时间 3秒
spring.rabbitmq.listener.simple.retry.initial-interval=3000ms
#乘子 重试间隔*乘子得出下次重试间隔 3s 6s 12s 24s 此处24s>20s 走20s
spring.rabbitmq.listener.simple.retry.multiplier=2
#重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列)
spring.rabbitmq.listener.simple.default-requeue-rejected=false

Direct Exchange        直流式交换机

需要路由键——routingkey

创建配置类:在配置类写交换机的配置,队列的配置,绑定信息

@Bean
public DirectExchange directExchange(){
    return new DirectExchange("DirectExchange-01",true,false) ;

}

队列的配置

   @Bean
    public Queue DirectQueue(){
        return QueueBuilder.durable("DirectQueue-01").build();
//        自动删除
//        return QueueBuilder.durable("DirectQueue-01").autoDelete().build();

    }

绑定信息

@Bean
public  Binding binding(){
   return BindingBuilder.bind(DirectQueue()).to(directExchange()).with("Direct-RoutingKey-01");
}

如果仅仅是写好配置信息却没有调用的话,是不会往队列里发送信息的,这时候我们就要调用我们写好的配置,首先我们创建消费者加入注解 @RabbitHandler这要知道springMVC的执行流程。了解过后知道Handler是执行类。加入注解@RabbitListener用来监听我们想监听的地方,这里我们要监听的是我们配置类中创建的队列。这时候如果我们运行的话就会向队列里发出请求。队列也可以接收到请求。

消费者包,consumer

建立一个DirectConsumer

@Component
public class DirectConsumer {
    @RabbitHandler
    @RabbitListener(queues = "DirectQueue-01")
    public void process(Message message) {
        String test = new String(message.getBody());
        System.out.println(test);
    }

}
@RabbitHandlerhandler类用来执行的
@RabbitListener用来监听队列的

 这是用来消费队列数据的

还有一种写法是不需要配置类config,直接在消费类上面通过注解来进行配置信息

@Component 
public class DirectConsumer {
@RabbitHandler
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "DirectQueue-01", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "DirectExchange-01", type = ExchangeTypes.DIRECT),
key = "Direct-RoutingKey-01"))
public void process(Message message) { 
String test = new String(message.getBody()); 
System.out.println(test); 
}
 }

生产者包provider

建立一个DirectProvider

@Component
public class DirectProvider {
    @Resource
    private RabbitTemplate rabbitTemplate;

    public void send(){
        Message message=new Message("你好 ".getBytes());
        rabbitTemplate.send("DirectExchange-01","Direct-RoutingKey-01",message);
    }
}

 多个消费者消耗同一条队列

 我们设置一个生产者生产十个消息

@Component
public class DirectProvider {
    @Resource
    private RabbitTemplate rabbitTemplate;
    public void sendLong() {
        for (int i = 0; i < 10; i++) {
            Message message = new Message("消耗".getBytes());
            rabbitTemplate.send("Exchange01","RoutingKey01",message);
        }
        }

设置两个消费者进行消费,消费者对于这十个消息是如何分配的?

@Component
public class DirectConsumer {


    @RabbitHandler
    @RabbitListener(queues = "Queue01")
    public void consumer1(Message message) {
        String password = new String(message.getBody());
        System.out.println("消费者1号"+ password);
    }
    @RabbitHandler
    @RabbitListener(queues = "Queue01")
    public void consumer2(Message message) {
        String password = new String(message.getBody());
        System.out.println("消费者2号"+ password);
    }

可以看出来,对于这十个消息,两个消费者是进行平均分配的,一人一个,一人一个的循环着。

不过一般都是创建一个消费者进行打包,如果需要第二个消费者,则将打包出来的消费者再发出去一份就变成了两个消费者

 多个队列连接同一个交换机

        多个队列可以绑定在一个交换机上面

队列传递对象

        创建一个实体类

        

@Data
public class OrderIngOK implements Serializable {
    private Integer id;
    private String OrderNo;
    private String userName;
}

要继承这个接口不然在传递对象的时候就会报错,显示只能传递Serializable 类的对象 

        Serializable 

 生产者传递对象

@Component
public class DirectProvider {
    @Resource
    private RabbitTemplate rabbitTemplate;
    public void sendLong() {
        OrderIngOK orderIngOK = new OrderIngOK();
            orderIngOK.setOrderNo("202025077101" );
            orderIngOK.setId(1);
            orderIngOK.setUserName("买核弹的小姑娘");
            rabbitTemplate.convertAndSend("Exchange01","RoutingKey01",orderIngOK);
    }
send传递参数
convertAndSend传递对象用

 传递的对象到达MQ后的样子是base64的样子

我们要做的就是将bsae64字节码转换为utf-8,在配置类里面添加一个配置

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

但是在正常开发中不可能我们让每个人都把json数据转换好发送,所以我们需要一种让他自动转换为对象 ,所以我们在消费者的参数里面加入实体类的形参就可以了。

消费者应答模式

配置里面有对于应答方式的两种选择,一种为自动应答,一种为手动应答

#手动应答
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#自动应答
spring.rabbitmq.listener.simple.acknowledge-mode=auto

当改成手动应答后MQ就会显示接受未应答消息。

 当我们设置为手动应答后,程序出现错误时会调用自动重试的配置

# 开启自动重试
spring.rabbitmq.listener.simple.retry.enabled=true
# 最大重试次数  遇到异常重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#最大间隔时间
spring.rabbitmq.listener.simple.retry.max-interval=30000ms
#重试间隔时间 3秒
spring.rabbitmq.listener.simple.retry.initial-interval=1000ms
#乘子 重试间隔*乘子得出下次重试间隔 3s 6s 12s 24s 此处24s>20s 走20s
spring.rabbitmq.listener.simple.retry.multiplier=1

 配置中有一个最大重试次数,我在配置的时候配成5,所以会在自动重连五次后取消自动重连,后面的是自动重连的间隔实践。有一个最大的间隔时间,就是当每次重连的时间是

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

 的次方

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

 上面的意思是,如果第一条为2秒的时候,下面为5,那么第一次重连是2秒,第二次重连就是间隔4秒,依次类推到达最大的32秒,但是

spring.rabbitmq.listener.simple.retry.max-interval=30000ms

 这个是最大间隔时间,就是如果最大的间隔时间大于这个间隔时间的时候就会断开,不进行重试,比如最大30秒,五次间隔32秒,那么这次的间隔时间为30秒。

还有就是如果手动应答,超过应答次数后,这条数据就会直接被丢掉,这是不允许的,所以一般不建议使用手动应答,除非有补救措施,但是补救措施也不是万能的,还是存在隐患,所以不建议使用手动应答。不过对于丢掉数据这个有死信队列可以解决,后面又详细介绍

#手动应答

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

一旦使用手动应答,下面的一系列的配置都会失效  

Channel

         导包的时候,要看清楚是这个包,不要导错了。

import com.rabbitmq.client.Channel;

        channel.后能出来目前会使用的,一个是basicAck接受应答,一个是basicReject 拒绝应答,一个是baseNack

basicAck使用是手动应答的方式,关于它的源码

/**
 * Acknowledge one or several received
 * messages. Supply the deliveryTag from the {@link com.rabbitmq.client.AMQP.Basic.GetOk}
 * or {@link com.rabbitmq.client.AMQP.Basic.Deliver} method
 * containing the received message being acknowledged.
 * @see com.rabbitmq.client.AMQP.Basic.Ack
 * @param deliveryTag the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
 * @param multiple true to acknowledge all messages up to and
 * including the supplied delivery tag; false to acknowledge just
 * the supplied delivery tag.
 * @throws java.io.IOException if an error is encountered
 */
void basicAck(long deliveryTag, boolean multiple) throws IOException;

翻译过来的意思就是

 /**
             * 确认应答
             * basicAck(long deliveryTag, boolean multiple)
             * deliveryTag:当前消息在队列中的的索引;
             * multiple:为true的话就是批量确认
             */
    void basicAck(long deliveryTag, boolean multiple) throws IOException;
deliveryTag当前消息在队列中的的索引
multiple是否按照批处理,如果是true就会收集,收集到一定的数量后整体应答,如果是false就是一个一个应答,处理一个解决一个。在工作中,我们的准则就是能不产生这个数据也不要产生一个错误的数据。所以一般我们采用的是flase

使用的方式,在消费的时候加入一个参数为Channel

    @RabbitHandler
    @RabbitListener(queues = RabbitConfig.QUEUE_LONG)
    public void consumer(OrderIngOK orderIngOK,Message message,Channel channel) throws IOException {
        System.out.println("消费者龙号" + orderIngOK);
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        channel.basicAck(deliveryTag,false);

    }

basicReject 是拒绝应答,配合basicAck使用,使用场景是,当数据发生错误的时候,拒绝应答,这样的话数据还在队列里面

@RabbitHandler
@RabbitListener(queues = RabbitConfig.QUEUE_LONG)
public void consumer(OrderIngOK orderIngOK, Message message, Channel channel) throws IOException {
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    try {
           int a=0;
           int b=9/a;
        System.out.println("消费者龙号" + orderIngOK);
        channel.basicAck(deliveryTag, false);

    } catch (Exception ex) {
        channel.basicReject(deliveryTag, false);
        System.out.println("出现异常");
        System.out.println(ex.getMessage());
    }
}

注意:

        如果拒绝应答的是否批处理是true的话就会一直进行重复的验证是否错误,或者是否应答,如果处理不当的话就会造成光日志打印的超乎你想象的事情

      如果拒绝应答的是否批处理是false的时候,会将数据直接丢掉,就会造成数据丢失的情况。


Fanout Exchange 扇形交换机

与直流交换机一样,唯一不同的就是在使用绑定的时候,roudingkey是null 

 @Bean
public Binding binding2(){

     return  BindingBuilder.bind("fanoutqueue").to("fanoutexchange").with("null");
 }

 Topic Exchange  主题交换机

 与直流交换机一样,唯一不同的就是在使用绑定的时候,roudingkey是是规则,是自己想要的东西

@Bean
public Binding binding1() {
    return BindingBuilder.bind(hedan()).to(topicExchange()).with("T.1.#");
}
@Bean
public Binding binding2() {
    return BindingBuilder.bind(feiji()).to(topicExchange()).with("T.#");
}
@Bean
public Binding binding3() {
    return BindingBuilder.bind(yuandidan()).to(topicExchange()).with("T.*");
}

死信队列 

        死信队列其实就是一种单门的队列,是我们自己根据需要添加进去的。也可以叫做超时信息处理。比如我们在上面举的列子,当有消息出现异常的时候,我们的程序会自动进行重试,连续五次重试后,消息直接就被丢掉了。当手动的时候,就不会触发自动重试机制,会一直进行重试,或者直接将消息丢掉。对于这种消息我们就可以将他们放到死信队列里面。这是一种解决方法,我们还可以将他们放到redis里面或者暂时贮存在我们的数据库里面。

我们建立一个死信队列的配置

@Configuration
public class DeadLetterConfig {

    public static final String DEAD_QUEUE_NAME = "Dead-死信队列";
    public static final String DEAD_EXCHANGE_NAME = "Dead-Exchange";
    public static final String DEAD_ROUTING_KEY = "Dead-Routing-Key";

    @Bean
    public Queue deadQueue() {
        return QueueBuilder.durable(DEAD_QUEUE_NAME).build();
    }

    @Bean
    public DirectExchange deadExchange() {
        return new DirectExchange(DEAD_EXCHANGE_NAME);
    }

    @Bean
    public Binding deadBind() {

        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(DEAD_ROUTING_KEY);
    }
}

 在消费者前面进行加入死信队列的操作

long deliveryTag = message.getMessageProperties().getDeliveryTag();

延迟队列

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值