RabbitMQ五種核心消息模式
相關概念
首先先了解下RabbitMQ中的相關概念,這裏以五種消息模式中的路由模式爲例:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uMYDs7Kr-1592365180053)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200615102520.png)]
標誌 | 名稱 | 描述 |
---|---|---|
P | 生產者 | 消息的發送者,可以將消息發送到交換機 |
C | 消費者 | 消息的接收者,從隊列中獲取信息並進行消費 |
X | 交換機 | 接收生產者消息,並根據路由鍵發送給指定隊列 |
Q | 隊列 | 存儲從交換機發來的消息 |
type | 交換機類型 | 不同類型的交換機轉發消息方式不同 |
fanout | 發佈/訂閱模式 | 廣播消息給所有綁定交換機的隊列 |
direct | 路由模式 | 根據路由鍵發送消息 |
topic | 通配符模式 | 根據路由鍵的匹配規則發送消息 |
五種消息模式
這五種消息模式都是構件與RabbitMQ的消息應用的基礎,這裏我們使用Spring AMQP的形式來實現它們。
簡單模式
簡單模式是最簡單的消息模式,包含一個生產者,一個消費者和一個隊列。生產者向隊列裏發送消息,消費者從隊列中獲取消息並消費。
模式示意圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vMJiMSxL-1592365180055)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200615110046.png)]
簡單模式的實現
- 首先需要在pom.xml文件中添加Spring AMQP的相關依賴
<!--Spring AMQP依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 修改application.yml文件,添加RabbitMQ的相關配置
server:
port: 8008
spring:
rabbitmq:
host: localhost
port: 5672
virtual-host: /zhonghu
username: zhonghu
password: 123456
publisher-confirms: true #消息發送到交換器確認
publisher-returns: true #消息發送到隊列確認
- 添加簡單模式相關java配置,首先先創建一個名爲simple.hello的隊列、然後創建一個生產者一個消費者
@Configuration
public class SimpleRabbitConfig {
@Bean
public Queue hello() {
return new Queue("simple.hello");
}
@Bean
public SimpleSender simpleSender(){
return new SimpleSender();
}
@Bean
public SimpleReceiver simpleReceiver(){
return new SimpleReceiver();
}
}
- 生產者通過sent方法向隊列simple.hello中發送消息:
public class SimpleSender {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSender.class);
@Autowired
private RabbitTemplate template;
private static final String queueName="simple.hello";
public void send() {
String message = "Hello RabbitMQ ZhongHu!";
this.template.convertAndSend(queueName, message);
LOGGER.info(" Sent'{}'", message);
}
}
- 消費者從隊列中獲取消息
@RabbitListener(queues = "simple.hello")
public class SimpleReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleReceiver.class);
@RabbitHandler
public void receive(String in) {
LOGGER.info(" Received '{}'", in);
}
}
- 最後在controller中添加測試接口,調用該接口開始發送消息
@Api(tags = "RabbitController", description = "RabbitMQ功能測試")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {
@Autowired
private SimpleSender simpleSender;
@ApiOperation("簡單模式")
@RequestMapping(value = "/simple", method = RequestMethod.GET)
@ResponseBody
public CommonResult simpleTest() {
for(int i=0;i<10;i++){
simpleSender.send();
ThreadUtil.sleep(1000);
}
return CommonResult.success(null);
}
}
測試
運行程序,輸入剛纔的url:http://localhost:8008/rabbit/simple
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NjdrRJci-1592365180057)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200615152115.png)]
工作模式
工作模式是指向多個相互競爭的消費者發送信息的模式,包含一個生產者,兩個消費者和一個隊列。兩個消費者同時綁定到一個隊列上去,當消費者獲取消息處理耗時任務時,空閒的消費者從隊列中獲取並消費信息。
模式示意圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aaHXhH5w-1592365180059)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200615152636.png)]
工作模式的實現
- 添加工作模式相關java配置,創建一個名爲work.hello的隊列
@Configuration
public class WorkRabbitConfig {
@Bean
public Queue workQueue() {
return new Queue("work.hello");
}
@Bean
public WorkReceiver workReceiver1() {
return new WorkReceiver(1);
}
@Bean
public WorkReceiver workReceiver2() {
return new WorkReceiver(2);
}
@Bean
public WorkSender workSender() {
return new WorkSender();
}
}
- 生成者通過send方法向隊列work.hello中發送消息,消息中包含一定數量的?號
public class WorkSender {
private static final Logger LOGGER = LoggerFactory.getLogger(WorkSender.class);
@Autowired
private RabbitTemplate template;
private static final String queueName = "work.hello";
public void send(int index) {
StringBuilder builder = new StringBuilder("Hello");
int limitIndex = index % 3+1;
for (int i = 0; i < limitIndex; i++) {
builder.append('?');
}
builder.append(index+1);
String message = builder.toString();
template.convertAndSend(queueName, message);
LOGGER.info(" Sent '{}'", message);
}
}
- 兩個消費者從隊列work.hello中獲取消息,名稱分別爲instance 1和instance 2,消息中包含的?越多,耗時約長。
@RabbitListener(queues = "work.hello")
public class WorkReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(WorkReceiver.class);
private final int instance;
public WorkReceiver(int i) {
this.instance = i;
}
@RabbitHandler
public void receive(String in) {
StopWatch watch = new StopWatch();
watch.start();
LOGGER.info("instance {} Received '{}'", this.instance, in);
doWork(in);
watch.stop();
LOGGER.info("instance {} Done in {}s", this.instance, watch.getTotalTimeSeconds());
}
private void doWork(String in) {
for (char ch : in.toCharArray()) {
if (ch == '?') {
ThreadUtil.sleep(1000);
}
}
}
}
- 在controller中添加測試接口,調用該接口開始發送消息
@Api(tags = "RabbitController", description = "RabbitMQ功能測試")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {
@Autowired
private WorkSender workSender;
@ApiOperation("工作模式")
@RequestMapping(value = "/work", method = RequestMethod.GET)
@ResponseBody
public CommonResult workTest() {
for(int i=0;i<10;i++){
workSender.send(i);
ThreadUtil.sleep(1000);
}
return CommonResult.success(null);
}
}
測試
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-koKNBayY-1592365180060)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200615154346.png)]
通過運行結果可以發現生產者往隊列中發送包含不同數量的?號消息,instance 1和instance 2消費者互相競爭,分別消費了一部分消息。
發佈/訂閱模式
發佈/訂閱模式是指同時向多個消費者發送消息的模式(類似於廣播的形式),它包含一個生產者、兩個隊列和一個交換機。兩個消費者同時綁定到不同的隊列上去、兩個隊列綁定到交換機上去、生產者通過發送消息到交換機,所有消費者接受並消費消息。
模式示意圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DX9NGL7Y-1592365180062)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200615160118.png)]
發佈/訂閱模式的實現
添加發布/訂閱模式相關java配置,創建一個名爲exchange.fanout的交換機、一個生產者、兩個消費者和兩個匿名隊列,將兩個匿名隊列都綁定到交換機。
@Configuration
public class FanoutRabbitConfig {
@Bean
public FanoutExchange fanout() {
return new FanoutExchange("exchange.fanout");
}
@Bean
public Queue fanoutQueue1() {
return new AnonymousQueue();
}
@Bean
public Queue fanoutQueue2() {
return new AnonymousQueue();
}
@Bean
public Binding fanoutBinding1(FanoutExchange fanout, Queue fanoutQueue1) {
return BindingBuilder.bind(fanoutQueue1).to(fanout);
}
@Bean
public Binding fanoutBinding2(FanoutExchange fanout, Queue fanoutQueue2) {
return BindingBuilder.bind(fanoutQueue2).to(fanout);
}
@Bean
public FanoutReceiver fanoutReceiver() {
return new FanoutReceiver();
}
@Bean
public FanoutSender fanoutSender() {
return new FanoutSender();
}
}
- 生產者通過send方法向交換機exchange.fanout中發送消息,消息中含有一定數據的?號
public class FanoutSender {
private static final Logger LOGGER = LoggerFactory.getLogger(FanoutSender.class);
@Autowired
private RabbitTemplate template;
private static final String exchangeName = "exchange.fanout";
public void send(int index) {
StringBuilder builder = new StringBuilder("Hello");
int limitIndex = index % 3 + 1;
for (int i = 0; i < limitIndex; i++) {
builder.append('?');
}
builder.append(index + 1);
String message = builder.toString();
template.convertAndSend(exchangeName, "", message);
LOGGER.info("Sent '{}'", message);
}
}
- 消費者從綁定的匿名隊列中獲取消息,消息包含的?號越多,耗時越長,由於該消費者可以從兩個隊列中獲取並消費信息,可以看做兩個消費者,名稱分別爲instance1和instance2
public class FanoutReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(FanoutReceiver.class);
@RabbitListener(queues = "#{fanoutQueue1.name}")
public void receive1(String in) {
receive(in, 1);
}
@RabbitListener(queues = "#{fanoutQueue2.name}")
public void receive2(String in) {
receive(in, 2);
}
private void receive(String in, int receiver) {
StopWatch watch = new StopWatch();
watch.start();
LOGGER.info("instance {} Received '{}'", receiver, in);
doWork(in);
watch.stop();
LOGGER.info("instance {} Done in {}s", receiver, watch.getTotalTimeSeconds());
}
private void doWork(String in) {
for (char ch : in.toCharArray()) {
if (ch == '.') {
ThreadUtil.sleep(1000);
}
}
}
}
- 在controller中添加測試接口、調用該接口開始發送消息
@Api(tags = "RabbitController", description = "RabbitMQ功能測試")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {
@Autowired
private FanoutSender fanoutSender;
@ApiOperation("發佈/訂閱模式")
@RequestMapping(value = "/fanout", method = RequestMethod.GET)
@ResponseBody
public CommonResult fanoutTest() {
for(int i=0;i<10;i++){
fanoutSender.send(i);
ThreadUtil.sleep(1000);
}
return CommonResult.success(null);
}
}
路由模式
路由模式是可以根據路由鍵選擇性給多個消費者發送消息的模式,它包含一個生產者,兩個消費者,兩個隊列和一個小換機。兩個消費者同時綁定到不同的隊列上去,兩個隊列通過路由鍵綁定到交換機上去,生產者發送消息到交換機,交換機通過路由鍵轉發到不同隊列、隊列綁定的消費者接受並消費信息。
模式示意圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JkapwSWl-1592365180063)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200617095350.png)]
路由模式的實現
添加路由模式相關Java配置,創建一個名爲exchange.direct的交換機、一個生產者、兩個消費者和兩個匿名隊列、隊列通過路由鍵都綁定到交換機,隊列1的路由鍵爲orange和black,隊列2的路由鍵爲green和black
@Configuration
public class DirectRabbitConfig {
@Bean
public DirectExchange direct() {
return new DirectExchange("exchange.direct");
}
@Bean
public Queue directQueue1() {
return new AnonymousQueue();
}
@Bean
public Queue directQueue2() {
return new AnonymousQueue();
}
@Bean
public Binding directBinding1a(DirectExchange direct, Queue directQueue1) {
return BindingBuilder.bind(directQueue1).to(direct).with("orange");
}
@Bean
public Binding directBinding1b(DirectExchange direct, Queue directQueue1) {
return BindingBuilder.bind(directQueue1).to(direct).with("black");
}
@Bean
public Binding directBinding2a(DirectExchange direct, Queue directQueue2) {
return BindingBuilder.bind(directQueue2).to(direct).with("green");
}
@Bean
public Binding directBinding2b(DirectExchange direct, Queue directQueue2) {
return BindingBuilder.bind(directQueue2).to(direct).with("black");
}
@Bean
public DirectReceiver receiver() {
return new DirectReceiver();
}
@Bean
public DirectSender directSender() {
return new DirectSender();
}
}
- 生產者通過send方法向交換機exchange.direct中發送消息,發送時使用不同的路由鍵,根據路由鍵會被轉發到不同隊列
public class DirectSender {
@Autowired
private RabbitTemplate template;
private static final String exchangeName = "exchange.direct";
private final String[] keys = {"orange", "black", "green"};
private static final Logger LOGGER = LoggerFactory.getLogger(DirectSender.class);
public void send(int index) {
StringBuilder builder = new StringBuilder("Hello to ");
int limitIndex = index % 3;
String key = keys[limitIndex];
builder.append(key).append(' ');
builder.append(index+1);
String message = builder.toString();
template.convertAndSend(exchangeName, key, message);
LOGGER.info(" Sent '{}'", message);
}
}
- 消費者從自己綁定的匿名隊列中獲取消息,由於該消費者可以從兩個隊列中獲取並消費消息,可以看做兩個消費者,名稱分別爲instance1和instance2
public class DirectReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(DirectReceiver.class);
@RabbitListener(queues = "#{directQueue1.name}")
public void receive1(String in){
receive(in, 1);
}
@RabbitListener(queues = "#{directQueue2.name}")
public void receive2(String in){
receive(in, 2);
}
private void receive(String in, int receiver){
StopWatch watch = new StopWatch();
watch.start();
LOGGER.info("instance {} Received '{}'", receiver, in);
doWork(in);
watch.stop();
LOGGER.info("instance {} Done in {}s", receiver, watch.getTotalTimeSeconds());
}
private void doWork(String in){
for (char ch : in.toCharArray()) {
if (ch == '.') {
ThreadUtil.sleep(1000);
}
}
}
}
- 在controller中添加測試接口,調用該接口開始發送消息:
@Api(tags = "RabbitController", description = "RabbitMQ功能測試")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {
@Autowired
private DirectSender directSender;
@ApiOperation("路由模式")
@RequestMapping(value = "/direct", method = RequestMethod.GET)
@ResponseBody
public CommonResult directTest() {
for(int i=0;i<10;i++){
directSender.send(i);
ThreadUtil.sleep(1000);
}
return CommonResult.success(null);
}
}
通配符模式
通配符模式是可以根據路由鍵匹配規則,選擇性給多個消費者發送消息的模式,它包含一個生產者、兩個消費者、兩個隊列和一個交換機。兩個消費者同時綁定到不同的隊列上去,兩個隊列通過路由鍵匹配規則綁定到交換機,交換機通過路由鍵匹配規則轉發到不同隊列,隊列綁定的消費者接受並消費消息
特殊匹配符號
- *只能匹配一個單詞
- #可以匹配零個或多個單詞
模式示意圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nMhtuJXl-1592365180064)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200617103230.png)]
通配符模式的實現
添加通配符模式的相關java配置,創建一個名爲exchange.topic的交換機、一個生產者、兩個消費者和兩個匿名隊列。匹配*.orange.*
和*.*.rabbit
發送到隊列1
,匹配lazy.#
發送到隊列2
;
@Configuration
public class TopicRabbitConfig {
@Bean
public TopicExchange topic() {
return new TopicExchange("exchange.topic");
}
@Bean
public Queue topicQueue1() {
return new AnonymousQueue();
}
@Bean
public Queue topicQueue2() {
return new AnonymousQueue();
}
@Bean
public Binding topicBinding1a(TopicExchange topic, Queue topicQueue1) {
return BindingBuilder.bind(topicQueue1).to(topic).with("*.orange.*");
}
@Bean
public Binding topicBinding1b(TopicExchange topic, Queue topicQueue1) {
return BindingBuilder.bind(topicQueue1).to(topic).with("*.*.rabbit");
}
@Bean
public Binding topicBinding2a(TopicExchange topic, Queue topicQueue2) {
return BindingBuilder.bind(topicQueue2).to(topic).with("lazy.#");
}
@Bean
public TopicReceiver topicReceiver() {
return new TopicReceiver();
}
@Bean
public TopicSender topicSender() {
return new TopicSender();
}
}
- 生產者通過sent的方法向交換機exchange.topic中發送消息,消息中包含不同的路由鍵
public class TopicSender {
@Autowired
private RabbitTemplate template;
private static final String exchangeName = "exchange.topic";
private static final Logger LOGGER = LoggerFactory.getLogger(TopicSender.class);
private final String[] keys = {"quick.orange.rabbit", "lazy.orange.elephant", "quick.orange.fox",
"lazy.brown.fox", "lazy.pink.rabbit", "quick.brown.fox"};
public void send(int index) {
StringBuilder builder = new StringBuilder("Hello to ");
int limitIndex = index%keys.length;
String key = keys[limitIndex];
builder.append(key).append(' ');
builder.append(index+1);
String message = builder.toString();
template.convertAndSend(exchangeName, key, message);
LOGGER.info(" [x] Sent '{}'",message);
System.out.println(" [x] Sent '" + message + "'");
}
}
- 消費者從自己綁定的匿名隊列中獲取消息,由於該消費者可以從兩個隊列中獲取並消費消息,可以看做兩個消費者,名稱分別爲instance1和instance2
public class TopicReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(TopicReceiver.class);
@RabbitListener(queues = "#{topicQueue1.name}")
public void receive1(String in){
receive(in, 1);
}
@RabbitListener(queues = "#{topicQueue2.name}")
public void receive2(String in){
receive(in, 2);
}
public void receive(String in, int receiver){
StopWatch watch = new StopWatch();
watch.start();
LOGGER.info("instance {} [x] Received '{}'", receiver, in);
doWork(in);
watch.stop();
LOGGER.info("instance {} [x] Done in {}s", receiver, watch.getTotalTimeSeconds());
}
private void doWork(String in){
for (char ch : in.toCharArray()) {
if (ch == '.') {
ThreadUtil.sleep(1000);
}
}
}
}
- 在controller中添加測試接口,調用該接口開始發送消息
@Api(tags = "RabbitController", description = "RabbitMQ功能測試")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {
@Autowired
private TopicSender topicSender;
@ApiOperation("通配符模式")
@RequestMapping(value = "/topic", method = RequestMethod.GET)
@ResponseBody
public CommonResult topicTest() {
for(int i=0;i<10;i++){
topicSender.send(i);
ThreadUtil.sleep(1000);
}
return CommonResult.success(null);
}
}