RabbitMq的个人笔记

一、RabbitMq发送消息与接收消息的大致步骤

生产者

  1. 创建连接工厂ConnectionFactory并设置连接属性
  2. 通过工厂创建连接Connection connection = connectionFactory.newConnection();
  3. 通过连接创建channel Channel channel = connection.createChannel();
  4. 然后发送消息到交换机中 channel.basicPublish(exchangeName, routingKey, basicProperties, msg.getBytes());

消费者

  1. 监听队列 channel.basicConsume(queueName, false, new MyConsumer(channel)); false表示关闭自动签收,需要我们ask确认。
  2. 消息确认 channel.basicAck(envelope.getDeliveryTag(),false);

二、交换机类型

  1. direct直连交换机
    发送到direct类型中的消息会被投递到routing_key完全相同的绑定队列中
  2. Topic
    利用通配符进行绑定交换机和队列*表示一个字段,#表示多个字段
  3. 扇形交换机
    交换机直接绑定到队列,不需要路由key进行绑定分发、

三、自定义消费者

自定义消费者就很简单了,需要我们实现DefaultConsumer接口重写handleDelivery()方法。

/**
 * Convenience class providing a default implementation of {@link Consumer}.
 * We anticipate that most Consumer implementations will subclass this class.
 */
public class DefaultConsumer implements Consumer {
    /** Channel that this consumer is associated with. */
    private final Channel _channel;
    /** Consumer tag for this consumer. */
    private volatile String _consumerTag;

    /**
     * Constructs a new instance and records its association to the passed-in channel.
     * @param channel the channel to which this consumer is attached
     */
    public DefaultConsumer(Channel channel) {
        _channel = channel;
    }
    /**
     * No-op implementation of {@link Consumer#handleDelivery}.
     */
    public void handleDelivery(String consumerTag,
                               Envelope envelope,
                               AMQP.BasicProperties properties,
                               byte[] body)
        throws IOException
    {
            // no work to do
    } 
}
##############################
使用的话就是
api: String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
生产者接收消息方式
channel.basicConsume(queueName, false, new AckConsumer(channel));

四、死信队列 x-dead-letter-exchange

定义:队列中的消息没有被消费者ack确认,这个消息就是一个死消息,那这个消息就会重现发送另一个死信队列中。
具体的实现如下:

ap<String,Object> queueArgs = new HashMap<>();
//正常队列上绑定死信队列
queueArgs.put("x-dead-letter-exchange",dlxExhcangeName);
queueArgs.put("x-max-length",4);
channel.queueDeclare(nomalqueueName,true,false,false,queueArgs);
channel.queueBind(nomalqueueName,nomalExchangeName,routingKey);

不消息确认不签收
channel.basicNack(envelope.getDeliveryTag(),false,false);
  • 可以看到过期的消息自动分发到我们的死信队列中了。
    在这里插入图片描述

五、延迟队列

延迟队列的使用场景

  1. 支付项目中的自动取消支付呀
  2. 半小时之后闹钟啊
  • 肯定比每秒定时去数据库中查询的效率高吧,并且数据量大了你的效率就更低了。
  • 实现逻辑:给消息设置一个失效时间,然后监听死信队列。
生产者
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
            .deliveryMode(2)
            //30秒过期后就回发送到死信队列中
            .expiration("30000")
            .build();
//生产者发送消息
channel.basicPublish(exchangeName,routingKey,basicProperties,message.getBytes());

消费者
channel.basicConsume(queueName, false, new MyDleConsumer(channel));

六、消息的限流

作用:当我们的一个服务发送的消息特别多时,如果大量的消息发送到我们的消费者,那么我们的消费者就会很大的压力,甚至导致服务器的宕机,所以我们做消息的限流。
其实注释也给我们解释的很清楚了。

/**
     * Request specific "quality of service" settings.
     *
     * These settings impose limits on the amount of data the server
     * will deliver to consumers before requiring acknowledgements.
     * Thus they provide a means of consumer-initiated flow control.
     * @see com.rabbitmq.client.AMQP.Basic.Qos
     * @param prefetchSize maximum amount of content (measured in
     * octets) that the server will deliver, 0 if unlimited
     * @param prefetchCount maximum number of messages that the server
     * will deliver, 0 if unlimited
     * @param global true if the settings should be applied to the
     * entire channel rather than each consumer
     * @throws java.io.IOException if an error is encountered
     */
    void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException;

使用方式  
-参数1 prefetchSize如果没有限制,则为0
-参数2 prefetchCount服务器上的最大消息数
channel.basicQos(0,1,false);

七、消息确认(ConfirmListener )

Confirm确认机制
Confirm确认机制 当生产者发送完消息,mq接收到消息后会给生产者发送一个收到消息的答应。然后就能判断这个消息是否正常的发送到了mq上。这个机制可以用来保证消息的可靠性投递。
这段注释也写的很明白了
/**
 * Implement this interface in order to be notified of Confirm events.
 * Acks represent messages handled successfully; Nacks represent
 * messages lost by the broker.  Note, the lost messages could still
 * have been delivered to consumers, but the broker cannot guarantee
 * this.
 */
public interface ConfirmListener {
	//消息的正常确认
    void handleAck(long deliveryTag, boolean multiple)
        throws IOException;

	//消息的否定确认
    void handleNack(long deliveryTag, boolean multiple)
        throws IOException;
}

步骤1. 开启消息确认模式
channel.confirmSelect();
步骤2. 开启消息确认监听
- 使用方式
channel.addConfirmListener(new TestConfirmListener());

八、消息的幂等性

