每篇文章都有屬於它本身的故事,沒有故事的文章是沒有靈魂的文章。而我就是這個靈魂擺渡人。git
主人公張某某,這邊不方便透露姓名,就叫小張吧。小張在一家小型的互聯網創業團隊中就任。github
職位是Java後端開發,因此總體和業務代碼打交道在所不免。數據庫
以前有個搜索相關的需求,並且數量量也算比較大,就採用了ElasticSearch來作搜索。初版因爲時間比較趕,作的比較粗糙。越到後面發現代碼越難寫下去了,主要是在更新索引數據的場景沒處理好,纔有了今天的故事。後端
Spring的事件就是觀察者設計模式,一個任務結束後須要通知任務進行下一步的操做,就可使用事件來驅動。設計模式
在Spring中使用事件機制的步驟以下:bash
Spring Event在不少開源框架中都有使用案例,好比Spring Cloud中的Eureka裏面就有使用微信
event包架構
定義Eventapp
發佈Event框架
EventBus是Guava的事件處理機制,在使用層面和Spring Event差很少。這裏不作過多講解,今天主要講Spring Event。
全部的數據都會有一個定時任務去同步數據到ElasticSearch中,業務中直接從ElasticSearch查詢數據返回給調用方。
之因此把全部數據都存入ElasticSearch是爲了方便,若是隻存儲搜索的字段,那麼搜索出來後就還須要去數據庫查詢其餘信息進行組裝。
就是因爲全部數據都會存儲ElasticSearch中,因此當這些數據發生變動的時候咱們須要去刷新ElasticSearch中的數據,這個就是咱們今天文章的核心背景。
假設咱們ElasticSearch中的數據是文章信息,也就是咱們常常看的技術文章,這個文章中存儲了訪問量,點贊量,評論量等信息。
當這些動做發生的時候,都須要去更新ElasticSearch的數據才行,咱們默認的操做都是更新數據庫中的數據,ElasticSearch是由定時任務去同步的,同步會有周期,作不到毫秒別更新。
倔強青銅就是在每一個會涉及到數據變動的地方,去手動調用代碼進行數據的刷新操做,弊端在於每一個地方都要去調用,這仍是簡單的場景,有複雜的業務場景,一個業務操做可能會涉及到不少數據的刷新,也就是須要調用不少次,模擬代碼以下:
// 瀏覽
public void visit() {
articleIndexService.reIndex(articleId);
XXXIndexService.reIndex(articleId);
........
}
// 評論
public void comment() {
articleIndexService.reIndex(articleId);
}
複製代碼
倔強青銅的弊端在於不解耦,並且是同步調用,若是在事務中會加長事務的時間。因此咱們須要一個異步的方案來執行重建索引的邏輯。
通過你們激烈的討論,而項目也是以Spring Boot爲主,因此選擇了Spring Event來做爲異步方案。
定義一個重建文章索引的Event,代碼以下:
public class ArticleReIndexEvent extends ApplicationEvent {
private String id;
public ArticleReIndexEvent(Object source, String id) {
super(source);
this.id = id;
}
public String getId() {
return id;
}
}
複製代碼
而後寫一個EventListener來監聽事件,進行業務邏輯處理,代碼以下:
@Component
public class MyEventListener {
@EventListener
public void onEvent(ArticleReIndexEvent event) {
System.out.println(event.getId());
}
}
複製代碼
使用的地方只須要發佈一個Event就能夠,這個動做默認是同步的,若是咱們想讓這個操做不會阻塞,變成異步只須要在@EventListener上面再增長一個@Async註解。
// 瀏覽
public void visit() {
applicationContext.publishEvent(new ArticleReIndexEvent(this, articleId));
applicationContext.publishEvent(new XXXReIndexEvent(this, articleId));
}
// 評論
public void comment() {
applicationContext.publishEvent(new ArticleReIndexEvent(this, articleId));
}
複製代碼
秩序白銀的方案在代碼層面確實解耦了,可是使用者發佈事件須要關注的點太多了,也就是我改了某個表的數據,我得知道有哪些索引會用到這張表的數據,我得把這些相關的事件都發送出去,這樣數據纔會異步進行刷新。
當業務複雜後或者有新來的同事,不是那麼的瞭解業務,壓根不可能知道說我改了這個數據對其餘那些索引有影響,因此這個方案仍是有優化的空間。
榮耀黃金的方案是將全部的事件都統一爲一個,而後在事件里加屬性來區分修改的數據是哪裏的。每一個數據須要同步變動的索引都有本身的監聽器,去監聽這個統一的事件,這樣對於發佈者來講我只須要發送一個事件告訴你,我這邊改數據了,你要不要消費,要不要更新索引我並不關心。
定義一個數據表發生修改的事件,代碼以下:
public class TableUpdateEvent extends ApplicationEvent {
private String table;
private String id;
public TableUpdateEvent(Object source, String id, String table) {
super(source);
this.id = id;
this.table = table;
}
public String getId() {
return id;
}
public String getTable() {
return table;
}
}
複製代碼
而後每一個索引都須要消費這個事件,只須要關注這個索引中數據的來源表有沒有變更,若是有變更則去刷新索引。
好比索引A的數據都是article表中過來的,因此只要是article表中的數據發生了變動,索引A都要作對應的處理,因此索引A的監聽器只須要關注article表有沒有修改便可。
@Component
public class MyEventListener {
private List<String> consumerTables = Arrays.asList("article");
@Async
@EventListener
public void onEvent(TableUpdateEvent event) {
System.out.println(event.getId() + "\t" + event.getTable());
if (consumerTables.contains(event.getTable())) {
System.out.println("消費本身關注的表數據變更,而後處理。。。");
}
}
}
複製代碼
好比索引B的數據是從comment和comment_reply兩個表中過來的,因此只要是comment和comment_reply兩個表的數據發生了變動,索引B都須要作對應的處理,因此索引B的監聽器只須要關注comment和comment_reply兩個表有沒有修改便可。
@Component
public class MyEventListener2 {
private List<String> consumerTables = Arrays.asList("comment", "comment_replay");
@Async
@EventListener
public void onEvent(TableUpdateEvent event) {
System.out.println(event.getId() + "\t" + event.getTable());
if (consumerTables.contains(event.getTable())) {
System.out.println("消費本身關注的表數據變更,而後處理。。。");
}
}
}
複製代碼
榮耀黃金的方案已經很完美了,代碼解耦不說,使用者關注點也少了,不容易出錯。
但還有一個致命的問題就是全部涉及到業務修改的方法中,得手動往外發送一個事件,從代碼解耦的場景來講還殘留了一點瑕疵,至少仍是有那麼一行代碼來發送事件。
尊貴鉑金的方案將徹底解耦,不須要寫代碼的時候手動去發送事件。咱們將經過訂閱MySql的binlog來統一發送事件。
binlog是MySQL數據庫的二進制日誌,用於記錄用戶對數據庫操做的SQL語句信息,MySQL的主從同步也是基於binlog來實現的,對於咱們這種數據異構的場景再合適不過了。
binlog訂閱的方式有不少種,開源的框架通常都是用canal來實現。
canal:github.com/alibaba/can…
若是你買的雲數據庫,像ALI雲就有dts數據訂閱服務,跟canal同樣。
以後的方案圖以下:
沒有什麼方案和架構是永恆的,跟着業務的變動而演進,符合當前業務的需求才是王道。越後面考慮的東西越多,畢竟最後是要升級到最強王者的,哈哈。
感興趣的能夠關注下個人微信公衆號 猿天地,更多技術文章第一時間閱讀。個人GitHub也有一些開源的代碼 github.com/yinjihuan