我们以购买商品为例,用户支付后需要调用订单服务完成订单状态修改,调用物流服务,从仓库分配响应的库存并准备发货。
在事件模式中,支付服务是事件发布者(publisher),在支付完成后只需要发布一个支付成功的事件(event),事件中带上订单id。
订单服务和物流服务是事件订阅者(Consumer),订阅支付成功的事件,监听到事件后完成自己业务即可。
为了解除事件发布者与订阅者之间的耦合,两者并不是直接通信,而是有一个中间人(Broker)。发布者发布事件到Broker,不关心谁来订阅事件。订阅者从Broker订阅事件,不关心谁发来的消息。
Broker 是一个像数据总线一样的东西,所有的服务要接收数据和发送数据都发到这个总线上,这个总线就像协议一样,让服务间的通讯变得标准和可控。
吞吐量提升:无需等待订阅者处理完成,响应更快速
故障隔离:服务没有直接调用,不存在级联失败问题
调用间没有阻塞,不会造成无效的资源占用
耦合度极低,每个服务都可以灵活插拔,可替换
流量削峰:不管发布事件的流量波动多大,都由Broker接收,订阅者可以按照自己的速度去处理事件
SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。
SpringAmqp的官方地址
SpringAMQP提供了三个功能:
几种不同RabbitMQ消息模型的用法
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
dependencies>
@SpringBootApplication
public class PublisherApplication {
public static void main(String[] args) {
SpringApplication.run(PublisherApplication.class, args);
}
}
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
server:
port: 8015
spring:
application:
name: publisher
cloud:
nacos:
discovery:
server-addr: http://127.0.0.1:8868 # 注册中心 http://ip:端口号
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
server:
port: 8016
spring:
application:
name: consumer
cloud:
nacos:
discovery:
server-addr: http://127.0.0.1:8868 # 注册中心 http://ip:端口号
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
@RestController
@RequestMapping("/publisher/basic")
public class BasicQueueController {
@Resource
private BasicQueueMessage basicQueueMessage;
@GetMapping("/queue")
public void basicQueue(@RequestParam("message")String message) {
basicQueueMessage.sendBasicQueueMessage(message);
}
}
public interface BasicQueueMessage {
void sendBasicQueueMessage(String message);
}
@Service
public class BasicQueueMessageImpl implements BasicQueueMessage {
String queueName = "basic_queue_message";
@Resource
private RabbitTemplate rabbitTemplate;
@Override
public void sendBasicQueueMessage(String message) {
rabbitTemplate.convertAndSend(queueName,message);
}
}
@Component
public class BasicQueueListener {
@RabbitListener(queuesToDeclare = @Queue(name = "basic_queue_message"))
public void basicQueueConsumer(String message){
System.out.println("message = " + message);
}
}
spring:
cloud:
nacos:
server-addr: localhost:8868 # nacos地址
gateway:
routes: # 网关路由配置
- id: publisher
uri: lb://publisher
predicates:
- Path=/publisher/**
@RestController
@RequestMapping("/publisher/work")
public class WorkQueueController {
@Resource
private WorkQueueMessage workQueueMessage;
@GetMapping("/queue")
public void basicQueue(@RequestParam("message")String message) throws Exception {
workQueueMessage.sendWorkQueueMessage(message);
}
}
public interface WorkQueueMessage {
void sendWorkQueueMessage(String message) throws Exception;
}
@Service
public class WorkQueueMessageImpl implements WorkQueueMessage {
@Resource
private RabbitTemplate rabbitTemplate;
String queueName = "work_queue_message";
@Override
public void sendWorkQueueMessage(String message) throws Exception {
for (int i = 0; i < 50; i++) {
rabbitTemplate.convertAndSend(queueName,message +" -- "+i);
Thread.sleep(20);
}
}
}
@Component
public class WorkQueueListener {
@RabbitListener(queuesToDeclare = @Queue(name = "work_queue_message"))
public void workQueueConsumer1(String message) throws Exception {
System.out.println("message1 = " +" 【 "+ message +" 】 "+ LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queuesToDeclare = @Queue(name = "work_queue_message"))
public void workQueueConsumer2(String message) throws Exception {
System.err.println("message2 = " +" 【 "+ message +" 】 "+ LocalTime.now());
Thread.sleep(50);
}
}
在订阅模型中,多了一个exchange角色,而且过程略有变化:
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
@RestController
@RequestMapping("/publisher/fanout")
public class FanoutQueueController {
@Resource
private FanoutQueueMessage fanoutQueueMessage;
@GetMapping("/queue")
public void fanoutQueue(@RequestParam("message")String message) throws Exception {
fanoutQueueMessage.sendFanoutQueueMessage(message);
}
}
public interface FanoutQueueMessage {
void sendFanoutQueueMessage(String message) throws Exception;
}
@Service
public class FanoutQueueMessageImpl implements FanoutQueueMessage {
final String fanoutExchangeName = "fanout_exchange";
@Resource
private RabbitTemplate rabbitTemplate;
@Override
public void sendFanoutQueueMessage(String message) throws Exception {
for (int i = 0; i < 100; i++) {
rabbitTemplate.convertAndSend(fanoutExchangeName,"",message+" -- "+i);
}
}
}
@Component
public class FanoutQueueListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue("fanout_queue_message1"),
exchange = @Exchange(name = "fanout_exchange",type = ExchangeTypes.FANOUT)
))
public void fanoutQueueConsumer1(String message) throws Exception {
System.out.println("message1 = " +" 【 "+ message +" 】 "+ LocalTime.now());
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue("fanout_queue_message1"),
exchange = @Exchange(name = "fanout_exchange",type = ExchangeTypes.FANOUT)
))
public void fanoutQueueConsumer3(String message) throws Exception {
System.out.println("message3 = " +" 【 "+ message +" 】 "+ LocalTime.now());
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue("fanout_queue_message2"),
exchange = @Exchange(name = "fanout_exchange",type = ExchangeTypes.FANOUT)
))
public void fanoutQueueConsumer2(String message) throws Exception {
System.err.println("message2 = " +" 【 "+ message +" 】 "+ LocalTime.now());
}
}
RoutingKey
(路由key)RoutingKey
。Routing Key
进行判断,只有队列的Routingkey
与消息的 Routing key
完全一致,才会接收到消息@RestController
@RequestMapping("/publisher/direct")
public class DirectQueueController {
@Resource
private DirectQueueMessage directQueueMessage;
@GetMapping("/queue")
public void directQueue(@RequestParam("message")String message) throws Exception {
directQueueMessage.sendDirectQueueMessage(message);
}
}
public interface DirectQueueMessage {
void sendDirectQueueMessage(String message);
}
@Service
public class DirectQueueMessageImpl implements DirectQueueMessage {
@Resource
private RabbitTemplate rabbitTemplate;
final String directExchangeName = "direct_exchange";
@Override
public void sendDirectQueueMessage(String message) {
for (int i = 1; i <= 50; i++) {
if (i<15) {
rabbitTemplate.convertAndSend(directExchangeName, "red",message +"--routingKey-->" +"red "+i);
} else if (i > 17&& i<34) {
rabbitTemplate.convertAndSend(directExchangeName, "yellow",message+"--routingKey-->" +"yellow "+i);
}else{
rabbitTemplate.convertAndSend(directExchangeName, "blue",message+"--routingKey-->" +"blue "+i);
}
}
}
}
@Component
public class DirectQueueLitener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue("direct_queue_message1"),
exchange = @Exchange(name = "direct_exchange",type = ExchangeTypes.DIRECT),
key = {"red","blue"}))
public void directQueueConsumer1(String message) {
System.out.println("message1 -> consumer - red 、blue" +" 【 "+ message +" 】 "+ LocalTime.now());
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue("direct_queue_message2"),
exchange = @Exchange(name = "direct_exchange",type = ExchangeTypes.DIRECT),
key = {"yellow","blue"}))
public void directQueueConsumer2(String message) {
System.err.println("message2 -> consumer - yellow 、blue" +" 【 "+ message +" 】 "+ LocalTime.now());
}
}
Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符!
Routingkey
一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
#
:匹配一个或多个词
@RestController
@RequestMapping("/publisher/topic")
public class TopicQueueController {
@Resource
private TopicQueueMessage topicQueueMessage;
@GetMapping("/queue")
public void topicQueue(@RequestParam("message")String message) throws Exception {
topicQueueMessage.sendTopicQueueMessage(message);
}
}
public interface TopicQueueMessage {
void sendTopicQueueMessage(String message);
}
@Service
public class TopicQueueMessageImpl implements TopicQueueMessage {
@Resource
private RabbitTemplate rabbitTemplate;
final String topicExchangeName = "topic_exchange";
@Override
public void sendTopicQueueMessage(String message) {
for (int i = 1; i <= 50; i++) {
if (i < 17) {
rabbitTemplate.convertAndSend(topicExchangeName, "new.message", message + "--routingKey-->" + "new.message " + i);
} else if (i > 37) {
rabbitTemplate.convertAndSend(topicExchangeName, "old.message", message + "--routingKey-->" + "old.message " + i);
} else {
rabbitTemplate.convertAndSend(topicExchangeName, "china.news", message + "--routingKey-->" + "china.news " + i);
}
}
}
}
@Component
public class TopicQueueListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue("topic_queue_message1"),
exchange = @Exchange(name = "topic_exchange",type = ExchangeTypes.TOPIC),
key = {"old.#"}))
public void directQueueConsumer1(String message) {
System.out.println("message1 -> consumer - old.# " +" 【 "+ message +" 】 "+ LocalTime.now());
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue("topic_queue_message2"),
exchange = @Exchange(name = "topic_exchange",type = ExchangeTypes.TOPIC),
key = {"new.#","china.#"}))
public void directQueueConsumer2(String message) {
System.err.println("message2 -> consumer - new.# 、china.#" +" 【 "+ message +" 】 "+ LocalTime.now());
}
}