數據庫最終一致性的四種方案
緩存是什么
緩存的速度是有區別的.緩存就是把低速存儲的結果,臨時保存在高速存儲的數據.

如圖所示.金字塔更上面的存儲,可以作為下面存儲的緩存.
我們本次的討論,主要針對數據庫緩存場景,將以redis作為mysql的緩存為案例來進行.
為什么需要緩存
存儲如mysql通常支持完整的ACID特性,因為可靠性,持久性等因素.性能普遍不高,高並發的查詢會給mysql帶來壓力,造成數據庫系統的不穩定.同時也容易產生延遲.
根據局部性原理,80%請求回落到20%的熱點數據上,在讀多寫少場景,增加一層緩存非常有助於提升系統的吞吐量和健壯性.
存在問題
存儲的數據隨着時間可能發生變化,而緩存中的數據就會不一致.具體能容忍的不一致時間,需要具體業務分析,但是通常的業務,都需要做到最終一致.
Redis作為mysql緩存
通常的開發模式中,都會使用mysql作為緩存,而redis作為緩存,加速和保護mysql.但是,當mysql數據更新之后,redis怎么保持同步呢.
強一致性同步成本太高,如果追求強一致,那么就沒必要用緩存了,直接用mysql即可.通常考慮的都是最終一致性.
解決方案
方案一
通過key的過期時間,mysql更新時,redis不更新.
這種方式實現簡單,但不一致的時間會很長.如果讀請求非常頻繁,且過期時間比較長,則會產生很多長期的臟數據.
優點:
* 開發成本低,易於實現;
* 管理成本低,出問題概率會比較小.
缺點:
* 完全依賴過期時間,時間太短容易緩存頻繁失效,太長容易有長時間更新延遲(不一致).
方案二
在方案一的基礎上擴展,通過key的過期時間兜底,並且,在更新mysql時,同時更新redis.
優點:
* 相對方案一,更新延遲更小
缺點:
* 如果更新mysql成功,更新redis卻失敗,就退化到了方案一;
* 在高並發場景,業務server需要和mysql,redis同時進行連接.這樣需要損耗雙倍的連接資源,容易造成連接數過多的問題.
方案三
針對方案二的同步寫redis進行優化,增加消息隊列,將redis更新操作交給kafka,由消息隊列保證可靠性,再搭建一個消費服務,來異步更新redis.
優點:
* 消息隊列可以用一個句柄,很多消息隊列客戶端還支持本地緩存發送,有效解決了方案二的連接過多問題;
* 使用消息隊列,實現了邏輯上的解耦;
* 消息隊列本身具有可靠性,通過手動提交等手段.可以至少一次消費到redis.
缺點:
* 依舊解決不了時序性問題,如果多台業務服務器分別處理針對同一行數據的兩條請求,舉個例子,a=1;a=5;如果mysql中十第一條先執行,而進入kafka的順序是第二條先執行,那么數據就會產生不一致.
* 引入了消息隊列,同時要增加服務消費消息,成本較高,還有重復消費的風險.
方案四
通過訂閱binlog來更新redis,把我們搭建的消費服務,作為mysql的一個slave,訂閱binlog,解析出更新內容,再更新到redis.
優點:
* 在mysql壓力不大的情況下,延遲較低;
* 和業務完全解耦
* 解決了時序問題
缺點:
* 要單獨搭建一個同步服務,並且引入binlog同步機制,成本較大.
總結
方案選型
首先確認產品的延遲性要求,如果延遲要求極高,且數據可能變化,別用緩存.
通常來說,方案1就夠了,因為能用緩存的方案,通常是讀多寫少場景,同時業務上對延遲具有一定的包容性.方案一是沒有開發成本的,其實比較實用.
如果想增加更新時的及時性,選擇方案二,不過沒必要做重試保證之類的.
方案3和方案4正對於對延遲要求較高業務,一個是推模式,一個是拉模式,而方案四具備更強的可靠性,既然都願意花功夫做處理消息的邏輯,不如一步到位,用方案4.
一般情況下,方案1夠用.
