RabbitMQ之订阅模式与主题模式,消息确认机制

1.订阅模式

作用类似与微信公众号,你订阅了就可以接收到消息

RabbitMQ之订阅模式与主题模式,消息确认机制_第1张图片解读:
1.一个生产者,多个消费者。
2.每一个消费者 都有自己的队列
3.生产者没有直搂把洧息发送到队列而是发到了交换机 转发器exchange
4.每个队列都要绑定到交换机上。
5.生产者发送的消息经过交换机到达臥列 就能实现一个消息到多个消费者消费

1.2分发模式fanout

该模式下,生产者发送消息,多个消费者通过他们对应的队列获得消息,但是所获得的消息是一样的,不能指定将哪个消息给那个队列

创建一个生产者

public class send {
    private static final String EXCHANGE_NAME="MXH_Exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        ----声明一个交换机
        ----fanout:分发模式
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");

        String msg = "hello exchange";
        发送消息到交换机
        channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());

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

创建第一个消费者

public class receive1 {
   private static final String EXCHANGE_NAME="MXH_Exchange";
   private static final String QUEUE_NAME="MXH_Exchange_note";
   public static void main(String[] args) throws IOException, TimeoutException {
       Connection connection = ConnectionUtil.getConnection();

       Channel channel = connection.createChannel();
       声明一个对列
       channel.queueDeclare(QUEUE_NAME,false,false,false,null);
       将队列绑定到交换机
       channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
       一次应答前只发送一次
       channel.basicQos(1);

       DefaultConsumer consumer = new DefaultConsumer(channel)
       {
           @Override
           public void handleDelivery(String consumerTag, Envelope envelope,
                                      AMQP.BasicProperties properties,
                                      byte[] body) throws IOException
           {
               String msg = new String(body);
               System.out.println("RE:"+msg);
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e)
               {
                   e.printStackTrace();
               }finally
               {
                   //手动应答
                   channel.basicAck(envelope.getDeliveryTag(),false);
               }
           }

       };
       boolean autoAck = false;//自动应答 true为开启 false为关闭
       channel.basicConsume(QUEUE_NAME,autoAck, consumer);

   }
}

创建第二个消费者

public class receiver2
{
    private static final String EXCHANGE_NAME="MXH_Exchange";
    private static final String QUEUE_NAME="MXH_Exchange_emain";
    public static void main(String[] args) throws IOException, TimeoutException
    {
        ------创建一个连接
        Connection connection = ConnectionUtil.getConnection();
        ------创建一个通道
        Channel channel = connection.createChannel();
        ------创建一个队列,用户名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        ------将队列绑定到交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        ------一次应答前只发送一次
        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    ------手动应答
                    channel.basicAck(envelope.getDeliveryTag(),false);

                }
            }
        };
        ------自动应答 true为开启 false为关闭
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);
    }
}

2.Exchange交换机

作用:一方面是接收生产者的消息,另一方面是向队列推送消息。

2.1Fanout(不处理路由健)

匿名转发。
Fanout(不处理路由健):对消息不作处理,消息不能指定发给特定的消费者

2.2Direct路由模式

交换机给队列发送消息时会在后面加一个Key,只有队列的Key和交换机的Key对应上,交换机才会把消息发给队列,如下图,交换机有error,info,warng三个Key,error的key对应c1和c2,所以带有error的key的消息就会发送给c1和c2
RabbitMQ之订阅模式与主题模式,消息确认机制_第2张图片
创建一个生产者
绑定key到交换机

public class send {
    private static final String EXCHANGE_NAME="MXH_Exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        -----声明一个交换机
      -----fanout:分发模式  channel.exchangeDeclare(EXCHANGE_NAME,"direct");

        String msg = "hello direct";
        -----发送消息到交换机,绑定key到交换机
        String key ="love";
        channel.basicPublish(EXCHANGE_NAME,key,null,msg.getBytes());

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

创建第一个消费者
绑定key到交换机

public class receive1 {
    private static final String EXCHANGE_NAME="MXH_Exchange";
    private static final String QUEUE_NAME="MXH_Exchange_note";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        //声明一个对列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //将队列绑定到交换机,绑定key到交换机
        String key ="error";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
        //一次应答前只发送一次
        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    //手动应答
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }

        };
        boolean autoAck = false;//自动应答 true为开启 false为关闭
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);

    }
}

