RabbitMQ

RabbitMQ

1. RabbitMQ介绍

  • 分布式系统中的重要组件,主要解决,异步处理,应用解耦,流量削峰等问题
  1. 异步处理

RabbitMQ_第1张图片

  1. 应用解耦
    • 作为典型的生产者消费者模型,实现了订单系统和库存系统的应用解耦。

RabbitMQ_第2张图片

  1. 流量削峰
  • 对高并发场景进行流量控制,抛弃超过长度的队列。

1.1 各组件功能

RabbitMQ_第3张图片

  • Publisher:消息的生产者。

  • Virtual Host:虚拟主机。各个主机拥有自己的配置(队列、交换机)。

  • Exchange:交换器:接受消息,转发给队列。

  • Banding:绑定。将消息绑定至路由。

  • Queue:消息队列。

  • Channel:信道,复用TCP连接,独立的双向数据流通道。包括旗下的Connection(一个TCP连接)

  • Consumer:消息的消费者

2. RabbitMQ使用

2.1 测试连接

  • 一个专门获取连接的工具类。
public class ConnectionUtil {
     
    public static Connection getConnection() throws Exception{
     
        ConnectionFactory factory = new ConnectionFactory();
        //设置连接信息
        factory.setHost("IP");
        factory.setPort(5672);
        factory.setVirtualHost("/lagou");
        factory.setUsername("zz");
        factory.setPassword("123456");
       //获得连接
        Connection connection = factory.newConnection();
        return connection;
    }

    public static void main(String[] args) throws Exception {
     
        Connection connection = getConnection();
        System.out.println(connection);
        //amqp://zz@IP:5672//lagou
    }
}

2.2 RabbitMQ模式

  • 点对点模式:
    • 消息队列(queue),发送者(sender),接收者(receiver)
    • 发送方与接受方不存在依赖关系。

1.简单模式

RabbitMQ_第4张图片

  • 单纯的存储、转发消息。

  • 生产者:

    public class sender {
           
        public static void main(String[] args) throws Exception {
           
            //获取连接
            Connection connection = ConnectionUtil.getConnection();
            //创建信道
            Channel channel = connection.createChannel();
            //声明队列
            //队列名、是否持久化、是否排外、是否自动删除(连接为0时删除)、队列参数
            channel.queueDeclare("no1", false, false, false, null);
            //发布消息
            String msg = new String("hello2 RabbitMQ");
            //交换机名、队列名、消息属性、字节内容
            channel.basicPublish("", "no1", null, msg.getBytes());
            System.out.println("已发送:" + msg);
    
            //资源释放
            channel.close();
            connection.close();
        }
    }
    
    
  • 消费者:

    public class Recer {
           
        public static void main(String[] args) throws Exception {
           
            //获取连接
            Connection connection = ConnectionUtil.getConnection();
            //创建信道
            Channel channel = connection.createChannel();
            //从信道中获取消息
            DefaultConsumer consumer = new DefaultConsumer(channel) {
           
                @Override //交付处理(收件人信息,包裹上的快递标签,协议的配置,消息)
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
           
                    System.out.println(new String(body));//body为消息内容
    
                }
            };
            //监听队列 ACK机制 true自动确认
            channel.basicConsume("no1", true, consumer);
        }
    }
    

2. 工作队列模式

  • 多个消费者共同处理消息。

RabbitMQ_第5张图片

  • 这里生产者发布100条消息,不同消费者处理速度不同。

  • 生产者:

//省略获取连接、创建信道、声明队列、与关闭资源
 for (int i = 0; i < 100; i++) {
     
       String msg = new String("hello " + i);
    	//发布100条消息到no1队列上
       channel.basicPublish("", "no1", null, msg.getBytes());
        System.out.println("已发送:" + msg);
  }
  • 消费者(两台消费者):

    • 在两个消费者中定义了静态变量I计数。
    • 消费者中也声明队列,在此处的作用是获取!
    • 尽管不同的消费者处理速度不同,但RebbitMQ并不清楚,如果要做到按效率分配,需要指定::channel.basicQos(1);并配合手动确认。
    public class Recer1 {
           
        static int i = 1; // 记录执行次数
        public static void main(String[] args) throws Exception {
           
    
         //获取队列
         channel.queueDeclare("no1", false, false, false, null);
         //指定分配方式(按效率来)
         channel.basicQos(1);
    	//手动确认
         hannel.basicAck(envelope.getDeliveryTag(), false);  
         
           //手动确认
          channel.basicConsume("no1", false, consumer);
        }
    }
    

