RabbitMQ

RabbitMQ

简介

官方文档:https://www.rabbitmq.com/documentation.html

RabbitMQ是实现了AMQP协议的高性能开源的消息队列技术。RabbitMQ支持多种语言、多种框架的客户端调用。它有生产者和消费者两种角色,生产者是将消息数据发送给MQ处理,消费者是监听MQ队列中产生的消息,获取消息。

特点及适用场景:

  • 分布式系统下,具有异步、削峰、限流、负载均衡等高级功能;
  • 实现生产者和消费者之间的解耦;
  • 拥有持久化机制(收到消息后,会先提交日志文件后才发送响应);
  • 实现服务间的异步通讯;
  • 实现定时任务;

核心组件:

  • 连接对象:使用基于TCP的长连接,但并不是直接调用连接操作,不需要连接池提升并发性能,并发由MQ集中处理;直接操作是基于长连接建立channel连接信道对象来实现,消耗资源少,可以频繁创建、销毁;
  • 交换机(exchange):由并发语言erlang编写,并发能力极高,生产者是将消息发送给交换机处理,由交换机处理并发。
  • 队列组件(queue):在内存中管理,有固定的结构,绑定对应的交换机,如果交换机没有队列绑定,则该消息会直接消失,若队列中有多个消费者,则消息将以循环的方式发送给消费者。

安装

检查安装erlang:

  • 检查erl版本,若版本不对需要先卸载(在rabbit官网查看对应版本);
  • 卸载erl(执行yum list | grep erlang只要有结果就执行yum remove xxx);
  • erlang官网下载对应版本的源码包;
  • 安装erl依赖:yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc
  • 解压并进入目录执行:./configure --prefix=/data/otp_erlang --without-javac
  • 安装erlang:make && make install
  • 添加erlang的环境变量;

安装socat(MQ内部调用执行的一些插件命令环境);

  • 搜索下载socat源码包->解压->./configure->make && make install

安装rabbitMQ(上面的方式安装的erl不能安装mq的rpm包);

  • 官网下载:rabbitmq-server-generic-unix-3.7.14.tar.xz;
  • xz -d解压->tar解压;
  • 将sbin目录配置到环境变量;

启动及初始化:

  • 启用web管理页面:rabbitmq-plugins enable rabbitmq_management
  • 启动:rabbitmq-server,加参数-detached为后台运行,关闭:rabbitmqctl stop
  • 添加用户:rabbitmqctl add_user root 1234;(guest只能本地登录)
  • 授予管理员权限:rabbitmqctl set_user_tags admin administrator
  • 在浏览器通过root/1234在http://ip:15672登录(生产者、消费者访问端口是5672);
  • 添加virtual host为"/"的权限;

使用(可以直接通过web页面添加用户了)

  • 添加用户(Admin->Users下);
  • 添加虚拟机(Admin->virtualHost下);
  • 绑定用户与虚拟机(点击用户名->选择虚拟机);

四种交换机

direct(默认)

名称为空字符串的交换机,会使用队列名称作为路由Key的交换机。发送端和接收端都不需要创建或者声明交换机。该交换机具有以下特点:

  • 公平调度:direct会轮训公平的分发给每个消费者(需要消息确认正确);
  • 消息发后即忘:既接受者不知道消息的来源,如果要指定来源,需要包含在消息体中;
  • 消息确认:若消费者消息收到未确认,则不会在发送更多消息,在消息确认之前,可以与rabbitmq断开连接,则消息回到ready状态,发送给其他消费者,也可以拒绝消息(channel.basicReject),根据参数确定是发给其他消费者还是放入死信。(经过测试,当使用QueueingConsumer时,即使没有确认消息,一直调用接收方法也可以收到消息。)

fanout

发布订阅模式,会广播消息到所有绑定该交换机的队列。使用时在需要先创建/声明交换并指定名称和类型(channel.exchangeDeclare("name", "fanout")),还需要将需要接收的队列绑定到该交换机。(该交换机会忽略路由Key参数)

topic