创建第二个消费者

public class receiver2
{
    private static final String EXCHANGE_NAME="MXH_Exchange";
    private static final String QUEUE_NAME="MXH_Exchange_emain";
    public static void main(String[] args) throws IOException, TimeoutException
    {
        //创建一个连接
        Connection connection = ConnectionUtil.getConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
        //创建一个队列,用户名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //将队列绑定到交换机,绑定key到交换机
        String key1 = "error";
        String key2 = "info";
        String key3 = "love";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key1);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key2);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key3);
        //一次应答前只发送一次
        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    //手动应答
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        boolean autoAck = false;//自动应答 true为开启 false为关闭
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);
    }
}

2.3Toplc exchanger主题模式

Toplc exchanger:将路由键和某模式匹配,
#匹配一个或者多个。
‘*’匹配一个。

第二个消费者:
注意我的key值: String key =“love.#”;
#代表多个,只要生产者发给交换机的key是love开头,这个消费者就能接收到
RabbitMQ之订阅模式与主题模式,消息确认机制_第3张图片RabbitMQ之订阅模式与主题模式,消息确认机制_第4张图片
创建一个生产者:
注意我的key值:String key =“love.del”;

public class send {
    private static final String EXCHANGE_NAME="MXH_Exchange_topic";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        //声明一个交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"topic");//fanout:分发模式

        String msg = "hello direct";
        //发送消息到交换机
        String key ="love.del";
        channel.basicPublish(EXCHANGE_NAME,key,null,msg.getBytes());

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

创建第一个消费者
注意我的key值: String key =“love.add”;

public class receive1 {
    private static final String EXCHANGE_NAME="MXH_Exchange_topic";
    private static final String QUEUE_NAME="MXH_Exchange_note";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        //声明一个对列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //将队列绑定到交换机
        String key ="love.add";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
        //一次应答前只发送一次
        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    //手动应答
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }

        };
        boolean autoAck = false;//自动应答 true为开启 false为关闭
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);

    }
}

创建第二个消费者:
注意我的key值: String key =“love.#”;
#代表多个,只要生产者发给交换机的key是love开头,这个消费者就能接收到

public class receiver2
{
    private static final String EXCHANGE_NAME="MXH_Exchange_topic";
    private static final String QUEUE_NAME="MXH_Exchange_emain";
    public static void main(String[] args) throws IOException, TimeoutException
    {
        //创建一个连接
        Connection connection = ConnectionUtil.getConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
        //创建一个队列,用户名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //将队列绑定到交换机
        String key = "love.#";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
        //一次应答前只发送一次
        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    //手动应答
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        boolean autoAck = false;//自动应答 true为开启 false为关闭
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);
    }
}

消息确认机制

在rabbitmq中我们可以通过持久化数据解决rabbitmq服务器异常的数据丢失问题。

问题:生产者将消息发送出去之后,消息到底有没有到达rabbitmq 服务器默认的情况是不知道的;

两种方式可以知道:
1.AMQP实现的事务机制
2.Confirm横式

AMQP实现的事务机制

AMQP提供了三个方法
channel.txSelect();-----开启事物:在消息发送前开启
channel.txCommit();-----提交事物:在消息发送后提交
channel.txRollback();-----事物回滚:如果有异常就回滚,处理异常

这种模式增加了服务器的请求量,使服务器处理的效率降低

public class send {
    private static final String QUEUE_NAME="MXH";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //创建一个连接
        Connection connection = ConnectionUtil.getConnection();
        //从连接中获取一个通道
        Channel channel = connection.createChannel();
        //创建一个消息队列  QUEUE_NAME:队列名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