避免消息堆积:

  1. 如上述方式,多消费者监听同一队列(no1)
  2. 消费者处理消息时,使用多线程异步消费。

3.发布订阅模式(fanout)

  • 声明路由关键字:fanout

  • P为生产者、红色为具体队列、bind为绑定。

  • X是路由:将生产者发布的信息,绑定到不同的队列。

  • 运行过程:启动生产者(创建路由)、启动消费者、再次启动生产者。

  • 生产者:
    • 声明路由
public class sender {
     
    public static void main(String[] args) throws Exception {
     

        //声明路由
        /*fanout:
        不处理路由键(只需要将队列绑定到路由上,发送到路由的消息都会被转发到与该
路由绑定的所有队列上)*/
        channel.exchangeDeclare("exchange","fanout");
        String msg="hello 消息订阅模式";
        channel.basicPublish("exchange","",null,msg.getBytes());

    }
}
  • 消费者:
    • 声明队列,将队列绑定至路由
//获取连接、创建信道
//声明队列
channel.queueDeclare("no3", false, false, false, null);
/*
参数1:队列名
参数2:交换器名称
参数3:路由键(暂时无用,""即可)
*/
//将消息队列绑定路由
channel.queueBind("no3", "exchange", "");

4.路由模式(direct)

  • 声明路由关键字:direct

  • 根据路由键,定向分发消息给不同的队列。

RabbitMQ_第6张图片

  • 生产者:
//声明路由
// direct:根据路由键进行定向分发消息
channel.exchangeDeclare("exchange_direct","direct");
String msg="hello 路由模式";
channel.basicPublish("exchange_direct","delete",null,msg.getBytes());
  • 消费者1:
channel.queueDeclare("no1",false,false,false,null);
//绑定路由.路由键是delete绑定到队列1
channel.queueBind("no1","exchange_direct","delete");
  • 消费者2:
channel.queueDeclare("no2",false,false,false,null);
//绑定路由.路由键是insert绑定到队列1
channel.queueBind("no2","exchange_direct","insert");
  • 一样的,运行要先创建路由。

5.通配符模式(topic)

  • 声明路由关键字:topic

  • 路由键支持模糊匹配:

    *:一个字符

    #:N

RabbitMQ_第7张图片

6.持久化

  • 在消费者未确认前、未消费前。防止信息丢失。

  • 路由和队列均持久化

  • 生产者:

    channel.exchangeDeclare("exchange_topic","topic",true);//路由持久化
    String msg="hello 通配符模式";
    //参数3:消息持久化
    channel.basicPublish("exchange_topic","user.select", MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes());
       
    
  • 消费者:

    channel.queueDeclare("no5",true,false,false,null);
    //绑定路由  路由键:user.开头
    channel.queueBind("no5","exchange_topic","user.#");
    

2.3 Spring整合Rabbit

1. 生产者Spring.xml

  • 配置连接
    
    
  • 配置队列、队列管理、路由(队列绑定)
    
    <rabbit:queue name="queue1"/>
    
    <rabbit:admin connection-factory="connectionFactory"/>
    
    
    <rabbit:topic-exchange name="spring_topic_exchange">
        <rabbit:bindings>
            <rabbit:binding queue="queue1" pattern="msg.#" >rabbit:binding>
        rabbit:bindings>
    rabbit:topic-exchange>
    
    <bean id="jsonMessageConverter"
   class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
     
  • Rabbit模板对象、消息确认机制
    
    <rabbit:template
        id="rabbitTemplate"
        connection-factory="connectionFactory"                    
        exchange="spring_topic_exchange"
        message-converter="jsonMessageConverter"
    />

2. 生产者发送消息

public class Sender {
     
    public static void main(String[] args) {
     
        //加载配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        //从容器中获得模板对象
        RabbitTemplate template = context.getBean(RabbitTemplate.class);
		//消息发送
        HashMap<String, String> map = new HashMap();
        map.put("name", "张三");
        map.put("age", "18");
        //转换并发送
        template.convertAndSend("msg.user", map);
        context.close();
    }
}

