【MQ】RubbitMq

背景

rubbitmq是有erlang语言开发的基于AMQP的开源的消息队列

场景

(1)异步处理
这里写图片描述
(2)应用解耦
这里写图片描述
(3)流量削峰
这里写图片描述

特点

(1)持久化机制
exchange,queue : durable = true
deliveryMode=2 持久化 /1 非持久化

(2)事务机制
txSelect()选择事务 txCommit()提交事务 txRollback()回滚事务

channel.txSelect();
channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
channel.txCommit();

这里写图片描述
事务的确认机制:

client发送Tx.Select
broker发送Tx.Select-Ok(之后publish)
client发送Tx.Commit
broker发送Tx.Commit-Ok

事务的消耗很大,所以一般使用消息确认机制

(3)消息确认机制
消息队列向消费者推送消息,如果消息得到处理,需要消费者向队列发一个确认,然后队列可以删除这个消息;如果没有返回确认,mq没有使用超时机制,只通过consumer的中断来判断消息是否得到处理,如果一个consumer退出时还没有返回确认,队列会重新把消息发给另一个consumer。保证不会丢失数据

(4)公平分发
channel.basic_qos(prefetch_count) = 1

AMQP组件

AMQP是一个抽象的消息通讯协议
这里写图片描述

  • server(broker):mq
  • virtural host: 权限控制
  • exchange:交换机,根据binding规则将消息路由到指定的queue上。分为三类:direct exchange, funout exchange, topic exchange
  • message queue: 消息队列
  • message:header+ body 数据主要在body里
  • binding:连接exchange 和 message queue。会维护一张路由表 binding key routing key
  • connection:生产者,消费者和broker之间的tcp连接
  • channel:信道 因为connection是tcp连接,而tcp连接昂贵,一个connection可以连接多个channel
  • command:命令 basePublish baseConsume

6种实现

(1)最简单的mq
这里写图片描述
生成者:

public class Send {
    private final static String QUEUE_NAME="hello";

    public static void main(String[] args) throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        String message = "hello world!";
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes("UTF-8"));
        System.out.println("[x] Send '" + message + "'");

        channel.close();
        connection.close();


    }
}

消费者:

public class Recv {
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        System.out.println("[*] Waiting for messages. To exit press CTRL + C");

        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] body)throws IOException{
                String message = new String(body,"UTF-8");
                System.out.println("[x] Received '" + message +"'");
            }
        };
        //true代表自动消息确认开启,如果消费者没有确认发给别人,如果确认了队列删除数据
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

channel.basicConsume(队列,true,消费者); true 表示开启消息确认机制
(2)工作队列–简单消息分发
这里写图片描述
生产者:

public class NewTask {
    private static final String TASK_QUEUE_NAME = "task_queue";

    public static void main(String[] args) throws Exception {
        String[] para = new String[4];
        para[0]="hello...";
        para[1]="Daniel...";
        para[2]="Hi..";
        para[3]="Summer..";
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //true表示队列数据可持久化
        channel.queueDeclare(TASK_QUEUE_NAME,true,false,false,null);

        String message = getMessage(para);

        //持久化
        channel.basicPublish("",TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("UTF-8"));
        System.out.println("[x] Sent '" + message + "'");

        channel.close();
        connection.close();
    }

    private static String getMessage(String[] strings){
        if(strings.length < 1)
            return "Hello world";
        return joinStrings(strings," ");
    }

    private static String joinStrings(String[] strings,String delimiter){
        int length = strings.length;
        if(length == 0 ) return "";
        StringBuilder stringBuilder = new StringBuilder(strings[0]);
        for(int i = 1; i < length; i++){
            stringBuilder.append(delimiter).append(strings[i]);
        }
        return stringBuilder.toString();
    }
}
  • 持久化两个参数:
    durable deliveryMode
  • 队列声明:
    channel.queueDeclare(TASK_QUEUE_NAME,true,false,false,null)