            try
            {


            String msg = "hello,rqbbitmq你好";
            channel.txSelect();-----开启事物
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            int i =1/0;
            channel.txCommit();-----提交事物
            }catch (Exception e)
            {
                channel.txRollback();-----事物回滚
                System.out.println("发送异常,消息未送达");
            }

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

Confirm横式

原理

生产者将信道设置成confirm横式,一旦信道进入confirm横式,所有在该信道上面发布的洧息都会被指很一个唯一的ID(从1开始)。一旦洧息被投递到所有匹配的队列之后,broker就会发送-一个确认给生产者(包含消息的唯一ID) .这就使得生产者知道消息已经正确到达目的队列了。如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker 固传给生产者的确认消息中deliver-tag 域包含了确认消息的序列号,此外broker也可以设置basic.ack 的multiple城,表示到这个序列号之前的所有消息都已经得到了处理。。

Confirm摸式最大的好处在于他是异步。

串行模式

channel.confirmSelect();//开启confirm模式
channel.waitForConfirms():该方法在生产者发送消息到队列后会返回true
if(channel.waitForConfirms())
{
System.out.println(“发送成功”);
}else
{
System.out.println(“发送失败”);
}

普通模式:该模式一次发送一条消息到队列,一发一回

public class send {
    private static final String QUEUE_NAME="MXH_confirm";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //创建一个连接
        Connection connection = ConnectionUtil.getConnection();
        //从连接中获取一个通道
        Channel channel = connection.createChannel();
        //创建一个消息队列  QUEUE_NAME:队列名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        try {

          channel.confirmSelect();//开启confirm模式
            String msg = "hello,rqbbitmq你好";
            int i =1/0;
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            if(channel.waitForConfirms())
            {
                System.out.println("发送成功");
            }else
            {
                System.out.println("发送失败");
            }
        }catch (Exception e)
        {
            System.out.println("发送失败");
        }

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

批量模式:该模式用for循环发送多条数据,多发一回,发完所有的消息服务器回一条是否收到消息

public class send {
    private static final String QUEUE_NAME="MXH_confirm";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //创建一个连接
        Connection connection = ConnectionUtil.getConnection();
        //从连接中获取一个通道
        Channel channel = connection.createChannel();
        //创建一个消息队列  QUEUE_NAME:队列名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        try {

          channel.confirmSelect();//开启confirm模式
            String msg = "hello,rqbbitmq你好";
            int i =1/0;
            for(int p =0;p<10;p++)
            {
                channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            }

            if(channel.waitForConfirms())
            {
                System.out.println("发送成功");
            }else
            {
                System.out.println("发送失败");
            }
        }catch (Exception e)
        {
            System.out.println("发送失败");
        }

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

异步模式

原理

Channel对象提供的Confirmlistener回调方法只包含deliveryTag (当前Chanel发出的消息序号)。我们需要自己为每一个Channel維护一个unconfirm的消息序号集合,每publish 一条数据,集合中元素加1.每回调一次handleAck方法,unconfirm 集合删掉相应的一条(multiple=false)或多条(multiple=true)记录。从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构。

创建一个生产者:

public class send {
    private static final String QUEUE_NAME="MXH_confirm";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException
    {
        //创建一个连接
        Connection connection = ConnectionUtil.getConnection();
        //从连接中获取一个通道
        Channel channel = connection.createChannel();
        //创建一个消息队列  QUEUE_NAME:队列名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

            channel.confirmSelect();//开启confirm模式
            ----未确认的消息标识
            final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
            ----添加通道监听
            channel.addConfirmListener(new ConfirmListener()
            {
                @Override
               -----消息发送成功
                public void handleAck(long deliveryTag , boolean mulpitle) throws IOException
                {
                    if (mulpitle) {
                        confirmSet.headSet(deliveryTag  + 1).clear();
                    } else {
                        confirmSet.remove(deliveryTag );
                    }
                }

                @Override
                -----消息发送失败
                public void handleNack(long deliveryTag , boolean mulpitle) throws IOException {
                    if (mulpitle) {
                        confirmSet.headSet(deliveryTag  + 1).clear();
                    } else {
                        confirmSet.remove(deliveryTag );
                    }
                }
            });

            String msg = "hello,rqbbitmq你好";
        while(true)
        {
            long setNo = channel.getNextPublishSeqNo();
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            confirmSet.add(setNo);
        }
//        channel.close();
//        connection.close();
    }
}

spring整合rabbitMQ

RabbitMQ之订阅模式与主题模式,消息确认机制_第5张图片RabbitMQ之订阅模式与主题模式,消息确认机制_第6张图片

RabbitMQ之订阅模式与主题模式,消息确认机制_第7张图片

你可能感兴趣的:(RabbitMQ,rabbitmq)