rabbitMQ

安装

docker

rabbitmq_delayed_message_exchange插件需要从外部安装,github地址

github地址 :https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/tag/3.9.0

  • Dockerfile
FROM rabbitmq:3.8.11
ADD ["rabbitmq_delayed_message_exchange-3.9.0.ez","/plugins/"]
RUN ["ls","/plugins"]
RUN ["rabbitmq-plugins","enable","rabbitmq_management"]
RUN ["rabbitmq-plugins","enable","rabbitmq_delayed_message_exchange"]
# docker run 的时候暴露两个端口 15762 5762 
# 添加用户 root root
rabbitmqctl add_user root root
# 为用户添加权限
rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
rabbitmqctl set_user_tags root administrator

#开通防火墙端口
firewall-cmd --zone=public --add-port=15672/tcp --permanent
firewall-cmd --zone=public --add-port=4369/tcp --permanent
firewall-cmd --zone=public --add-port=5672/tcp --permanent
firewall-cmd --zone=public --add-port=25672/tcp --permanent

概念

在这里插入图片描述
broker: 接收和分发消息的应用类似server
channel:每次访问时会建立一个连接(connection),一个连接会有多个信道(channel)
virtualhost: 小型的server 类似于mysql中有多个数据库

测试

  • 工具类,主要提供channel
public class MQUtil {
//    获取信道
    public static Channel getChannel() throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("43.143.132.3");
        factory.setUsername("root");
        factory.setPassword("root");
        Connection connection = factory.newConnection();
        return connection.createChannel();
    }
}
  • 生产者
public class Producer implements Callable<String> {
    String QUEUE_NAME;
    Channel channel;
    Producer(String QUEUE_NAME, Channel channel){
        this.QUEUE_NAME = QUEUE_NAME;
        this.channel = channel;
    }

    @Override
    public String call() throws Exception {
        String msg;
        do {
            msg = WorkTest.buffer();
            if(Integer.parseInt(msg) > 10) break;
            System.out.println(Thread.currentThread().getName()+":"+new String(msg));
//        队列名称,是否持久化,是否可以被多个消费者消费,是都断开连接,队列中的消息删除
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//        发送到哪个交换机,路由的key值,本次是队列的名称,其他参数信息,发送消息的消息体(二进制)
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        } while (true);

        return null;
    }
}
  • 消费者
public class Consumer implements Callable<String> {
    String QUEUE_NAME;
    Channel channel;
    Consumer(String QUEUE_NAME,Channel channel){
        this.QUEUE_NAME = QUEUE_NAME;
        this.channel = channel;
    }
	@Override
	public String call() throws Exception{
        System.out.println(Thread.currentThread().getName());
        DeliverCallback deliverCallback = (consumerTag,message)->{
            System.out.println(Thread.currentThread().getName()+":"+new String(message.getBody()));
        };
        CancelCallback cancelCallback = (consumerTag)-> System.out.println("消息被取消");
//        队列名,成功后是否自动应答(推荐false),消费成功的回调,消费未成功的回调
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
        return null;
    }
}
  • 测试
public class WorkTest {
    static int msg = 0;
    public static void main(String[] args) throws Exception{
        String QUEUE_NAME = "demo2";
        Channel channel = MQUtil.getChannel();
        for (int i = 0; i < 3; i++) {
            System.out.println("----开始----");
            FutureTask<String> ProductTask = new FutureTask<>(new Producer(QUEUE_NAME,channel));
            FutureTask<String> ConsumerTask = new FutureTask<>(new Consumer(QUEUE_NAME,channel));
            Thread p = new Thread(ProductTask,"producer"+i);
            Thread c = new Thread(ConsumerTask, "consumer" + i);
            p.start();
            c.start();
        }
    }
    public static synchronized String buffer(){//    线程共享
        msg++;
        return ""+msg;
    }
}

消息应答

当消费者处理完消息之后,要向队列发送应答,自动应答是接收完消息之后就会应答,这里尝试手动应答

      DeliverCallback deliverCallback = (consumerTag,message)->{
                    System.out.println(Thread.currentThread().getName()+":"+new String(message.getBody()));
        //            对哪一个消息进行应答,是否进行批量应答
                    channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
                };

持久化