3.消费者Spring.xml

  • 配置连接、配置队列、配置队列管理。前三步骤与生产端一致。

  • 注解扫描、配置监听、


<context:component-scan base-package="listener"/>

<rabbit:listener-container connection-factory="connectionFactory">
    <rabbit:listener ref="consumerListener" queue-names="queue1"/>
 rabbit:listener-container>

4. 消费者接收消息

  • 实现MessageListene接口,并重写onMessage()方法,方法内对消息处理。
public class ConsumerListener implements MessageListener {
     
    //json装换类 序列化与反序列化 使用最多的类,用来转换json的

    private static final ObjectMapper MAPPER = new ObjectMapper();
    public void onMessage(Message message) {
     
        try {
     
            //message->json
            JsonNode jsonNode = MAPPER.readTree(message.getBody());
            String name = jsonNode.get("name").asText();
            String age = jsonNode.get("age").asText();
            System.out.println("获取到:" + name + age);
        } catch (IOException e) {
     
            e.printStackTrace();
        }
    }
}
  • 项目启动,启动Spring配置文件,并用System.in.read();命令防止程序结束

2.4 消息确认机制

  • 原始的信道事务方式,使用
channel.txSelect(): 开启事务
channel.txCommit() :提交事务
channel.txRollback() :回滚事务

命令控制。在Spring的封装下,使用Confirm机制。

1. Spring.xml


<rabbit:connection-factory
	publisher-confirms="true"
/>

    <rabbit:template
        confirm-callback="msgSendConfirmCallback"
    />


<bean class="confirm.MsgSendConfirm" id="msgSendConfirmCallback"/>

2.确认处理类

@Component
public class MsgSendConfirm implements RabbitTemplate.ConfirmCallback {
     
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
     
        /*
         correlationData 消息id
         b 确认状态
         s 失败原因
         */
        if (b) {
     
            System.out.println("确认成功!");
        } else {
     
            System.out.println("发送失败!");
        }
    }
}

2.5 消费端限流

  • 注意:对生产端限流是不科学的,应该对消费端限流,用于保持消费端的稳定

1.Spring.xml

  • prefetch:一次性消费的消息数量。未确认前不往队列中派送消息。



<rabbit:listener-container connection-factory="connectionFactory"
prefetch="3" acknowledge="manual">
<rabbit:listener ref="consumerListener" queuenames="test_spring_queue_1" />
rabbit:listener-container>

2.6 过期时间TTL

  • TTL:time to live。还能活多久?

1. 设置队列

  • 创建队列时指定过期时间

<rabbit:queue name="queue_ttl" auto-declare="true">			<rabbit:queue-arguments>
	<entry key="x-message-ttl" value-type="long" value="5000">entry>
	rabbit:queue-arguments>
rabbit:queue>

2. 设置消息

  • 在创建消息时指定
RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
// 创建消息配置对象
MessageProperties messageProperties = new MessageProperties();
// 设置消息过期时间
messageProperties.setExpiration("6000");
// 创建消息
Message message = new Message("6秒后自动删除".getBytes(),
messageProperties);
// 发送消息
rabbitTemplate.convertAndSend("msg.user", message);
  • 同时设置队列、消息的过期时间时,小值生效。

2.7 死信队列

  • DLX(Dead Letter Exchanges)死信交换机。
  • 未被及时消费的消息分配到DLX死信交换机中。
  • 未被及时消费的原因
    1. 消息被拒绝
    2. 消息超时未被消费
    3. 达到队列最大长度。

