消息投递

1.一般一个项目用一个vhost,在控制台新建一个vhost就好,然后选用该vhost.

控制台地址为:mq的安装主机IP:15672,我这里是http://192.168.206.99:15672/,如果安装时未指定用户名和密码,默认都是guest.

rabbitmq springboot server 可以配置域名吗_spring

2.导入依赖,由于是boot项目所以不用指定版本.

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

3.yml文件配置

spring:
  rabbitmq:
    host: ly-mq #mq所在的主机的IP
    username: leyou
    password: 123321
    virtual-host: /leyou #刚才新建的vhost
    template:
      retry: #失败重试
        enabled: true #开启失败重试
        initial-interval: 10000ms #第一次重试的间隔时长
        max-interval: 80000ms #最长重试间隔,超过这个间隔将不在重试
        multiplier: 2 #下次重试间隔的倍数,此处时2,即下次重试间隔是上次的2倍
    publisher-confirms: true #生产者确认机制,此处配置后,如果发送失败会有错误回执,从而触发重试.

4.默认情况下,AMQP会使用JDK的序列化方式对发送的消息进行处理,传输数据比较大,效率太低,可读性差。我们可以自定义消息转换器,使用JSON来处理.

@Configuration
public class RabbitConfig {

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

5.在需要使用的地方导入AmqpTemplate即可

// 三个参数分别为交换机名,routingKey,要投递的内容.前2者我这里定义了常量,可以直接写字符串.
amqpTemplate.convertAndSend(MQConstants.ExchangeConstants.ITEM_EXCHANGE_NAME, routingKey, id);

消费消息

1.新建一个模块,基本这个模块只用来监听了.

2.同样导入依赖,yml文件配置,因为只是监听配置变少了

spring:
  rabbitmq:
    host: ly-mq #mq所在的主机的IP
    username: leyou
    password: 123321
    virtual-host: /leyou #刚才新建的vhost

3.同样需要配置消息转换器如上

4.写一个监听器,你刚才往哪个队列投递的消息就监听谁.这里是我的

@Component
public class ItemListener {

    @Autowired
    private IndexService indexService;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = QueueConstants.SEARCH_ITEM_UP, durable = "true"),
            exchange = @Exchange(name = ExchangeConstants.ITEM_EXCHANGE_NAME, type = ExchangeTypes.TOPIC),
            key = RoutingKeyConstants.ITEM_UP_KEY
    ))
    public void listenItemUp(Long spuId){
        if (spuId != null) {
            // 商品上架,我们新增商品到索引库
            indexService.saveGoodsById(spuId);
        }
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = QueueConstants.SEARCH_ITEM_DOWN, durable = "true"),
            exchange = @Exchange(name = ExchangeConstants.ITEM_EXCHANGE_NAME, type = ExchangeTypes.TOPIC),
            key = RoutingKeyConstants.ITEM_DOWN_KEY
    ))
    public void listenItemDown(Long spuId){
        if (spuId != null) {
            // 商品下架,我们删除商品
            indexService.deleteGoodsById(spuId);
        }
    }
}

业务随便你怎么写,我感觉方法上的注解较为难记.我们只创建了vhost,交换机和队列都是这个监听方法头上的注解帮我们创建的,所以要想成功使用,得先把这个监听模块启动起来.然后消息才能成功投递.

附上我的常量类

public abstract class MQConstants {

    public static final class ExchangeConstants {
        /**
         * 商品服务交换机名称
         */
        public static final String ITEM_EXCHANGE_NAME = "ly.item.exchange";
        /**
         * 消息服务交换机名称
         */
        public static final String SMS_EXCHANGE_NAME = "ly.sms.exchange";
        /**
         * 订单业务的交换机
         */
        public static final String ORDER_EXCHANGE_NAME = "ly.order.exchange";
        /**
         * 死信队列交换机名称
         */
        public static final String DEAD_EXCHANGE_NAME = "ly.dead.exchange";
    }