对生产者进行修改

  • 队列持久化
 # 第二参数改为true
 channel.queueDeclare(QUEUE_NAME, true, false, false, null);

之前声明的非持久化队列不能改,要新建队列

  • 消息持久化
# 修改props为 MessageProperties.PERSISTENT_TEXT_PLAIN
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());

分发策略

默认为轮询,不公平分发就是能者多劳

  • 不公平
int prefetchCount = 0;
channel.basicQos(prefetchCount);//0公平分发 1不公平分发
  • 预期值
    设置 prefetchCount ,预期值此消费者即为最大值,如果消息不够预期值,则分发取决于处理效率

发布确认

确保消息不会丢失
就算 队列持久化,消息持久化,等到保存到磁盘上之后,收到确认才会确保消息不会丢失

  • 开启发布确认
channel.confirmSelect();

每条要确认还是一次确认很多条呢

单个确认(同步)

每次发送的时候发布确认 ` channel.waitForConfirms();

批量发布确认

在发送多个消息后,确认一次

异步确认发布

发布单个确认,但是异步,而不是每次发一条数据都要等待确认(同步)

  • 使用
  1. 开启发布确认
  2. 设置针对发布成功和发布失败的回调函数
  3. 创建确认监听器

注意不要每次创建队列!!!

  public static void main(String[] args) throws Exception {
        String QUEUE_NAME = "c4";
        Channel channel = MQUtil.getChannel();
//        1.开启发布确认
        channel.confirmSelect();

        long begin = System.currentTimeMillis();

//        2.异步确认监听
//        成功回调函数  tag:消息的编号  multiple:是否批量确认
        ConfirmCallback ackCallback = (tag,multiple)->{};
//        失败回调函数
        ConfirmCallback nackCallback = (tag,multiple)->{};
//        3.创建监听器
        channel.addConfirmListener(ackCallback,nackCallback);
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        for (int i = 0; i < 1000; i++) {
            String msg = i+"";
            System.out.println(msg);
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        }
        long end = System.currentTimeMillis();
        System.out.println(end-begin);
    }

交换机

生产者会将消息发送到交换机,交换机再转发给队列。

  • 临时队列 :不能持久化,没有具体名字,断开连接后自动删除
    创建 channel.queueDeclare().getQueue();这个方法返回随机分配的队列名

  • 绑定:交换机和队列的绑定,交换器就可以转发到绑定队列

  • 创建交换机 channel.exchangeDeclare(EXCHANGE_NAME, "类型");// 名字 类型

三种订阅模式

  • fanout
    广播到所有队列
  • direct
    可以选择发送给哪一个队列,绑定需要设置一个rountingKey 生产需要一个rountingKey参数,这样消息就会发送到对应的queueu中
  • topic
    类似direct,但可以多播,解决了direct只能单播的问题
    • key规则
      . 连接多个key
      * 代替一个字符
      #代替0个或者多个字符(如果一个队列的绑定键为#,它将接收所有的消息)

通配符用于队列绑定时,也就是绑定key匹配发送消息的key

死信队列

在这里插入图片描述
死信需要的额外步骤

  1. 队列中传props要包含死信交换机名字(声明死信交换机)
  2. 队列中传props要包含死信交换机和死信队列绑定的key
  • consumer1
    dead_e 和 dead_q绑定
    norm_e norm_q 绑定
    新建交换机和队列
    配置死信key
    声明死信交换机
public class c1 {
    public static void main(String[] args) throws Exception {
//        初始化
        String NORMAL_EXCHANGE = "normal_exchange";
        String NORMAL_QUEUE = "normal_queue";
        String NORMAL_KEY = "normal_key";
        String DEAD_EXCHANGE = "dead_exchange";
        String DEAD_QUEUE = "dead_queue";
        String DEAD_KEY = "dead_key";
//        配置props
        Map<String,Object> props = new HashMap<>();
        props.put("x-dead-letter-exchange",DEAD_EXCHANGE);//声明死信交换机`
        props.put("x-dead-letter-routing-key",DEAD_KEY); //设置死信key[死信交换机转发到死信队列的key]
        Channel channel = MQUtil.getChannel();
