發(fā)布訂閱模式
什么是發(fā)布訂閱模式?
在發(fā)布訂閱模式中有個(gè)重要的角色,一個(gè)是發(fā)布者Publisher,另一個(gè)訂閱者Subscriber。本質(zhì)上來(lái)說(shuō),發(fā)布訂閱模式就是一種生產(chǎn)者消費(fèi)者模式,Publisher負(fù)責(zé)生產(chǎn)消息,而Subscriber則負(fù)責(zé)消費(fèi)它所訂閱的消息。這種模式被廣泛的應(yīng)用于軟硬件的系統(tǒng)設(shè)計(jì)中。比如:配置中心的一個(gè)配置修改之后,就是通過(guò)發(fā)布訂閱的方式傳遞給訂閱這個(gè)配置的訂閱者來(lái)實(shí)現(xiàn)自動(dòng)刷新的。
不就是觀察者模式嗎?
看到這里,學(xué)過(guò)設(shè)計(jì)模式的同學(xué)可能很容易將它與設(shè)計(jì)模式中的觀察者模式聯(lián)系起來(lái),你會(huì)覺(jué)得發(fā)布訂閱模式中的兩個(gè)概念與觀察者模式中的兩個(gè)概念似乎干的是一樣的事情?所以:Publisher就是觀察者模式中的Subject?Subscriber就是觀察者模式中的Observer?
重要區(qū)別在哪里?
從這兩種模式的角色任務(wù)來(lái)說(shuō)確實(shí)是非常相似的,但從實(shí)現(xiàn)架構(gòu)上來(lái)說(shuō)有一個(gè)核心不同點(diǎn)!
我們通過(guò)下面的圖示來(lái)理解,就很清晰了:
可以看到這里有一個(gè)非常大的區(qū)別就是:發(fā)布訂閱模式在兩個(gè)角色中間是一個(gè)中間角色來(lái)過(guò)渡的,發(fā)布者并不直接與訂閱者產(chǎn)生交互。
回想一下生產(chǎn)者消費(fèi)者模式,這個(gè)中間過(guò)渡區(qū)域?qū)?yīng)的就是是緩沖區(qū)。因?yàn)檫@個(gè)緩沖區(qū)的存在,發(fā)布者與訂閱者的工作就可以實(shí)現(xiàn)更大程度的解耦。發(fā)布者不會(huì)因?yàn)橛嗛喺咛幚硭俣嚷?,而影響自己的發(fā)布任務(wù),它只需要快速生產(chǎn)即可。而訂閱者也不用太擔(dān)心一時(shí)來(lái)不及處理,因?yàn)橛芯彌_區(qū)在,可以一點(diǎn)點(diǎn)排隊(duì)來(lái)完成(也就是我們常說(shuō)的“削峰填谷”效果)。
而我們所熟知的RabbitMQ、Kafka、RocketMQ這些中間件的本質(zhì)其實(shí)就是實(shí)現(xiàn)發(fā)布訂閱模式中的這個(gè)中間緩沖區(qū)。而Redis也提供了簡(jiǎn)單的發(fā)布訂閱實(shí)現(xiàn),當(dāng)我們有一些簡(jiǎn)單需求的時(shí)候,也是可以一用的!如果你已經(jīng)理解了這個(gè)概念,那么就進(jìn)入下一節(jié),一起來(lái)做個(gè)例子吧!
#動(dòng)手試一試
下面的動(dòng)手任務(wù),我們將在Spring Boot應(yīng)用中,通過(guò)接口的方式實(shí)現(xiàn)一個(gè)消息發(fā)布者的角色,然后再寫(xiě)一個(gè)Service來(lái)實(shí)現(xiàn)消息的訂閱(把接口傳過(guò)來(lái)的消息內(nèi)容打印處理)。
第一步:創(chuàng)建一個(gè)基礎(chǔ)的Spring Boot應(yīng)用,如果還不會(huì)點(diǎn)這里open in new window
第二步:pom.xml
中加入必須的幾個(gè)依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
第三步:創(chuàng)建一個(gè)接口,用來(lái)發(fā)送消息。
@SpringBootApplication
public class Chapter55Application {
private static String CHANNEL = "didispace";
public static void main(String[] args) {
SpringApplication.run(Chapter55Application.class, args);
}
@RestController
static class RedisController {
private RedisTemplate<String, String> redisTemplate;
public RedisController(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@GetMapping("/publish")
public void publish(@RequestParam String message) {
// 發(fā)送消息
redisTemplate.convertAndSend(CHANNEL, message);
}
}
}
這里為了簡(jiǎn)單實(shí)現(xiàn),公用CHANNEL名稱字段,我都寫(xiě)在了應(yīng)用主類(lèi)里。
第四步:繼續(xù)應(yīng)用主類(lèi)里實(shí)現(xiàn)消息訂閱,打接收到的消息打印處理
@Slf4j
@Service
static class MessageSubscriber {
public MessageSubscriber(RedisTemplate redisTemplate) {
RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
redisConnection.subscribe(new MessageListener() {
@Override
public void onMessage(Message message, byte[] bytes) {
// 收到消息的處理邏輯
log.info("Receive message : " + message);
}
}, CHANNEL.getBytes(StandardCharsets.UTF_8));
}
}
第六步:驗(yàn)證結(jié)果
- 啟動(dòng)應(yīng)用Spring Boot主類(lèi)
- 通過(guò)curl或其他工具調(diào)用接口
curl localhost:8080/publish?message=hello
- 觀察控制臺(tái),可以看到打印了收到的message參數(shù)
2021-06-19 16:22:30.935 INFO 34351 --- [ioEventLoop-4-2] .c.Chapter55Application$MessageSubscribe : Receive message : hello
好了,今天的內(nèi)容到這里結(jié)束了。如果你對(duì)本系列教程《Spring Boot 2.x基礎(chǔ)教程》感興趣,可以點(diǎn)擊直達(dá)!。學(xué)習(xí)過(guò)程中如遇困難,建議加入Spring技術(shù)交流群open in new window,參與交流與討論,更好的學(xué)習(xí)與進(jìn)步!
#代碼示例
本文的完整工程可以查看下面?zhèn)}庫(kù)中的chapter5-5
目錄:
- Github:https://github.com/dyc87112/SpringBoot-Learning/open in new window
- Gitee:https://gitee.com/didispace/SpringBoot-Learning/