@ 第一个参数:队列名
@第二个参数:durable 持久化

  • 发送给队列:
    channel.basicPublish(“”,TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes(“UTF-8”));

@第一个参数:交换机名
@第二个参数:队列名
@第三个参数:deliveryMode(1:不持久化;2:持久化) MessageProperties.PERSISTENT_TEXT_PLAIN:可持久化
@第四个参数:message

消费者:

public class Worker {
    private static final String TASK_QUEUE_NAME="task_queue";

    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        final Connection connection=connectionFactory.newConnection();
        final Channel channel=connection.createChannel();

        channel.queueDeclare(TASK_QUEUE_NAME,true,false,false,null);
        System.out.println("[*] Waiting for messages. To exit press CTRL+C");

        //公平派遣,负载均衡
        channel.basicQos(1);

        final Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException{
                String message = new String(body,"UTF-8");

                System.out.println("[x] Received '" + message +"'");
                try{
                    doWork(message);
                }finally{
                    System.out.println("[x] Done");
                    //手动进行消息确认
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        channel.basicConsume(TASK_QUEUE_NAME,false,consumer);
    }

    private static void doWork(String task){
        for(char ch : task.toCharArray()){
            if(ch == '.'){
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException _ignored){
                    Thread.currentThread().interrupt();
                }
            }

        }
    }
}
  • 负载均衡两个属性:
    1.channel.basicQos(1) 消费者在没有确认前只能接受一条消息
    2.channel.prefetch_count(1)
    手动确认消息:
//手动进行消息确认
 channel.basicAck(envelope.getDeliveryTag(),false);

 channel.basicConsume(TASK_QUEUE_NAME,false,consumer);

(3)用exchange交换机的三个

(3.1)direct exchange 制定某队列发消息
这里写图片描述
生产者:

public class EmitLogDirect {
    private static final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws Exception{
        String[] argv = new String[4];
        argv[0]="Hello Daniel!";
        argv[1]="Hi Summer!";
        argv[2]="Have you had lunch?";
        argv[3]="yes, and you?";

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel  = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String severity = getSeverity(args);
        String message = getMessage(args);

        channel.basicPublish(EXCHANGE_NAME,severity,null,message.getBytes("UTF-8"));
        System.out.println("[x] Sent '" + severity + "':'"+message+"'");

        channel.close();
        connection.close();
    }

    private static String getSeverity(String[] strings){
        if(strings.length < 1)
            return "info";
        return strings[0];
    }

    private static String getMessage(String[] strings){
        if(strings.length < 2)
            return "Hello world!";
        return joinStrings(strings," ",1);
    }

    private static String joinStrings(String[] strings,String delimiter, int startIndex){
        if(strings.length == 0) return "";
        StringBuilder words = new StringBuilder(strings[0]);
        for(int i = startIndex; i<strings.length; i++){
            words.append(delimiter).append(strings[i]);
        }
        return words.toString();

    }


}

消费者:

public class ReceiveLogsDirect {
    private static final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        String queueName = channel.queueDeclare().getQueue();

        if(args.length < 1){
            System.err.println("usage:ReceiveLogsDirect  [info] [warning][error]");
            System.exit(1);
        }

       for(String severity : args){
            channel.queueBind(queueName,EXCHANGE_NAME,severity);
        }
        System.out.println("[*] Waiting for messages. To exit  press CTRL + C ");

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                String message = new String(bytes,"UTF-8");
                System.out.println("[x] Received '" +envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName,true,consumer);
    }

}

(3.2)fanout exchange 群发
这里写图片描述
发布订阅
生产者:

public class EmitLog {
    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args)throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        String message = getMessage(args);

        channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
        System.out.println("[x] Sent '" + message + "'");

        channel.close();
        connection.close();

    }

    private static String getMessage(String[] strings){
        if(strings.length < 1)
            return "info: Hello World!";
        return joinString(strings, " ");
    }

    private  static String joinString(String[] strings, String delimiter){
        int length = strings.length;
        if(length == 0) return "";
        StringBuilder words = new StringBuilder(strings[0]);
        for(int i = 1; i< length;i++){
            words.append(delimiter).append(strings[i]);
        }
        return words.toString();
    }
}
  • 交换机声明:
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
  • 交换机的类型:
    @ fanout 扇出–发布订阅型–群发
    @direct 路由–定向发
    @ topic 有选择群发使用临时队列:
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
        System.out.println("[x] Sent '" + message + "'");

消费者:

public class ReceiveLogs {
    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName,EXCHANGE_NAME,"");

        System.out.println("[*] Waiting for message. To exit press CTRL+C");

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                String message = new String(bytes,"UTF-8");
                System.out.println("[x] Received '" + message +"'");
            }
        };
        channel.basicConsume(queueName,consumer);
    }
}
  • binding属性,绑定交换机和队列 channel.queueBind(queueName,EXCHANGE_NAME,"");

(3.3)topic exchange 群发但是发给满足条件的
这里写图片描述

生产者:

public class EmitLogTopic {
    private static final String EXCHANGE_NAME="topic_logs";

    public static void main(String[] args) throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        String routingKey = getRouting(args);
        String message = getMessage(args);

        channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes("UTF-8"));
        System.out.println("[x] Sent '" + routingKey + "':'" + message + "'");

        channel.close();
        connection.close();
    }

    private static String getRouting(String[] strings){
        if(strings.length < 1)
            return  "anonymous.info";
        return strings[0];
    }

    private static String getMessage(String[] strings){
        if(strings.length < 2)
            return "Hello World!";
        return joinStrings(strings," ",1);
    }

    private static String joinStrings(String[] strings,String delimiter,int startIndex){
        int length = strings.length;
        if(length == 0)return "";
        if(length < startIndex) return  "";
        StringBuilder words = new StringBuilder(strings[0]);
        for(int i = startIndex; i < length; i++){
            words.append(delimiter).append(strings[i]);
        }
        return words.toString();
    }
}

消费者:

public class ReceiveLogsTopic {
    private static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args) throws Exception{
        ConnectionFactory factory  = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        String queueName = channel.queueDeclare().getQueue();

         if(args.length < 1){
             System.out.println("Usage : ReceivingLogsTopic [binding_key]...");
             System.exit(1);
         }
         for(String bindingKey: args){
            channel.queueBind(queueName,EXCHANGE_NAME,bindingKey);
         }
        System.out.println("[*] Waiting for message.");

        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)throws IOException{
                String message = new String(body,"UTF-8");
                System.out.println("[x] Received '" + envelope.getRoutingKey() + "':'"+ message + "'");
            }
        };
        channel.basicConsume(queueName,true,consumer);
    }
}
  • 根据bindingkey绑定交换机和队列: channel.queueBind(queueName,EXCHANGE_NAME,bindingKey);

(4)RPC生产者:
这里写图片描述

public class RPCServer {
    private static final String RPC_QUEUE_NAME = "rpc_queue";

    private static int fib(int n){
        if(n==0) return 0;
        if(n==1)return 1;
        return fib(n-1)+fib(n-2);
    }

    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        Connection connection =null;
        try {
            connection = factory.newConnection();
            final Channel channel = connection.createChannel();

            channel.queueDeclare(RPC_QUEUE_NAME,false,false,false,null);

            channel.basicQos(1);

            System.out.println("[x] Awaiting RPC requests");

            Consumer consumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] body)throws IOException{
                    AMQP.BasicProperties replyProps = new AMQP.BasicProperties
                            .Builder()
                            .correlationId(properties.getCorrelationId())
                            .build();
                    String response = "";
                    try{
                        String message = new String(body,"UTF-8");
                        int n = Integer.parseInt(message);

                        System.out.println("[.] fib(" + message + ")");
                        response += fib(n);
                    }catch (RuntimeException e){
                        System.out.println("[.]"+ e.toString());
                    }finally {
                        channel.basicPublish("",properties.getReplyTo(),replyProps,response.getBytes("UTF-8"));
                        channel.basicAck(envelope.getDeliveryTag(),false);
                        synchronized (this){
                            this.notify();
                        }
                    }
                }
            };

            channel.basicConsume(RPC_QUEUE_NAME,false,consumer);
            while(1==1){
                synchronized (consumer){
                    try{
                        consumer.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }finally {
            if(connection != null)
                try{
                    connection.close();
                }catch (IOException _ignore){}
        }

    }

}