和fanout类似,但可以通过路由Key更灵活的匹配队列。使用该交换机时需要先创建或者声明交换机(channel.exchangeDeclare(“name”, "topic")),同时,在发送消息和绑定队列时还需要指定路由Key。路由Key名称不得超过255字节,用"."分割多个关键字,还可以使用通配符(#:匹配0或者多个字符;*匹配一个分段的关键字)。另外topic和fanout都是没有历史数据的,中途创建的队列不能接收之前的消息。

header

功能和direct一样,不同之处在于它是使用消息的header部分匹配队列,性能很差,没用。

使用模式

https://www.rabbitmq.com/getstarted.html

  1. 简单模式(一对一):交换机收到消息,若没有绑定队列则消息扔到垃圾桶,否则发送给队列,队列接收消息并存储在内存,等待消费者连接监听,消费成功后返回确认。(QQ、短信)
  2. 工作模式(资源争抢):一个队列被多个消费者同时监听,交换机收到消息,发送给绑定的后端队列。(抢红包)
  3. 发布订阅(交换机类型:fanout):交换机后绑定很多队列,收到消息后,交换机复制同步消息到后端所有队列中。(邮件群发、广告)
  4. 路由模式(交换机类型:direct):队列绑定交换机时提供了一个路由key(routingKey),(发布订阅时,所有fanout类型的交换机的后端队列的路由key都是"");(错误消息的接收展示);
  5. 主题模式(交换类型:topic):与路由模式很相似,做到按类划分消息,且路由key可以使用通配符:#(任意字符)、*(没有特殊字符的字符串)。

消息确认机制

生产端:即生产者投递消息后,如果broker收到消息,则会给生产者一个应答,生产者可以接收应答,以确保这条消息正常发送;开启方式如下:

  • 发送消息前,在channel上开启确认模式:channel.confirmSelect()
  • channel上添加监听:channel.addConfirmListener(ConfirmListener listener);根据返回结果确定后续处理;
  • 也可以添加监听:channel.addReturnListener(ReturnListenerlistener),获取更详细的确认消息;

说明:开启消息确认模式后,则在该信道上发布的所有消息都会被指派一个唯一的id,消息保存成功后,信道会异步的向生产者发送一个包含id的确认,如果保存失败则会发送nack消息;

消费端:消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息,也可以通过basicNack(long deliveryTag, boolean multiple, boolean requeue)方法设置消息重回队列;默认情况下,消息的消费具有如下特点:

  • RabbitMQ只有接收到了消费者的确认,才会安全的把消息从队列中删除;
  • 如果消费者在确认消息之前断开了连接或者取消订阅,RabbitMQ会将消息重新发给下一个订阅的消费者;
  • 如果消费者没有确认消息,没有断开连接,也没有取消订阅,则不会给该消费者分发更多消息;

由上可以,要保证消息的精确一次消费,就要求在消息体中包含一个全局唯一的业务id,作为去重的依据;

集群

普通模式:默认集群模式,多个节点仅有相同的元数据,消息只存在于其中一个节点,当消费者从另一个节点拉取时,消息会在集群内部传输;

搭建普通模式:

  • 按单机模式的方法在多台节点安装;
  • 查找文件:find / -name .erlang.cookie
  • 将每个节点的该文件内容设置为一样,且权限为400;接着重启MQ;

镜像模式:把需要的队列做成镜像模式,存在于多个节点,消息实体会主动在镜像节点之间同步;

搭建镜像模式:镜像模式是基于普通模式的,在普通模式的基础上,直接在web页面配置策略即可:admin->Policies;(集群中任意节点启用策略,策略会自动同步到集群节点)

ha-mode

  • all:所有节点都会同步镜像;
  • exactly:指定数量的节点同步镜像;若节点数量少于此数,则所有节点都同步;若多于次数,则当一个包含镜像的节点停止时,不会在另外的节点创建镜像,即不会发生迁移;
  • nodes:指定节点列表同步镜像;

Java客户端

官方文档:https://www.rabbitmq.com/api-guide.html

初始化

初始化连接工厂:

private Channel channel;
@Before
public void init(){
    ConnectionFactory factory= new ConnectionFactory();
    // 配置连接参数(前面创建的用户/虚拟机)
    factory.setUsername("demo");
    factory.setPassword("1234");
    factory.setVirtualHost("/demoHost");
    factory.setHost("192.168.48.101");
    factory.setPort(5672);
    // 也可以这样
    // factory.setUri("amqp://demo:1234@192/168.48.101:5672/demoHost");
    // 从连接工厂获取长连接
    try{
        Connection conn = factory.newConnection();
        //从长连接获取短连接信道对象
        channel=conn.createChannel();
    }catch(Exception e){
        e.printStackTrace();
    }
    // channel.close();
    // conn.close();
}

简单模式

生产者:

  • channel.queueDeclare方法声明/创建队列,方法参数依次为队列名称、是否持久化、是否专属连接、是否自动删除、消息的其他属性配置等,部分参数含义可以参考在web管理的新增队列页面。
  • 通过channel信道的basicPublish方法发送消息,方法参数有:交换机名称、路由Key、消息的属性、消息的byte数组,部分参数含义可以参考web管理的发送信息页面。(简单模式就使用队列名作为路由key)

消费者(已过时,有内存泄漏的风险):

  • channel.queueDeclare方法声明/创建队列(如果已有队列,则该方法可用可不用);
  • channel.basicQos方法设置空闲时最多收到的消息数量;
  • 创建QueueingConsumer对象;
  • channel.basicConsume绑定队列和Consumer,同时设置自动确认/手动确认(自动确认是在消费者刚刚接收到消息就返回确认;手动确认是在手动返回确认前,消息队列会一直保留消息,消费者断开重连还可以接收该消息,直到返回确认,队列才会删除该消息);
  • consumer.nextDelivery()监听并接收消息,若为手动确认,则需要通过channel.basicAck方法返回确认。

消费者:

  • 声明/创建队列;
  • 如果需要,设置空闲时最多收到的消息数;
  • channel.basicConsume方法绑定队列和消费者,消费者使用DefaultConsumer,重载handleDelivery方法;这种方式的消费者,只要进程没有退出,就可以一直接收消息,每次收到消息,就会执行handleDelivery方法。该方式中:
    • 自动确认:先接收队列中所有消息,然后对每个消息执行一次重载的方法完毕就返回确认,
    • 手动确认:每次手动调用channel.basicAck方法后才会接收下一个消息。
//生产者
@Test
public void senderTest() throws IOException {
    channel.queueDeclare("queue", false, false, false, null);
    String msg = "hello rabbitmq";
    channel.basicPublish("", "queue", null, msg.getBytes());
}
//消费者
@Test
public void consumer() throws IOException, InterruptedException {
        channel.queueDeclare(queue, false, false, false, null);
        channel.basicQos(1);
        //该消费者已经过时,因为有内存泄漏的风险
        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queue, false, consumer);
        Delivery delivery = consumer.nextDelivery();
        String msg = new String(delivery.getBody());
        System.out.println("消费者接收到消息:" + msg);
        channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}