//        配置交换机和队列
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.queueDeclare(NORMAL_QUEUE,false,false,false,props);
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,NORMAL_KEY);//正常队列和正常交换机绑定
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,DEAD_KEY);//死信队列和死信交换机绑定
        System.out.println("创建完毕");
        MQUtil.simpleConsumer(channel,NORMAL_QUEUE);
    }
}
  • consumer2
    正常接收死信消息
MQUtil.simpleConsumer(MQUtil.getChannel(),DEAD_QUEUE);
  • producer
    设置消息过期时间,如果消息过期将被设置为死信
  Channel channel = MQUtil.getChannel();
//        设置ttl时间,将消息变为死信
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("5000").build();
        for (int i = 0; i < 10; i++) {
            String msg = i+"";
            channel.basicPublish(NORMAL_EXCHANGE,NORMAL_KEY,properties,msg.getBytes());
        }

springboot 整合

  • 配置文件:创建交换机,队列,绑定交换机和队列
  • 生产者:只负责发送消息到交换机
  • 消费者:只负责从队列中接收消息
配置文件
  //   创建交换机交换机
    @Bean("X")
    public DirectExchange XExchange() {
        return new DirectExchange(X_EXCHANGE);
    }
  //  创建队列
    @Bean("dead_q")
    public Queue DeadQueue() {
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }

//    绑定
    @Bean
    public Binding XA(@Qualifier("X") DirectExchange exchange,@Qualifier("A") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with(A_KEY);
    }

一个配置文件的实例

@Configuration
public class conf01 {
    //    交换机配置
    public static final String X_EXCHANGE = "X";
    public static final String DEAD_EXCHANGE = "dead_exchange";
    public static final String A_QUEUE = "A";
    public static final String B_QUEUE = "B";
    public static final String DEAD_QUEUE = "dead_queue";
    public static final String DEAD_KAY = "dead_key";
    public static final String A_KEY = "A";
    public static final String B_KEY = "B";

    //    交换机
    @Bean("X")
    public DirectExchange XExchange() {
        return new DirectExchange(X_EXCHANGE);
    }

    @Bean("dead_e")
    public DirectExchange DeadExchange() {
        return new DirectExchange(DEAD_EXCHANGE);
    }

//    队列
    @Bean("dead_q")
    public Queue DeadQueue() {
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }

    @Bean("A")
    public Queue AQueue() {
        Map<String, Object> props = new HashMap<>();
        props.put("x-dead-letter-exchange", DEAD_EXCHANGE);//设置死信交换机
        props.put("x-dead-letter-routing-key", DEAD_KAY);//设置死信key
        props.put("x-message-ttl", 20000);
        return QueueBuilder.durable(A_QUEUE).withArguments(props).build();
    }

    @Bean("B")
    public Queue BQueue() {
        Map<String, Object> props = new HashMap<>();
        props.put("x-dead-letter-exchange", DEAD_EXCHANGE);//设置死信交换机
        props.put("x-dead-letter-routing-key", DEAD_KAY);//设置死信key
        props.put("x-message-ttl", 1000);//设置有效时间
        return QueueBuilder.durable(B_QUEUE).withArguments(props).build();
    }
//    绑定
    @Bean
    public Binding XA(@Qualifier("X") DirectExchange exchange,@Qualifier("A") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with(A_KEY);
    }
    @Bean
    public Binding XB(@Qualifier("X") DirectExchange exchange,@Qualifier("B") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with(B_KEY);
    }
    @Bean
    public Binding Dead(@Qualifier("dead_e") DirectExchange exchange,@Qualifier("dead_q") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with(DEAD_KAY);
    }

}
生产者
@RestController
public class Producer {
    public static final String X_EXCHANGE = "X";
    public static final String A_KEY = "A";
    public static final String B_KEY = "B";
    @Autowired
    private RabbitTemplate template;
    @GetMapping("rabbit/{msg}")
    public String setMessage(@PathVariable String msg){
        template.convertAndSend(X_EXCHANGE,A_KEY,msg);//向两个队列发送消息
        template.convertAndSend(X_EXCHANGE,B_KEY,msg);
        return msg+"发送成功";
    }
}
消费者

消费者在springboot中是一种监听器的形式,用来监听队列

