RabbitMQ

RabbitMQ

集群架构
  • 主备模式

    • 热备份,master->slave,当master挂掉以后启用slave为主节点,当master再次启动时,将成为从节点
    image-20210606073732998
    • 主要配置

      image-20210606073917341
  • 远程模式

    • 数据异地容灾难,当单个节点处理不过来的时候,将数据转发到下游集群来处理

    • 远距离通信和辅助,可以实现双活的一种模式

      image-20210606074443010
  • 镜像模式

    • 可以保证数据100%不被丢失
    image-20210606074843843
    • 不支持横向扩展
  • 多活模式

    • 数据异地复制
    • 异地容灾
    • 数据多活
    image-20210606075256317
核心概念

通过普通协议在完全不同的应用之间实现数据共享,基于 c,不适合做大的消息堆积除非消费端足够大足够多

  • AMQP 协议(Advanced Message Queuing Protocol,高级消息队列协议),是具有现代特征的二进制协议,是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间价设计
image-20210606105319045
  • S ever:又称broker,接受客户端链接,实现AMQP实体服务器。

  • Channel:网络通道,机会所有的操作都在Channel中进行,Channel是进行消息读写的通道。客户端科技简历多个Channel,每个Channel代表一个会话任务

  • Message:消息,服务器和应用程序之间 传送的数据,由properties和body组成。properties可以对消息进行修饰,比如晓得优先级、延迟等高级特性;body则就是消息体内容。

  • Virtual host:虚拟地址,用于进行逻辑隔离,最上层的消息路由。一个Virtual host里面不能有相同名称的exchange、Queue

  • exchange:交换机,接受消息,根据路由键转发消息到绑定的队列

  • Bingding:exchange和Queue之间的虚拟链接,Bingding中可以包含routing key

  • routing key:一个路由规则,虚拟机可以用它来确定如何路由一个特定消息

  • Queue:也称为Message Queue,消息队列,保存消息并将它们转发给消费者

image-20210606214914467 image-20210606215240444
安装

下载环境包

安装依赖包

yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz -y

配置主机名称

vi /etc/hostname

lhy-151

vi /etc/hosts

192.168.4.151 lhy-151

安装程序包

rpm -ivh erlang-18.3.4.5-1.el7.centos.x86_64.rpm

rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm

rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

修改配置

vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app

修改 {loopback_users, [<<“guest”>>]} 只保留{loopback_users, [“guest”]}

启动 使用 lsof -i:5672 查看端口是否被监听

/etc/init.d/rabbitmq-server start | stop | status | restart

安装管理插件 查看是否成功: lsof -i:15672

rabbitmq-plugins enable rabbitmq_management

访问

http://192.168.4.151:15672/

生产者消费者模型

生产者

 //创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //ip
        connectionFactory.setHost("192.168.0.106");
        //端口
        connectionFactory.setPort(5672);
        //业务域(订单域、注册域。。。)类似mysql中的某个数据库
        connectionFactory.setVirtualHost("/");

        //创建链接
        Connection connection = connectionFactory.newConnection();

        String queryName = "test001";

        Channel channel = connection.createChannel();
        // 队列名称、是否持久化、独占队列、是否自动删除,队列的属性
        channel.queueDeclare(queryName, false, false, false, null);

        Map<String, Object> headers = new HashMap<>();

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                //消息持久化为2
                .deliveryMode(2)
                //字符集
                .contentEncoding("UTF-8")
                //header头
                .headers(headers)
                .build();

        for (int i = 0; i < 5; i++) {
            String msg="你好罗恒"+i;
             //不指定exchange使用默认的exchange 按routingKey与queues名称匹配
            channel.basicPublish("",queryName,properties,msg.getBytes());
        }

消费者

 //创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置链接ip
connectionFactory.setHost("192.168.0.106");
//设置链接端口
connectionFactory.setPort(5672);
//业务域(订单域、注册域。。。)类似mysql中的某个数据库
connectionFactory.setVirtualHost("/");

//创建链接
Connection connection = connectionFactory.newConnection();

String queryName = "test001";
//创建频道,使用频道与mq通信,多个频道可以复用connection,而不用每次都去销毁和创建链接
Channel channel = connection.createChannel();
//创建消费者
QueueingConsumer consumer = new QueueingConsumer(channel);

// 设置频道的队列名称,是否自动ack(发送ack后消息会在队列中删除,重要的消息需要手动发送ack以确保消息被正确消费了),消费者(通过消费者获取消息)
channel.basicConsume(queryName,true,consumer);

while (true){
    //获取队列中的消息,没有消息时会阻塞线程,
    QueueingConsumer.Delivery delivery = consumer.nextDelivery();
    //通过交付类获取消息的相信信息
    byte[] body = delivery.getBody();
    System.out.println(new String(body));


}
exchange交换机