//消费者新
@Test
public void consumer02() throws IOException, InterruptedException {
    channel.basicQos(1);
    channel.basicConsume(queue, false, new DefaultConsumer(channel) {
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println(new String(body));
            channel.basicAck(envelope.getDeliveryTag(), false);
        }
    });
    while(true){
        Thread.sleep(1000);
    }
}

工作模式

工作模式具体代码与简单模式大致一样,不同的是有多个消费者进程接收同一个队列的消息;

发布订阅模式

该模式结构为一个生产者和多个消费者,

消费者:在简单模式中消费者的基础上,还需要在监听消息之前:

  • exchangeDeclare方法声明交换机名称和类型;
  • queueBind方法绑定队列和交换机,同时设置路由Key为""。

生产者:在简单模式中生产者的基础上,还需要在发布消息之前:

  • 通过exchangeDeclare方法声明交换机名称和类型,
  • 声明多个消息队列,每个消费只需要绑定一个队列即可;
@Test
public void sender() throws IOException {
    channel.queueDeclare("queue1", false, false, false, null);
    channel.queueDeclare("queue2", false, false, false, null);
    channel.exchangeDeclare("ex01", "fanout");
    for (int i = 0; i < 100; i++) {
        String msg = "fanout mode: " + i;
        channel.basicPublish("ex01", "", null, msg.getBytes());
        System.out.println("生产端发送了" + (i + 1) + "条消息到默认交换机");
    }
}
@Test
public void consumer() throws IOException, InterruptedException {
    channel.queueDeclare("queue1", false, false, false, null);
    channel.basicQos(1);
    QueueingConsumer consumer = new QueueingConsumer(channel);
    channel.basicConsume("queue01", false, consumer);
    channel.exchangeDeclare("ex01", "fanout");
    channel.queueBind("queue01", "ex01", "");
    while (true) {
        Delivery delivery = consumer.nextDelivery();
        String msg = new String(delivery.getBody());
        System.out.println("消费者01接收到消息:" + msg);
        Thread.sleep(10);
        channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
    }
}

路由模式

该模式与发布订阅模式基本一样,不同之处仅仅是:

  • 交换机类型改为:direct
  • 生产者发送消息时路由Key的参数不再是"",而是具体的值;
  • 消费在绑定交换机是添加了路由Key参数。

主题模式

只是在路由模式的基础上,交换机类型改为:topic,消费者路由Key使用了通配符。

定时

RabbitMQ本身没有延迟队列,只能通过死信交换机和消息存活时间来实现;

死信交换机,其实就是个普通的交换机,用于存放过期的消息,满足以下条件的消息,就会放入该交换机:

  • 当消息被拒收,且拒绝参数的requeue是false;
  • 消息的过期时间到了(队列和消息都可以设置过期时间,最终会以小的为准);
  • 队列长度限制满了,排在前面的消息;

实现定时的思路如下:

  • 创建自动过期的消息队列,或者对消息设置过期时间(该队列不设置消费者);
  • 设置消息过期后要进入的交换机;
  • 创建真正的消息处理队列,并绑定导死信交换机;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值