@Component
public class Consumer {
    public static final String DEAD_QUEUE = "dead_queue";
    public static final String A_QUEUE = "A";
    public static final String B_QUEUE = "B";
    @RabbitListener(queues = DEAD_QUEUE)//消费者接收来自两个队列的死信
    public void D(Message msg,Channel channel){
        System.out.println("DEAD:"+new String(msg.getBody()));
        System.out.println("---");
    }
    @RabbitListener(queues = A_QUEUE)//消费者接收来自两个队列的死信
    public void A(Message msg,Channel channel){
        System.out.println("A:"+new String(msg.getBody()));
    }
    @RabbitListener(queues = B_QUEUE)//消费者接收来自两个队列的死信
    public void B(Message msg,Channel channel){
        System.out.println("B:"+new String(msg.getBody()));
    }
}

延迟队列(死信实现)

  • 需求:在注册十分钟后没有登录则发送通知信息
  • 死信实现:注册后发消息到队列,并设置队列ttl为10分钟,10分钟后消息过期自动转发到死信交换机,死信交换机路由到死信队列,最后被监听器获取
  • 代码实现:上面那个例子
  • 优化:生产信息创建消息时就设置ttl,而不是在队列中设置死ttl

在这里插入图片描述

  • 缺点 死信只能排队执行,如果第二个消息ttl短于第一个死信消息,第二个消息不会按时过期

用插件解决(延迟队列)

前提是安装了插件rabbitmq_delayed_message_exchange

无需新建死信交换机和死信队列,只需要以下结构
p — x交换机—A队列----c

修改的地方
  • 新建延迟交换机
    @Bean("X")
    public CustomExchange XExchange() {
        Map<String,Object> arguments = new HashMap<>();
        arguments.put("x-delayed-type","direct");
        return new CustomExchange(X_EXCHANGE,"x-delayed-message",true,false,arguments);
    }
  • 绑定
   @Bean
    public Binding XA(@Qualifier("X") CustomExchange exchange, @Qualifier("A") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with(A_KEY).noargs();
    }
  • 生产者
    @GetMapping("rabbitDelay/{ttl}")//设置消息延迟时间
    public String setDelayMessage(@PathVariable String ttl){
        template.convertAndSend(X_EXCHANGE,A_KEY,ttl,message -> {
            message.getMessageProperties().setDelay(Integer.parseInt(ttl));
            return message;
        });//向两个队列发送消息
        return ttl+"发送成功";
    }   

发布确认的回调函数

目的还是为了防止消息丢失,如果交换机或者队列丢失,生产者不会有察觉,基于springboot的发布确认

针对交换机
  • springboot配置文件中开启交换机确认消息
    publisher-confirm-type: correlated 
  • 回调函数
//定义回调接口
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback {
    /*
    -- 无论是否成功都会调用confirm函数
    1. correlationData 返回的信息,由发送方决定
    2. ack 成功为true
    3,失败原因
     */
    @Autowired
    RabbitTemplate template;
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            System.out.println("成功");
        }else{
            System.out.println("失败原因:"+cause);
        }
    }

    /**
     * callback是template内部接口,就算在这定义了也不会被调用,所以要将自定义的回调函数注入到template中
     * "@PostConstruct" 这个注解下的方法放在最后执行,因为如果template如果没有注入就执行inti,回报空指针异常
     */
    @PostConstruct
    public void init(){
        template.setConfirmCallback(this);
    }
}
针对队列

和交换机很像

  1. 注入
        template.setReturnCallback(this);
  1. 重写回调函数
//    注意实现RabbitTemplate.ReturnCallback接口
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println(message+"被退回");
        System.out.println("原因:"+replyText);
    }

备用交换机

也是针对交换机到队列这个过程
在这里插入图片描述
唯一要设置的就是当正常交换机发送失败后,应转发到备份交换机(fanout),备份交换机和备份队列正常写即可

   @Bean("E")
    public DirectExchange Exchange() {
        Map<String,Object> arguments = new HashMap<>();
        arguments.put("alternate-exchange",BACKUP_EXCHANGE);
        return ExchangeBuilder.directExchange(EXCHANGE).withArguments(arguments).build();
    }

一般备份交换机是fanout,绑定备份队列和报警队列,当收到消息时广播到这两个队列

优先级队列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值