• Rabbit问题


    MQ如何将消息可靠(可靠就是要消费者收到消息)投递到消费者?

    Client发送Ack消息给MQ,通知MQ删除该消息,
    此处有可能因为网络问题导致Ack失败,那么Client会重复消息,这里就引出消费幂等的问题;
    “幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。”
    方案一:设计全局唯一的ID:Guid+时间戳  
    发送者将ID设置到消息头的MessageID中 ,发送消息携带消息体和消息头
    接收者先判断messageid 是否消费过,没有消费过写入Redis中,防止重复写入
    为每条消息设置一个全局唯一的messageId,消费者拿到消息后,
    使用setnx命令,将messageId作为key放到redis中:setnx(messageId,1)==》Redis Setnx(SET if Not eXists)命令在指定的key不存在时,为key设置指定的值
    若返回1,说明之前没有消费过,正常消费;     设置成功,返回 1 。 设置失败,返回 0 
    若返回0,说明这条消息之前已消费过,抛弃
    
    方案二:
    设置数据库中的主键,根据数据库的特性,防止重复写入

    防止消息丢失:

    autoAck=false时,需要消费端手动确认消息
    当客户端发送ACK给到Rabbitmq的时候,假如还没有到MQ就挂掉了,那么mq会将消息轮询给其他的客户端,
    但是,如果我只想将消息给到指定的客户端,可以用下面的属性
    channel.BasicRecover(false);//false代表只补发给当前的consumer
    

    参考链接(非常好,建议查阅)

    确保生产者把消息送到RabbitMq:

    1:事务模式   
     channel.txSelect()的方法把信道设置成事务模式,然后就可以发布消息给 RabbitMQ 了,
     如果 channel.txCommit();的方法调用成功,就说明事务提交成功,则消息一定到达了 RabbitMQ 中。
     如果在事务提交执行之前由于 RabbitMQ 异常崩溃或者其他原因抛出异常,
     这个时候我们便可以将其捕获,进而通过执行 channel.txRollback()方法来实现事务回滚。
     弊端:
     在事务模式下,只有收到了服务端的Commit-OK指令,才能提交成功,所以可以解决生产者和服务端确认的问题,
     但是事务模式有一个缺点,
     他是阻塞服务端的,一条消息没有发送完毕,就不能发送下一条消息,会榨干RabbitMq服务器的性能,
     所以不建议生产环境使用。官方说会降低250倍性能

    2.第二种机制是确认机制

    2.1 普通确认模式

    在生产者这边通过调用channel.confirmSelect方法将信道设置为Confirm模式,
    然后发送消息,一旦消息被投递Broker后,Rabbitmq就会发送一个确认的Basic.Ack给生产者,
    也就是调用channel.waitForConfirms方法返回true,这样生产者就知道消息被服务端接收了。
    但是这个是一条一条的发送确认,等待确认的过程中时阻塞的,所以这种方会阻塞客户端,效率也不会太高
     

    2.2 批量确认模式

    就是在开启Confirm模式后,先发送一批消息,然后再批量确认,
    使用的是channel.waitForConfirmsOrDie方法,如果方法没有抛出异常,就代表消息都被服务端接受了。
    
    批量确认的方式比单条确认的方式效率要高,但是也有两个问题,第一个就是批量的数量的确定。
    对于不同的业务,到底发送多少条消息确认一次?数量太少,效率提升不上去。数量多的话,
    又会带来另一个问题,比如我们发 1000 条消息才确认一次,因为不确定哪条消息发送失败了,那么前面所有的消息都要重发。
    但是发送了也可能导致一些消息重复发送。所以该方法存在较大问题

     

    2.3异步确认方式:

    异步确认模式需要在添加一个ConfirmListener,并且使用sortset来维护一个没有被确认的消息。
    Sortset<>对集合
    final SortedSet<long> confirmSet=new TreeSet();
    public class AsyncConfirm {
    
        public static void main(String[] args) throws Exception{
            ConnectionFactory factory = new ConnectionFactory();
            factory.setUri("amqp://guest:guest@192.168.18.140:5672");
            factory.setVirtualHost("/");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
    
            //用于保存维护未被确认的消息,要有序set  这里就是要treeset
            final SortedSet<Long> confirmSet = new TreeSet<>();
            channel.addConfirmListener(new ConfirmListener() {
                @Override
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                    System.out.println(String.format("Broker已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
                    //处理发送成功的情况的方法
                    // deliveryTag,发送成功的消息的标志,multiple是否批量发送的
                    if (multiple){
                        //如果true表示批量执行了deliveryTag这个值以前(小于deliveryTag的)的所有消息,如果为false的话表示单条确认
                        //如果为true,表示确认了此条条以及前面的n条的消息,需要批量重Sort中删除
                        //表示清除deliveryTag这条消息以及前面的消息,因为这个集合是维护未被确认的消息,而这些消息被确认了,就要删除掉了。
                        confirmSet.headSet(deliveryTag + 1L).clear();
                        //下面也可以做一些业务逻辑
                    }else {
                        //单条确认
                        confirmSet.remove(deliveryTag);
                    }
    
                    System.out.println("未确认消息:" + confirmSet);
                }
    
                @Override
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                    //这个是处理未发送成功的消息
                    System.out.println("Broker未确认消息,标识:" + deliveryTag);
                    if (multiple) {
                        // headSet表示后面参数之前的所有元素,全部删除
                        confirmSet.headSet(deliveryTag + 1L).clear();
                    } else {
                        confirmSet.remove(deliveryTag);
                    }
    
                    //这里可以写一些补偿逻辑
                }
            });
            String message = "asyncConfirmTest";
            channel.confirmSelect();
            for (int i = 0;i<10;i++){
                //获取下一次发送的发送Id
                long nextPublishSeqNo = channel.getNextPublishSeqNo();
                //加到为确认队列中
                confirmSet.add(nextPublishSeqNo);
                channel.basicPublish("TransactionEx", "TransactionMsg", null, (message +"-"+ i).getBytes());
    
            }
            System.out.println("所有消息:"+confirmSet);
        }
    }
  • 相关阅读:
    类的关联关系
    VisualStudio.DTE 对象可以通过检索 GetService() 方法
    openssl 安装
    反射的效率
    Ascll
    关于JavaScript 原型的理解
    asp.net MVC 学习笔记
    CSS3样式
    List<T>转DataTable
    SQL中的多表联查(SELECT DISTINCT 语句)
  • 原文地址:https://www.cnblogs.com/ZkbFighting/p/15987918.html
Copyright © 2020-2023  润新知