接受消息,并根据路由键(Routing key )转发消息到所绑定的队列,属性如下:

  • name:交换机名称

  • type:交换机类型

    • direct

      • 如果发送消息时不发送exchange名称,Routing key 与队列名称完全匹配才会被队列收到

      • 如果发送消息时发送exchange名称,那么发送时消息时的 Routing key 与exchange和队列绑定时的 Routing key 完全匹配,该队列才会收到消息

      • 消费者

         //创建连接工厂
                ConnectionFactory connectionFactory = new ConnectionFactory();
                //ip
                connectionFactory.setHost("192.168.4.151");
                //端口
                connectionFactory.setPort(5672);
                //业务域(订单域、注册域。。。)类似mysql中的某个数据库
                connectionFactory.setVirtualHost("/");
        
                //创建链接
                Connection connection = connectionFactory.newConnection();
                Channel channel = connection.createChannel();
                //生名
                String exChangeName="test_direct_exchange";
                String exChangeType="direct";
                String queueName="test_direct_queue";
                String routingKey="test_direct_routingKey";
        
                //创建一个 exchange
                channel.exchangeDeclare(exChangeName,exChangeType);
                //创建一个 queue
                channel.queueDeclare(queueName,true,false,false,null);
                //绑定 queue与exchange并设置routingKey
                channel.queueBind(queueName,exChangeName,routingKey);
                //创建消费者
                QueueingConsumer consumer = new QueueingConsumer(channel);
        
        
                channel.basicConsume(queueName,true,consumer);
                while (true){
                    QueueingConsumer.Delivery delivery = consumer.nextDelivery();
                    String msg = new String(delivery.getBody());
                    System.out.println(msg);
                }
        
        
      • 生产者

          //创建连接工厂
                ConnectionFactory connectionFactory = new ConnectionFactory();
                //ip
                connectionFactory.setHost("192.168.4.151");
                //端口
                connectionFactory.setPort(5672);
                //业务域(订单域、注册域。。。)类似mysql中的某个数据库
                connectionFactory.setVirtualHost("/");
        
                //创建链接
                Connection connection = connectionFactory.newConnection();
                Channel channel = connection.createChannel();
        
                String exChangeName="test_direct_exchange";
                String routingKey="test_direct_routingKey";
        
                String msg = "罗恒一发来的消息";
        		//发送消息 指定 exChange和 routingKey
                channel.basicPublish(exChangeName,routingKey,null,msg.getBytes());
        
    • topic exchange将routeKey 和某个topic进行模糊匹配。此时队列需要绑定一个Topic类型的exchange

      • 符号 “#” 匹配一个或多个例如:log.# 能够匹配到 “log.info.oa”
      • 符号 “*” 只能匹配1个词 例如:log.* 能偶匹配到 “log.erro”
    • fanout 广播模式,发送到交换机的消息会被转发到所有与该交换机绑定的队列上。效率最高

    • headers 消息头模式

  • Durabillity :是否持久化

  • Auto delete: 当exchange上绑定的队列全部删除后,自动删除该exchange

  • internal:当前exchange是否用于mq内部使用默认false

  • Arguments:扩展参数,用户扩展amqp协议自制定化使用

生产端可靠性投递与消费端幂等性
如何保证消息成功投递

对发送的消息进行记录并标记状态,等待mq的ack后标记为成功。期间使用定时任务扫描发送记录对超时的log进行重新发送

幂等性

保证消息在重复发送时不重复消费

(1)比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧

(2)比如你是写redis,那没问题了,反正每次都是set,天然幂等性

(3)比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。

confirm 确认机制

消息确认,是指生产者投递消息后,如果broker收到消息,则会给我们生产者一个应答,生产者进行接受应答,用来确认这条消息是否正常发送到 broker。这一过程是异步实现

如何实现confirm确认消息

  • 第一步:在channel上开启确认模式:channel.confirmSlect()

  • 第二部:在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送,或记录或记录日志等待后续处理!

     //创建连接工厂
            ConnectionFactory connectionFactory = new ConnectionFactory();
            //ip
            connectionFactory.setHost("192.168.0.107");
            //端口
            connectionFactory.setPort(5672);
            //业务域(订单域、注册域。。。)类似mysql中的某个数据库
            connectionFactory.setVirtualHost("/");
    
            //创建链接
            Connection connection = connectionFactory.newConnection();
            Channel channel = connection.createChannel();
    
            String exChangeName="test_direct_exchange";
            String exChangeType="direct";
            String queueName="test_direct_queue";
            String routingKey="test_direct_routingKey";
    
            String msg="luohyengyi发送的";
    
            //开启确认模式
            channel.confirmSelect();
    
    
            channel.addConfirmListener(new ConfirmListener() {
                @Override
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                    System.out.println("--------ok-----------");
                }
    
                @Override
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                    System.out.println("-----------error--------");
                }
            });
    
            channel.basicPublish(exChangeName,routingKey,null,msg.getBytes());
    
    
return 消息机制

retunrn Listener 用于处理一些不可路由的消息

消息生产者,通过指定一个exchange和routingkey。吧消息送达到某一个队列中去,然后我们的消费者监听队列,进行消费处理操作。但是在某些情况下,如果我们在发送消息的时候,当前的exchange不存在或者指定的路由key,路由不到,这个时候如果我们需要监听这个种不可达的消息,就要使用 return Listener!