消费者:

public class RPCClient {
    private Connection connection;
    private Channel channel;
    private String  requestQueueName = "rpc_queue";
    private String replyQueueName;

    public RPCClient() throws IOException,TimeoutException{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        connection = factory.newConnection();
        channel = connection.createChannel();

        replyQueueName = channel.queueDeclare().getQueue();
    }

    public String call(String message) throws IOException,InterruptedException{
        final String corrId = UUID.randomUUID().toString();

        AMQP.BasicProperties props = new AMQP.BasicProperties
                .Builder()
                .correlationId(corrId)
                .replyTo(replyQueueName)
                .build();

        channel.basicPublish("",requestQueueName,props,message.getBytes("UTF-8"));

        final BlockingQueue<String> response= new ArrayBlockingQueue<String>(1);

        channel.basicConsume(replyQueueName,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body)throws IOException{
                if(properties.getCorrelationId().equals(corrId)){
                    response.offer(new String(body,"UTF-8"));
                }
            }
        });
        return  response.take();
    }


    public void close()throws IOException{
        connection.close();
    }

    public static void main(String[] args) {
        RPCClient rpcClient = null;
        String response = null;
        try{
            rpcClient = new RPCClient();

            System.out.println("[x] Requesting fib(30)");
            response=rpcClient.call("30");
            System.out.println("[.]Got '" + response +"'");
        }catch (IOException | TimeoutException|InterruptedException e){
            e.printStackTrace();
        }finally {
            if(rpcClient != null){
                try{
                    rpcClient.close();
                }catch (IOException _ignore){}
            }
        }
    }

}

与spring整合

(1)普通mq
- 配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    ...
    <!--连接工厂-->
    <rabbit:connection-factory id="rabbitmqConnectionFactory"
                               username="guest" password="guest" host="127.0.0.1"
                               port="5672" virtual-host="summer-vhost"/>
    <!--生产者-->
    <rabbit:template id="template" connection-factory="rabbitmqConnectionFactory" encoding="UTF-8" exchange="exchange"/>
    <!--加上这句话,才可以自动生成队列和交换机-->
    <rabbit:admin id="connectAdmin" connection-factory="rabbitmqConnectionFactory"/>
    <!--定义队列-->
    <rabbit:queue name="summerQueue" durable="true" auto-delete="false" declared-by="connectAdmin"/>
    <!--定义交换机  加上declared-by才可以声明第二个交换机和队列-->
    <rabbit:direct-exchange name="exchange" durable="true" auto-delete="false" declared-by="connectAdmin">
       <rabbit:bindings>
           <rabbit:binding queue="summerQueue" key=""/>
       </rabbit:bindings>
    </rabbit:direct-exchange>

    <!--队列监听:ackonwledge确认机制 默认开启,现设置为手动的-->
    <rabbit:listener-container connection-factory="rabbitmqConnectionFactory" acknowledge="manual">
        <rabbit:listener ref="rabbitMqConsumerImpl" queue-names="summerQueue"/>
    </rabbit:listener-container>

    <bean id="consumer" class="com.hlp.myProject.service.impl.RabbitMqConsumerImpl"/>



</beans>
  • 声明connectionFactory,connection,queue declared by connection, exchange by connection,绑定exchange and queue, 接听consumer类

  • controller:

@Controller
@RequestMapping("message")
public class MessageQueueController {
    private Logger logger = LoggerFactory.getLogger(MessageQueueController.class);
    @Autowired
    @Qualifier("rabbitMqServiceImpl")
    private MessageQueueService messageQueueService;
    @Autowired
    @Qualifier("rabbitDelayServiceImpl")
    private MessageQueueService messageDelayQueueService;

    @ResponseBody
    @RequestMapping(value = "sendRubbitMassage",method = RequestMethod.GET)
    public HLPResult sendMessage(){
        HLPResult hlpResult = new HLPResult();
        hlpResult.setCode("0002");
        hlpResult.setMessage("~~哦哦。失败了呦");
        try{
            hlpResult.setCode("0001");
            hlpResult.setMessage("耶斯,成功了耶!");
            messageQueueService.sendDirectRabbitMessage();
        }catch (Exception e){
            logger.error("发送消息异常",e);
        }finally {
            return hlpResult;
        }
    }
  • 这两个例子一个普通,一个延时都是继承的同一个service,所以在注入,用qualifier()以示区分;

  • service

public interface MessageQueueService {
    void sendDirectRabbitMessage();
}
  • serviceImpl:
    生产者实现:
@Service("rabbitMqServiceImpl")
public class RabbitMqServiceImpl implements MessageQueueService {
    @Autowired
    private AmqpTemplate template;
    @Override
    public void sendDirectRabbitMessage() {
        RubbitMqEntity entity = new RubbitMqEntity();
        entity.setFavoriteMovie("英国病人");
        entity.setFavoriteSong("perfect");
        entity.setId(111L);
        Message message = MessageBuilder.withBody(
                Objects.requireNonNull(SerializationUtils.serialize(entity))).build();
        template.convertAndSend(message);
    }
}
  • 要把我们的实体放到message中,首先对实体序列化,再装入对象,最后放到message的body里。

  • 生产者,消费者之间通讯用的是Amqptemplate

消费者实现:

@Component
public class RabbitMqConsumerImpl implements MessageListener {
    @Override
    public void onMessage(Message message) {
        RubbitMqEntity entity = (RubbitMqEntity) SerializationUtils.deserialize(message.getBody());
        if(entity != null){
            System.out.println(entity.getFavoriteMovie());
            System.out.println(entity.getFavoriteSong());
            System.out.println(entity.getId());
        }
        System.out.println("为空喽");
    }
}

(2)延迟mq
- 配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       ... http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">

    <rabbit:connection-factory id="rabbitmqConnectionFactory" host="127.0.0.1" port="5672" username="guest" password="guest" virtual-host="summer-vhost"/>
    <rabbit:admin id="rabbitAdmin" connection-factory="rabbitmqConnectionFactory"/>
    <rabbit:queue name="delay_queue" auto-declare="true" declared-by="rabbitAdmin">
        <rabbit:queue-arguments>
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Long"/>
            <entry key="x-dead-letter-exchange" value="exchange_delay"/>
            <entry key="x-dead-letter-routing-key" value="task_queue"/>
        </rabbit:queue-arguments>
    </rabbit:queue>

    <rabbit:queue name="task_queue" auto-declare="true" declared-by="rabbitAdmin"/>
    <rabbit:direct-exchange name="exchange_delay" durable="false" auto-delete="false" id="exchange_delay" declared-by="rabbitAdmin">
        <rabbit:bindings>
            <rabbit:binding queue="task_queue" key="task_queue"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>
    <rabbit:template id="delayTemplate" connection-factory="rabbitmqConnectionFactory" queue="delay_queue" routing-key="delay_queue"/>

    <!--配置线程池-->
    <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="5"/>
        <property name="keepAliveSeconds" value="30000"/>
        <property name="maxPoolSize" value="1000"/>
        <property name="queueCapacity" value="200"/>
    </bean>