1.Spring.xml

  • 配置连接、配置模板对象、创建定向交换机、设置超时队列、创建死信交换机、死信队列
 
    <rabbit:connection-factory
        id="connectionFactory"
        host="IP"
        port="5672"
        username="zz"
        password="123456"
        virtual-host="/lagou"
        publisher-confirms="true"
    />
    <rabbit:admin connection-factory="connectionFactory"/>

    
    <rabbit:template
        id="rabbitTemplate"
        connection-factory="connectionFactory"                    
        exchange="exchange1"
    />

    
    <rabbit:direct-exchange name="exchange1">
        <rabbit:bindings>
            <rabbit:binding key="dlx_ttl" queue="ttl_quque"/>
        rabbit:bindings>
    rabbit:direct-exchange>

    
    <rabbit:queue name="ttl_quque">
        <rabbit:queue-arguments>
            <entry key="x-message-ttl" value-type="long" value="5000" />
            <entry key="x-dead-letter-exchange" value="dlx_exchange"/>
        rabbit:queue-arguments>
    rabbit:queue>

    
    <rabbit:direct-exchange name="dlx_exchange">
        <rabbit:bindings>
            <rabbit:binding key="dlx_ttl" queue="qlx_quque"/>
        rabbit:bindings>
    rabbit:direct-exchange>

    
    <rabbit:queue name="qlx_quque"/>

2.9 延迟队列

  • 延迟队列:过期+死信。

<rabbit:listener-container connection-factory="connectionFactory" 	prefetch="3"
acknowledge="manual">
<rabbit:listener ref="consumerListener" queue-names="dlx_queue" />
rabbit:listener-container>

3.Rabbit集群

3.1 RabbitMQ安装启动

# erlang
rpm -ivh erlang-21.3.8.16-1.el7.x86_64.rpm
# socat
rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm
# RabbitMQ
rpm -ivh rabbitmq-server-3.8.6-1.el7.noarch.rpm

#启动管理插件
rabbitmq-plugins enable rabbitmq_management
# 启动RabbitMQ
systemctl start rabbitmq-server.service
systemctl status rabbitmq-server.service
 systemctl restart rabbitmq-server.service
systemctl stop rabbitmq-server.service

3.2 测试

# 创建账号
rabbitmqctl add_user laosun 123456
# 设置用户角色
rabbitmqctl set_user_tags laosun administrator
# 设置用户权限
[root@localhost opt]# rabbitmqctl set_permissions -p "/" laosun ".*" ".*" ".*"
  • 端口:5672(客户端连接) 15672(网页管理端) 25672(集群)

3.3 集群搭建

  1. 修改 /etc/hosts 映射文件
127.0.0.1 name1 localhost localhost.localdomain localhost4
localhost4.localdomain4
::1 nam1 localhost localhost.localdomain localhost6
localhost6.localdomain6
192.168.204.141 nam1
192.168.204.142 name2

  • name1、name2对应各服务器的主机名
  1. 拷贝.erlang.cookie
scp /var/lib/rabbitmq/.erlang.cookie 192.168.204.142:/var/lib/rabbitmq
# 重启服务器
reboot
  1. 加入集群
# 启动rabbitmq服务
systemctl start rabbitmq-server
# 加入集群节点
rabbitmqctl stop_app
rabbitmqctl join_cluster rabbit@A
rabbitmqctl start_app
# 查看节点状态
rabbitmqctl cluster_status
  • 搭建集群结构之后,之前创建的交换机、队列、用户都属于单一结构,在新的集群环境中是 不能用的。
  • 在新集群中重新添加用户。

3.4 镜像模式

rabbitmqctl set_policy xall "^" '{"ha-mode":"all"}'
  • xall: 策略名,自定义
  • pattern:队列的匹配模式。^:任意队列
    ost.localdomain localhost6
    localhost6.localdomain6
    192.168.204.141 nam1
    192.168.204.142 name2

+ name1、name2对应各服务器的主机名

2. 拷贝.erlang.cookie

```cmd
scp /var/lib/rabbitmq/.erlang.cookie 192.168.204.142:/var/lib/rabbitmq
# 重启服务器
reboot
  1. 加入集群
# 启动rabbitmq服务
systemctl start rabbitmq-server
# 加入集群节点
rabbitmqctl stop_app
rabbitmqctl join_cluster rabbit@A
rabbitmqctl start_app
# 查看节点状态
rabbitmqctl cluster_status
  • 搭建集群结构之后,之前创建的交换机、队列、用户都属于单一结构,在新的集群环境中是 不能用的。
  • 在新集群中重新添加用户。

3.4 镜像模式

rabbitmqctl set_policy xall "^" '{"ha-mode":"all"}'
  • xall: 策略名,自定义
  • pattern:队列的匹配模式。^:任意队列
  • ‘{“ha-mode”:“all”}’:高可用模式,all:任意节点

你可能感兴趣的:(rabbitmq,java,队列)