在基础api中有一个关键的配置项

  • Mandatory :如果为true,则监听器会接受到路由不可到达的消息,然后进行后续处理,二u过为false,那么broker端自动删除该消息。

     //创建连接工厂
            ConnectionFactory connectionFactory = new ConnectionFactory();
            //ip
            connectionFactory.setHost("192.168.0.107");
            //端口
            connectionFactory.setPort(5672);
            //业务域(订单域、注册域。。。)类似mysql中的某个数据库
            connectionFactory.setVirtualHost("/");
    
            //创建链接
            Connection connection = connectionFactory.newConnection();
            Channel channel = connection.createChannel();
    
            String exChangeName="test_direct_exchange";
            String exChangeType="direct";
            String queueName="test_direct_queue";
            String routingKey="test_direct_routingKey123";
    
            String msg="luohyengyi发送的";
    
            //开启确认模式
            channel.confirmSelect();
    
    
            channel.addReturnListener(new ReturnListener() {
                @Override
                public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    System.out.println("*******************handleReturn*****");
                    System.out.println("replyCode:"+replyCode);
                    System.out.println("replyText:"+replyText);
                    System.out.println("exchange:"+exchange);
                    System.out.println("routingKey:"+routingKey);
                    System.out.println("body:"+new String(body));
                }
            });
            boolean mandatory= true;
            channel.basicPublish(exChangeName,routingKey,mandatory,null,msg.getBytes());
    
消费端限硫

RabbitMq 提供一种qos(服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过基于consume或者channek设置qos的值)未被确认前,不进行消费新的消息

  • void BasicQos(int prefetchSize,ushort prefetchCount,bool globle)

    • prefetchSize 报文大小
    • prefetchCount 提示mq不要同时给一个消费端推送多余N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack
    • globle 是否将上面的设置应用于channel,true所有channel下的消费者都使用这个配置。fasle 只有这个消费者使用这个配置
    ConnectionFactory connectionFactory = new ConnectionFactory();
    //设置链接ip
    connectionFactory.setHost("192.168.4.29");
    //设置链接端口
    connectionFactory.setPort(5672);
    //业务域(订单域、注册域。。。)类似mysql中的某个数据库
    connectionFactory.setVirtualHost("/");
    
    //创建链接
    Connection connection = connectionFactory.newConnection();
    
    String queryName = "test001";
    //创建频道,使用频道与mq通信,多个频道可以复用connection,而不用每次都去销毁和创建链接
    Channel channel = connection.createChannel();
    //创建消费者
    QueueingConsumer consumer = new QueueingConsumer(channel);
    
    //同时只能消费1条消息
    channel.basicQos(0,1,false);
    // 设置频道的队列名称,是否自动ack(发送ack后消息会在队列中删除,重要的消息需要手动发送ack以确保消息被正确消费了),消费者(通过消费者获取消息)
    channel.basicConsume(queryName,false,consumer);
    
    while (true){
        //获取队列中的消息,没有消息时会阻塞线程,
        QueueingConsumer.Delivery delivery = consumer.nextDelivery();
        //通过交付类获取消息的相信信息
        byte[] body = delivery.getBody();
        System.out.println(new String(body));
         channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
    
    }
    
消费端ack与重回队列

消费成功通知ack消费失败nack,消费端进行消费的时候,如果由于业务异常我们可以进行日志的记录,然后进行补偿!

//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置链接ip
connectionFactory.setHost("192.168.4.29");
//设置链接端口
connectionFactory.setPort(5672);
//业务域(订单域、注册域。。。)类似mysql中的某个数据库
connectionFactory.setVirtualHost("/");

//创建链接
Connection connection = connectionFactory.newConnection();

String queryName = "test001";
//创建频道,使用频道与mq通信,多个频道可以复用connection,而不用每次都去销毁和创建链接
Channel channel = connection.createChannel();
//创建消费者
QueueingConsumer consumer = new QueueingConsumer(channel);

// 设置频道的队列名称,是否自动ack(发送ack后消息会在队列中删除,重要的消息需要手动发送ack以确保消息被正确消费了),消费者(通过消费者获取消息)
channel.basicConsume(queryName,false,consumer);

while (true){
    //获取队列中的消息,没有消息时会阻塞线程,
    QueueingConsumer.Delivery delivery = consumer.nextDelivery();
    //通过交付类获取消息的相信信息
    byte[] body = delivery.getBody();
    System.out.println(new String(body));
    //手动ack,nack可以选择重回队列,回到队列的尾部
    if ((Integer) delivery.getProperties().getHeaders().get("flag")==0){
        channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,false);
    }else {
        channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);

    }

}
TTL队列/消息

TTL 生存时间

  • TTL队列

    队列中的消息都有统一的生存时间,超过时间消息会被删除

  • TTL消息

    一条消息有自己的生存时间,这条消息在mq中的存活时间过期后会被删除,在发送消息时指定

死信队列
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值