    <rabbit:listener-container connection-factory="rabbitmqConnectionFactory" acknowledge="manual" task-executor="taskExecutor">
        <rabbit:listener queues="task_queue" ref="rabbitDelayConsumerImpl"/>
    </rabbit:listener-container>
</beans>
  • 逻辑是这样的:
    生产者和消费者是靠template通讯的,生产者把消息传给消费者,首先template根据路由
    routing-key=”delay_queue”把消息放到delay_queue;
    delay_queue里设置了消息过期时间x-message-ttl,并且在过期后不是直接删掉而是把消息发给交换机x-dead-letter-exchange,路由到另一个队列x-dead-letter-routing-key里;
    这个队列监听着消费者

@Autowired
    @Qualifier("rabbitDelayServiceImpl")
    private MessageQueueService messageDelayQueueService;

@ResponseBody
    @RequestMapping(value = "sendDelayMessage",method = RequestMethod.GET)
    public HLPResult sendDelayMessage(){

        HLPResult hlpResult = new HLPResult();
        hlpResult.setCode("002");
        hlpResult.setMessage("哦哦,失败了");
        try{
            hlpResult.setCode("001");
            hlpResult.setMessage("成功了耶(^U^)ノ~YO");
            messageDelayQueueService.sendDirectRabbitMessage();
        }catch (Exception e){
            logger.error("消息发送异常",e);
        }finally {
            return hlpResult;
        }
    }
  • service:
public interface MessageQueueService {    void sendDirectRabbitMessage();}
  • serviceImpl:
    生成者实现:
@Service("rabbitDelayServiceImpl")
public class RabbitDelayServiceImpl implements MessageQueueService{
    @Autowired
    private AmqpTemplate delayTemplate;
    @Override
    public void sendDirectRabbitMessage() {
        RubbitMqEntity entity = new RubbitMqEntity();
        entity.setId(222L);
        entity.setFavoriteSong("blue");
        entity.setFavoriteMovie("廊桥遗梦");
        Message message = MessageBuilder.withBody(Objects.requireNonNull(SerializationUtils.serialize(entity))).build();
        delayTemplate.convertAndSend("delay_queue", message, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setDelay(30000);
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                System.out.println("消息延迟3秒! ");
                return message;
            }
        });
    }
}

消费者实现:

@Component
public class RabbitDelayConsumerImpl implements ChannelAwareMessageListener{
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        RubbitMqEntity entity = (RubbitMqEntity) SerializationUtils.deserialize(message.getBody());
        if(entity != null){
            System.out.println(entity.getId());
            System.out.println(entity.getFavoriteSong());
            System.out.println(entity.getFavoriteMovie());
        }
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        channel.basicAck(deliveryTag,false);
        System.out.println(deliveryTag);
        System.out.println("延迟消费的消费者");
    }
}

过期时间设置

  • 队列的过期时间:
 args.put("x-expires", 1800000);

一个队列如果超过一定时间没有被使用,就被删除掉。

  • 队列中消息的过期时间(针对所有消息):
    方法一:配置文件
<rabbit:queue name="delay_queue" auto-declare="true" declared-by="rabbitAdmin">
        <rabbit:queue-arguments>
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Long"/> <!--消息过期时间设置>

            <entry key="x-dead-letter-exchange" value="exchange_delay"/>
            <entry key="x-dead-letter-routing-key" value="task_queue"/>
        </rabbit:queue-arguments>
    </rabbit:queue>

方法二:Java中定义属性 message.getMessageProperties().setDelay(30000);

  • 队列中为单个消息设置过期时间:
    {"properties":{"expiration":"60000"}

    一个消息如果过了一定时间还没有被消费,就过期了,但也不只是粗暴的删掉,可以发给一个交换机。

<entry key="x-message-ttl" value="10000" value-type="java.lang.Long"/>
            <entry key="x-dead-letter-exchange" value="exchange_delay"/>
            <entry key="x-dead-letter-routing-key" value="task_queue"/>

如果配置文件中设置了ttl,代码中也设置了过期时间,以最短的为准。

挂张图总结一下

这里写图片描述

猜你喜欢

转载自blog.csdn.net/boniesunshine/article/details/81138303
MQ