在项目中有商品的变化,包括订单的一些状态变化需要用到RabbitMQ通知买家或者卖家,所以自己就去学习了一下。
在springBoot中使用RabbitMQ
1.RabbitMQ是什么?
采用AMQP高级消息队列协议的一种消息队列技术,最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦
1.1什么是AMQP?
Advanced Message Queuing Protocol, 高级队列协议,协议层规范,就像http协议只要按照规范在任何平台都能用。RabbitMQ使用Erlang语言编写,支持多种协议,高可用,支持消息集群以及多语言客户端等等。
1.1.1 AMQP 中包含的主要元素
- 生产者(Producer):向Exchange发布消息的应用。
- 消费者(Consumer):从消息队列queue中消费消息的应用。
- 消息队列(Message Queue):服务器组件,用于保存消息,直到发送给消费者。
- Queue:消息载体;每个消息都会被投入到一个或多个队列。
- 消息(Message):传输的内容。
- 交换器(exchange):路由组件,接收Producer发送的消息,并根据Routing Key转发给消息队列queue。
- Routing Key:路由关键字,exchange根据这个Routing Key进行消息投递到队列queue。
- 绑定器(Binding):把exchange和queue按照路由规则绑定起来。
1.1.2 exchange 与 Queue 的路由机制
生产者发消息不需要指定Queue,消费者可以指定Queue绑定到某个RoutingKey和某个Exchange,也可以不指定Queue,就只根据某个Exchange和某个RoutingKey接受到消息。
Exchange 将消息发送到哪一个queue是由exchange type 和 Binding绑定规则决定的,目前常用的有3种exchange,Direct exchange, Fanout exchange, Topic exchange :
1.2 什么是JMS
java message server 它通过统一的java api层面的标准使得多个消息客户端来通过JMS交互,大部分消息中间件提供商都对JMS提供支持。比如ActiveMQ,JMS类似于JDBC,而这个ActiveMQ就像数据库驱动,JMS是一个规范,是一个标准,而ActivMQ则是一个具体的实现。JMS包括2种消息发布模型,点对点,发布者订阅者,只支持java平台
2.为什么要使用RabbitMQ?
- 在分布式系统下具备异步,削峰,负载均衡等一系列高级功能
- 拥有持久化的机制,进程消息,队列中的信息也可以保存下来。
- 实现消费者和生产者之间的解耦
- 对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作
- 可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单
3.使用RabbitMQ的场景
- 服务间异步通信
- 顺序消费
- 定时任务
- 请求削峰
4.RabbitMQ在springBoot中的使用
4.1 创建一个springBoot工程
选择rabbitMQ依赖
4.2 配置rabbitMQ
4.3设置Exchage策略
RabbitMQ提供了4种不同的Exchange策略,分别是Direct, Fanout,Topic,Header
- Direct:所有发送到DirectExchange的消息将会被转发到RouteKey中指定的Queue
- Fanout:所有发送到FanoutExchange的消息将会被转发到与该Exchange绑定(Binding)的所有的Queue
- Topic:所有发送到TopicExchange的消息被将会被转发到所有关心Routekey指定Topic的Queue上,Exchange将RouteKey和某Topic进行模糊匹配。
- Header:取消了RouteKey,使用了header中的key/value匹配队列
4.3.1 Direct
Direct的定义是 所有发送到DirectExchange的消息将会被转发到RouteKey中指定的Queue所有发送到DirectExchange的消息将会被转发到RouteKey中指定的Queue
先创建静态变量,用来记录状态的变化
package org.java.rabbitmq.base;
public class AmqpExchange {
/**
* 商品变化消息
*/
public final static String GOODS_CHANGE = "GOODS_CHANGE";
/**
* 订单状态变化消息
* 带入库的
*/
public final static String ORDER_STATUS_CHANGE = "ORDER_STATUS_CHANGE";
/**
* 会员登录消息
*/
public final static String MEMEBER_LOGIN = "MEMEBER_LOGIN";
}
然后创建商品的消费者
package org.java.rabbitmq.receiver;
import org.java.rabbitmq.base.AmqpExchange;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class GoodsChangeReceiver {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = AmqpExchange.GOODS_CHANGE + "_QUEUE"),
exchange = @Exchange(value = AmqpExchange.GOODS_CHANGE)
))
public void handler(String msg){
System.out.println("商品变化信息-------------"+msg);
}
}
接下来在test里面进行测试
package org.java.rabbitmq;
import org.java.rabbitmq.base.AmqpExchange;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitmqApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend(AmqpExchange.GOODS_CHANGE + "_QUEUE","苹果库存-1");
}
}
可以看到成功被消费
4.3.2 Fanout
Fanout的定义是 所有发送到FanoutExchange的消息将会被转发到与该Exchange绑定(Binding)的所有的Queue
Fanout也是用的最多的,像创建一个订单不仅仅 只发送到一个Queue,所有与之关联的都要
我们将type = ExchangeTypes 设置为Fanout,然后创建2个不同的Queue就行了
package org.java.rabbitmq.receiver;
import org.java.rabbitmq.base.AmqpExchange;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class OrderStatusChangeReceiver {
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = AmqpExchange.ORDER_STATUS_CHANGE + "_QUEUE_ORDER",name = ""),
exchange = @Exchange(value = AmqpExchange.ORDER_STATUS_CHANGE,type = ExchangeTypes.FANOUT)
))
public void orderChange(String msg){
System.out.println("订单系统收到-------------"+msg);
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = AmqpExchange.ORDER_STATUS_CHANGE + "_QUEUE_TRADE"),
exchange = @Exchange(value = AmqpExchange.ORDER_STATUS_CHANGE,type = ExchangeTypes.FANOUT)
))
public void tradeChange(String msg){
System.out.println("交易系统收到-------------"+msg);
}
}
接下来在测试类里面测试
package org.java.rabbitmq;
import org.java.rabbitmq.base.AmqpExchange;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitmqApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend(AmqpExchange.GOODS_CHANGE + "_QUEUE","苹果库存-1");
}
@Test
public void test(){
rabbitTemplate.convertAndSend(AmqpExchange.ORDER_STATUS_CHANGE,null,"订单号:k9527,状态:已创建");
}
}
可以看到 与之相关的Queue 都被消费了
4.3.3 Topic
所有发送到TopicExchange的消息被将会被转发到所有关心Routekey指定Topic的Queue上,Exchange将RouteKey和某Topic进行模糊匹配。
定义说到需要将Routekey 进行模糊匹配,可是去哪设置呢?于是 我点开了@QueueBinding的源码,发现里面有个key,这个应该就是RoutKey。
接下来再创建一个有关登录的消费者,跟之前一样在@QueueBinding 添加一个key
package org.java.rabbitmq.receiver;
import org.java.rabbitmq.base.AmqpExchange;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MemeberChangeReceiver {
@RabbitListener(
bindings = @QueueBinding(
key = "#.pc.#",
value = @Queue(value = AmqpExchange.MEMEBER_LOGIN + "_QUEUE_1"),
exchange = @Exchange(value = AmqpExchange.MEMEBER_LOGIN,type = ExchangeTypes.TOPIC)
))
public void orderChange(String msg){
System.out.println("pc登录:-------------"+msg);
}
@RabbitListener(
bindings = @QueueBinding(
key = "huawei.#",
value = @Queue(value = AmqpExchange.MEMEBER_LOGIN + "_QUEUE_2"),
exchange = @Exchange(value = AmqpExchange.MEMEBER_LOGIN,type = ExchangeTypes.TOPIC)
))
public void orderChange2(String msg){
System.out.println("华为登录:-------------"+msg);
}
@RabbitListener(
bindings = @QueueBinding(
key = "iphone.#",
value = @Queue(value = AmqpExchange.MEMEBER_LOGIN + "_QUEUE_3"),
exchange = @Exchange(value = AmqpExchange.MEMEBER_LOGIN,type = ExchangeTypes.TOPIC)
))
public void orderChange3(String msg){
System.out.println("苹果登录:-------------"+msg);
}
}
其中xxxx.# 代表 以xxxx的开头的
#.xxxx.# 代表 以包含xxxx的
在测试类进行测试
package org.java.rabbitmq;
import org.java.rabbitmq.base.AmqpExchange;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitmqApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend(AmqpExchange.GOODS_CHANGE + "_QUEUE","苹果库存-1");
}
@Test
public void test(){
rabbitTemplate.convertAndSend(AmqpExchange.ORDER_STATUS_CHANGE,null,"订单号:k9527,状态:已创建");
}
@Test
public void test1(){
rabbitTemplate.convertAndSend(AmqpExchange.MEMEBER_LOGIN,"iphone.wap","会员编号:00001,状态:登录");
rabbitTemplate.convertAndSend(AmqpExchange.MEMEBER_LOGIN,"xiaomi.wap","会员编号:10001,状态:登录");
rabbitTemplate.convertAndSend(AmqpExchange.MEMEBER_LOGIN,"huawei.pc","会员编号:900001,状态:登录");
}
}
可以看到,结果与预期相符
4.3.4 Header
这个策略用得比较少,就不讲了。