为什么重发: 项目实践中为了让消息百分百发送成功我们一般都使用重发消息,例如由于网络问题,我们都会将这个消息重新发送一次。
重返导致问题: 会出现被消费者多次消费。
解决办法: 需要我们保证消息的幂等性。

  1. 对每一个消息加一个全局唯一id,每次重发的时候判断是否已存在,不存在就保存到数据库中。
  2. 消费者端可以利用redis的setnx来保证幂等性。

九、消息发送接收的伪代码

Map<String, Object> map = new HashMap<>();
map.put("name", "luoli");

//定义消息的属性 ·是否持久化 ·编码格式 ·唯一id ·消息头信息 ·失效时间
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
            .deliveryMode(2) //2为持久化,1 不持久化
            .contentEncoding("UTF-8") //编码格式
            .correlationId(UUID.randomUUID().toString())
            .headers(map) //头信息,消费端可以获取到 
            .expiration("10000") //失效时间
            .build();
channel.basicPublish(exchangeName, routingKey, basicProperties, msg.getBytes());


//自定义消费者
public class MyConsumer extends DefaultConsumer {

@Override
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] body) throws IOException {

	// name = luoli
    Sring name= (Sring) properties.getHeaders().get("name");   
    channel.basicAck(envelope.getDeliveryTag(),false); 
}

//消息的ack确认 ,当开启消息确认后,队列中的每个消息都需要被ack消费确认后才能继续消费
channel.basicAck(envelope.getDeliveryTag(),false);
//channel限流 0:不限制消息大小, 1:每次推送多少条消息
channel.basicQos(0,1,false); 

十、ReturnListener 不可达消息

  1. channel.basicPublish(exchangeName,okRoutingKey,true,basicProperties,msg.getBytes());
  2. 当交换机错误或不存在,或在根据路由键不能找到队列,对应的消息就会成为死消息
  3. 当mandatory设置true,mq会调用ReturnListener中的handleReturn(),当设置为false mq会将这种消息自动删除。

十一、spring整合rabbitmq

//最基本的一个配置
@Configuration
public class RabbitmqConfig {


    /**
     * 创建连接工厂
     * @return
     */
    @Bean
    public ConnectionFactory connectionFactory () {
        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
        cachingConnectionFactory.setAddresses("ip:port");
        cachingConnectionFactory.setVirtualHost("luoLi-mq");
        cachingConnectionFactory.setUsername("guest");
        cachingConnectionFactory.setPassword("guest");
        cachingConnectionFactory.setConnectionTimeout(100000);
        cachingConnectionFactory.setCloseTimeout(100000);
        return cachingConnectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        rabbitTemplate.setReceiveTimeout(50000);
        return rabbitTemplate;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        //spring容器启动加载该类
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }
//配置消息监听容器
	@Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer() {
        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory());
        //设置监听的队列
        simpleMessageListenerContainer.setQueues(testTopicQueue(),testDirectQueue(),testTopicQueue2(),orderQueue(),addressQueue(),fileQueue());
        //设置当前消费者者数量
        simpleMessageListenerContainer.setConcurrentConsumers(5);
        //最大消费者个数5
        simpleMessageListenerContainer.setMaxConcurrentConsumers(10);
        //设置签收模式 -自动签收
        simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
        //设置拒绝重回队列- 不允许重回队列
        simpleMessageListenerContainer.setDefaultRequeueRejected(false);
   }
}

//创建消息监听适配器对象
//自己创建一个消息委托对象
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new LuoLiMsgDelegate());
simpleMessageListenerContainer.setMessageListener(messageListenerAdapter);

因为默认的消费方法是 handleMessage,所以消息委托对象LuoLiMsgDelegate中需要配置handleMessage方法
public void handleMessage(String msgBody) {
    System.out.println("MsgDelegate。。。。。。handleMessage" + msgBody);
}


发送消息
rabbitTemplate.convertAndSend("luoli.exchange","routing.key","luoli hello");

springboot整合rabbitmq

@RabbitListener(queues = {ORDER_TO_PRODUCT_QUEUE_NAME})
@RabbitHandler
public void consumerMsg(Message message, Channel channel) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        MessageDto messageDto= objectMapper.readValue(message.getBody(), MessageDto.class);
        Long deliveryTag = (Long) message.getMessageProperties().getDeliveryTag();
        //消息确认
        channel.basicAck(deliveryTag,false);
        //处理业务逻辑
        System.out.println(msgTxtBo.getMsgId());
}

十二、RabbitMq实现分布式事务

  1. 用户下单后会生成一个订单和一个支付消息保存到数据库中,状态为0 位支付
  2. 开启定时任务,每2s去消息表中查询未支付的消息
  3. 支付成功了的将跟新消息状态生成发货的数据
  4. 完成消息的ACK确认
为什么要开启一个定时任务呢?

业务中支付模块和消费者不是在同一个服务中的,如果网络原因导致支付服务的支付失败,但是这个消息还是会被ACK,那么这条消息就会被直接消费掉,导致用户无法继续支付。我们只用当用户支付成功后该消息才应该被正常ACK。

如果用户支付时间较长那么就会生成多个消息,会导致用户重新支付。那又怎么办呢?

可以利用redis实现一个分布式锁redisTemplate.opsForValue().setIfAbsent(),利用每个支付消息的唯一id。
注意点,消费者监听到第一个消息的时候这个消息就已经保存到redis中了,如果支付失败也会导致用户无法再次支付,因为已经redis中已经存在唯一消息的key了,所以当支付失败的时候,我们需要将这个唯一消息key从redis中删除掉。这样用户才能继续的支付。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值