    public static final class RoutingKeyConstants {
        /**
         * 商品上架的routing-key
         */
        public static final String ITEM_UP_KEY = "item.up";
        /**
         * 商品下架的routing-key
         */
        public static final String ITEM_DOWN_KEY = "item.down";
        /**
         * 商品下架的routing-key
         */
        public static final String VERIFY_CODE_KEY = "sms.verify.code";
        /**
         * 清理订单routing-key
         */
        public static final String EVICT_ORDER_KEY = "order.evict";
    }

    public static final class QueueConstants{
        /**
         * 搜索服务,商品上架的队列
         */
        public static final String SEARCH_ITEM_UP = "search.item.up.queue";
        /**
         * 搜索服务,商品下架的队列
         */
        public static final String SEARCH_ITEM_DOWN = "search.item.down.queue";
        /**
         * 搜索服务,商品下架的队列
         */
        public static final String SMS_VERIFY_CODE_QUEUE = "sms.verify.code.queue";
        /**
         * 订单死信队列名称
         */
        public static final String DEAD_ORDER_QUEUE = "ly.dead.order.queue";
        /**
         * 订单清理队列名称
         */
        public static final String EVICT_ORDER_QUEUE = "ly.evict.order.queue";

    }
}

消息确认机制

生产者在投递消息后会得到broken的反馈,消费者在消费后也会给队列反馈.这里主要讨论后者.

1.自动ack.消费者刚拿到消息还没消费,就给队列发ack,让它删除该消息.这样做浏览大,但是有一定风险.有可能ack发出去后,消息被删除了,但是没有成功消费.这就是消息丢失.我们不做任何配置的话,默认就是自动ack.

2.手动ack:在yaml文件里配置ack未manual,然后在监听方法处这样写.

 

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;

@Component
public class ItemListener {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "xixixi", durable = "true"),
            exchange = @Exchange(name = "linjun", type = ExchangeTypes.TOPIC),
            key = "haha"
    ))

    public void listenItemUp(String spuId, Channel channel,Message message) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
        System.out.println("spuId = " + spuId);
        System.out.println(deliveryTag);
        int i=1/0;
        //channel.basicReject();//long deliveryTag , boolean requeue false未处理任要删除,true未处理,归队
       // channel.basicNack(); //long deliveryTag, boolean multiple, boolean requeue 同上,但是可以批量,怎么批量呢?该通道上未消费 的到tag标记这里的的一次性处理了
//            channel.basicAck(deliveryTag, false); 处理了,删除
        System.out.println("消费完成");
        } catch (Exception e) {
            e.printStackTrace();
//            channel.basicNack(deliveryTag, false, true);
        }

    }


}

这里不能照抄这只是一个测试案例.简单解释一下这个代码.

依然是头顶一堆注解,表示交换机,队列绑定关系等.

方法的参数以后都写3个,第一个是消息内容,你投递的什么格式就写什么格式.后面2个固定,导的是哪2个包,代码块里有注明.

deliveryTag是消息编号,消费者做ack时会带着这个,指定这个消息让队列删除它.业务代码写在try里面,最后一行写channel.basicAck(deliveryTag, false);表示消费成功,删除此消息.第一个参数时消息编号,第二个是指是否批量删除.如果写true,则该通道上该序号之前的未背确认的消息都会被确认.如果写false,则只确认者一个消息,它前面的消息不管.

catch块里面写处理异常的逻辑.消费出现异常,一个在这里解决异常,然后channel.basicNack(deliveryTag, false, true);发送一个未确认回去,并让消息重新返回队列,再次消费.前2个参数和上段意思一样,最后一个true表示让消息重新归队,可以被再次消费.一定要注意,catch块里一定要把异常解决掉,否则会无限循环.或者在catch里把此消息放到别的队列里,让别人去消费.

basicReject方法和basicNack方法是一样的,只是它只有2个参数,只能处理一条消息.不能批量处理.这2个方法最后一个参数写false,都表示我消费失败了,但是还是要删除该消息.