基於redis分布式緩存實現



Redis的復制功能是完全建立在之前我們討論過的基 於內存快照的持久化策略基礎上的,也就是說無論你的持久化策略選擇的是什么,只要用到了Redis的復制功能,就一定會有內存快照發生,那么首先要注意你 的系統內存容量規划,原因可以參考我上一篇文章中提到的Redis磁盤IO問題。

 

Redis復制流程在Slave和Master端各自是一套狀態機流轉,涉及的狀態信息是:

Slave 端:

Master端:

 

整個狀態機流程過程如下:

 

    Slave端在配置文件中添加了slave of指令,於是Slave啟動時讀取配置文件,初始狀態為REDIS_REPL_CONNECT。
  • Slave端在定時任務serverCron(Redis內部的定時器觸發事件)中連接Master,發送sync命令,然后阻塞等待master發送回其內存快照文件(最新版的Redis已經不需要讓Slave阻塞)。
  • Master端收到sync命令簡單判斷是否有正在進行的內存快照子進程,沒有則立即開始內存快照,有則等待其結束,當快照完成后會將該文件發送給Slave端。
  • Slave端接收Master發來的內存快照文件,保存到本地,待接收完成后,清空內存表,重新讀取Master發來的內存快照文件,重建整個內存表數據結構,並最終狀態置位為 REDIS_REPL_CONNECTED狀態,Slave狀態機流轉完成。
  • Master端在發送快照文件過程中,接收的任何會改變數據集的命令都會暫時先保存在Slave網絡連接的發送緩存隊列里(list數據結構),待快照完成后,依次發給Slave,之后收到的命令相同處理,並將狀態置位為 REDIS_REPL_ONLINE。

整個復制過程完成,流程如下圖所示:

從上面的流程可以看出,Slave從庫在連接Master主庫時,Master會進行內存快照,然后把整個快照文件發給Slave,也就是沒有象MySQL那樣有復制位置的概念,即無增量復制,這會給整個集群搭建帶來非常多的問題。

 

比如一台線上正在運行的Master主庫配置了一台從 庫進行簡單讀寫分離,這時Slave由於網絡或者其它原因與Master斷開了連接,那么當Slave進行重新連接時,需要重新獲取整個Master的內 存快照,Slave所有數據跟着全部清除,然后重新建立整個內存表,一方面Slave恢復的時間會非常慢,另一方面也會給主庫帶來壓力。

所以基於上述原因,如果你的Redis集群需要主從復制,那么最好事先配置好所有的從庫,避免中途再去增加從庫。

在我們分析過了Redis的復制與持久化功能后,我們不難得出一個結論,實際上Redis目前發布的版本還都是一個單機版的思路,主要的問題集中在,持久化方式不夠成熟,復制機制存在比較大的缺陷,這時我們又開始重新思考Redis的定位:Cache還是Storage?

 

如果作為Cache的話,似乎除了有些非常特殊的業務場景,必須要使用Redis的某種數據結構之外,我們使用Memcached可能更合適,畢竟Memcached無論客戶端包和服務器本身更久經考驗。

如果是作為存儲Storage的話,我們面臨的最大的問題是無論是持久化還是復制都沒有辦法解決Redis單點問題,即一台Redis掛掉了,沒有太好的辦法能夠快速的恢復,通常幾十G的持久化數據,Redis重啟加載需要幾個小時的時間,而復制又有缺陷,如何解決呢?

1. 主動復制避開Redis復制缺陷。

既然Redis的復制功能有缺陷,那么我們不妨放棄Redis本身提供的復制功能,我們可以采用主動復制的方式來搭建我們的集群環境。

所謂主動復制是指由業務端或者通過代理中間件對 Redis存儲的數據進行雙寫或多寫,通過數據的多份存儲來達到與復制相同的目的,主動復制不僅限於用在Redis集群上,目前很多公司采用主動復制的技 術來解決MySQL主從之間復制的延遲問題,比如Twitter還專門開發了用於復制和分區的中間件gizzard(https://github.com/twitter/gizzard) 。

主動復制雖然解決了被動復制的延遲問題,但也帶來了新 的問題,就是數據的一致性問題,數據寫2次或多次,如何保證多份數據的一致性呢?如果你的應用對數據一致性要求不高,允許最終一致性的話,那么通常簡單的 解決方案是可以通過時間戳或者vector clock等方式,讓客戶端同時取到多份數據並進行校驗,如果你的應用對數據一致性要求非常高,那么就需要引入一些復雜的一致性算法比如Paxos來保證數據的一致性,但是寫入性能也會相應下降很多。

通過主動復制,數據多份存儲我們也就不再擔心Redis單點故障的問題了,如果一組Redis集群掛掉,我們可以讓業務快速切換到另一組Redis上,降低業務風險。

通過主動復制我們解決了Redis單點故障問題,那么還有一個重要的問題需要解決:容量規划與在線擴容問題。

 

我們前面分析過Redis的適用場景是全部數據存儲在 內存中,而內存容量有限,那么首先需要根據業務數據量進行初步的容量規划,比如你的業務數據需要100G存儲空間,假設服務器內存是48G,那么根據上一 篇我們討論的Redis磁盤IO的問題,我們大約需要3~4台服務器來存儲。這個實際是對現有業務情況所做的一個容量規划,假如業務增長很快,很快就會發 現當前的容量已經不夠了,Redis里面存儲的數據很快就會超過物理內存大小,那么如何進行Redis的在線擴容呢?

Redis的作者提出了一種叫做presharding的方案來解決動態擴容和數據分區的問題,實際就是在同一台機器上部署多個Redis實例的方式,當容量不夠時將多個實例拆分到不同的機器上,這樣實際就達到了擴容的效果。

拆分過程如下:

    在新機器上啟動好對應端口的Redis實例。
  • 配置新端口為待遷移端口的從庫。
  • 待復制完成,與主庫完成同步后,切換所有客戶端配置到新的從庫的端口。
  • 配置從庫為新的主庫。
  • 移除老的端口實例。
  • 重復上述過程遷移好所有的端口到指定服務器上。

以上拆分流程是Redis作者提出的一個平滑遷移的過程,不過該拆分方法還是很依賴Redis本身的復制功能的,如果主庫快照數據文件過大,這個復制的過程也會很久,同時會給主庫帶來壓力。所以做這個拆分的過程最好選擇為業務訪問低峰時段進行。

我們線上的系統使用了我們自己改進版的Redis,主要解決了Redis沒有增量復制的缺陷,能夠完成類似Mysql Binlog那樣可以通過從庫請求日志位置進行增量復制。

 

我們的持久化方案是首先寫Redis的AOF文件,並 對這個AOF文件按文件大小進行自動分割滾動,同時關閉Redis的Rewrite命令,然后會在業務低峰時間進行內存快照存儲,並把當前的AOF文件位 置一起寫入到快照文件中,這樣我們可以使快照文件與AOF文件的位置保持一致性,這樣我們得到了系統某一時刻的內存快照,並且同時也能知道這一時刻對應的 AOF文件的位置,那么當從庫發送同步命令時,我們首先會把快照文件發送給從庫,然后從庫會取出該快照文件中存儲的AOF文件位置,並將該位置發給主庫, 主庫會隨后發送該位置之后的所有命令,以后的復制就都是這個位置之后的增量信息了。

目前大部分互聯網公司使用MySQL作為數據的主要持久化存儲,那么如何讓Redis與MySQL很好的結合在一起呢?我們主要使用了一種基於MySQL作為主庫,Redis作為高速數據查詢從庫的異構讀寫分離的方案。

 

為此我們專門開發了自己的MySQL復制工具,可以方便的實時同步MySQL中的數據到Redis上。

(MySQL-Redis 異構讀寫分離)

總結:

    Redis的復制功能沒有增量復制,每次重連都會把主庫整個內存快照發給從庫,所以需要避免向在線服務的壓力較大的主庫上增加從庫。
  • Redis的復制由於會使用快照持久化方式,所以如果你的Redis持久化方式選擇的是日志追加方式(aof),那么系統有可能在同一時刻既做aof日志 文件的同步刷寫磁盤,又做快照寫磁盤操作,這個時候Redis的響應能力會受到影響。所以如果選用aof持久化,則加從庫需要更加謹慎。
  • 可以使用主動復制和presharding方法進行Redis集群搭建與在線擴容。



http://www.infoq.com/cn/articles/tq-redis-copy-build-scalable-cluster



 

 

[置頂] 基於redis分布式緩存實現

分類: 分布式相關

簡單說明下,寫此文章算是對自己近一段工作的總結,希望能對你有點幫助,同時也是自己的一點小積累。


一.為什么選擇redis

在項目中使用redis做為緩存,還沒有使用memcache,考慮因素主要有兩點:

1.redis豐富的數據結構,其hash,list,set以及功能豐富的String的支持,對於實際項目中的使用有很大的幫忙。(可參考官網redis.io

2.redis單點的性能也非常高效(利用項目中的數據測試優於memcache).

基於以上考慮,因此選用了redis來做為緩存應用。

二.分布式緩存的架構設計

1.架構設計

由於redis是單點,項目中需要使用,必須自己實現分布式。基本架構圖如下所示:



2.分布式實現

通過key做一致性哈希,實現key對應redis結點的分布。

一致性哈希的實現:

l        hash值計算:通過支持MD5與MurmurHash兩種計算方式,默認是采用MurmurHash,高效的hash計算。

l        一致性的實現:通過Java的TreeMap來模擬環狀結構,實現均勻分布

3.client的選擇

對於jedis修改的主要是分區模塊的修改,使其支持了跟據BufferKey進行分 區,跟據不同的redis結點信息,可以初始化不同的ShardInfo,同時也修改了JedisPool的底層實現,使其連接pool池支持跟據 key,value的構造方法,跟據不同ShardInfos,創建不同的jedis連接客戶端,達到分區的效果,供應用層調用

4.模塊的說明

l        臟數據處理模塊,處理失敗執行的緩存操作。

l        屏蔽監控模塊,對於jedis操作的異常監控,當某結點出現異常可控制redis結點的切除等操作。

整個分布式模塊通過hornetq,來切除異常redis結點。對於新結點的增加,也可以通過reload方法實現增加。(此模塊對於新增結點也可以很方便實現)

對於以上分布式架構的實現滿足了項目的需求。另外使用中對於一些比較重要用途的緩存數據 可以單獨設置一些redis結點,設定特定的優先級。另外對於緩存接口的設計,也可以跟據需求,實現基本接口與一些特殊邏輯接口。對於cas相關操作,以 及一些事物操作可以通過其watch機制來實現。(參考我以前寫的redis事物介紹


以上是基於redis分布式架構的介紹!但是應用中讀寫都是在一起的。 相關寫是在應用操作后flush或者update的,有一定的耦合。為了使讀寫分離,以及緩存模塊跟應用的耦合更小,考慮使用mysql binlog來刷新緩存。以下是基於binlog刷新可性行分析以及實現過程中需要注意的地方。


三.采用binlog架構刷新緩存可行性分析

1.Mysql日志格式介紹可參考我以前的的介紹

2.對於使用MIXED日志格式,此日志格式,記錄的是對應數據庫操作的SQL語句,采用此日志方式存在的問題:

l        對於一些未任何更新操作的SQl語句,像條件不滿足,對應的sql也會記錄到binlog日志中。

l        SQL語句記錄的未必包括所有的更新操作。

l        對於一些分布式數據庫,對於SQL中的where條件指定的是非均衡字段,也許會存在多條SQL,跟設計有關!

基於以上考慮,采用MIXED的日志格式進行binlog解析是行不通的。(官網給出的指示是failed statementsare not logged ,但不包括語法沒錯誤,更新條件不符合對應的SQL)

3.采用ROW日志格式

對於此日志格式,每行變化都有對應的記錄,此日志格式,對於解析及采集數據都是非常方便的,也只有采用此日志格式,才能基於binlog修改,做刷新緩存相關方案的設計。但是基於此日志格式也存在一些問題:

l        需要考慮項目中是否有大量的批量的update操作,如果采用此日志格式,批量操作每一行修改都會記錄一條日志,大量的批量操作所產生的日志量,以及所帶來的IO開銷是否可以接受。

通過以上分析,最終項目中還是考慮基於ROW日志格式進行緩存刷新,還有一個問題需要考 慮,在應用層DB進行了相應的update操作后,所產生的Binlog是會帶來一定的延遲,如果Binlog處理模塊正常運行,數據是的延遲會非常 少,MS級別以內,對用戶體驗是沒有感知的,但是Binlog模塊是多點,異常,以及相應的延遲肯定會是存在的,這樣,緩存數據肯定會存在臟數據。

不過通過以上方案,數據能達到最終一致性,因此how to權衡,需要考慮。

 通過以上分析,是否采用Binlog來做緩存數據刷新相信大家有一個基本概念了

四.基於binlog刷新緩存的實現時注意的地方

1.如果是采用java做相關開發,可以使用開源的tungstenAPI

2.Binlog日志解析是按照mysql 的master/slave同步流程來實現,即一個線程同步,一個線程解析。

3.設計是可分Binlog處理模塊以及緩存處理SqlEvent兩部分,其中Binlog處理解析好對應的SqlEvent,然后對應的緩存刷新處理SqlEvent,一個簡單的生產者-消費者模式。

4.對於多個Binlog處理模塊可以是單點,也可以是通過一些協同工具來管理,看需求。可以使用ZooKeeper等。

5.對於分布式緩存中的數據,對於Binlog來刷新的緩存數據會存在load數據的問題,為了減輕DB的額外壓力,flush操作可在get緩存數據處完成。看需求,如果讀寫完全分享的話此DB的額外壓力可以接收的話也可行。

6.對於緩存數據性一致性要求比較高的,可以通過版本號來控制,即在應用層引入一定的耦合,在DB操作時帶mark ,緩存刷新是也mark,另外get操作時比較雙版本號來達到數據的一致性。(此跟5談論的一定的聯系,讀寫是否完全分離,以及相應一致性實現的一些方法)

五.一點心得

前前后后,對redis完成調研,以及相關的一些使用,分布式緩存的實現,基於binlog方式的修改等,接觸有一年多了,這段時間下來,學了很多,以上算是一點小記,這部分工作的一點小記。實現過程中存在更多的問題。

對於調研相關的一些工作,一定要做的仔細,相應的細節一定要了解透徹,否則也許一此小問題會導致整個方案的不可行,甚至更大的的問題。連鎖反應!

接下來有時間會寫一篇關於BloomFilter的的文章 ,以及D-Left_BloomFilter,在此說明,只為自己有更大的動力去完成它。項目中實現了D-Left_BloomFilter,但在網上沒有相關實現,在對其優化后,會在博文上做一些小小的記錄。


以上如果有什么不對的地方請指正,有什么相關問題也可以跟我聯系,可以一起交流學習!


 

[置頂] 基於redis分布式緩存實現

分類: 分布式相關

簡單說明下,寫此文章算是對自己近一段工作的總結,希望能對你有點幫助,同時也是自己的一點小積累。


一.為什么選擇redis

在項目中使用redis做為緩存,還沒有使用memcache,考慮因素主要有兩點:

1.redis豐富的數據結構,其hash,list,set以及功能豐富的String的支持,對於實際項目中的使用有很大的幫忙。(可參考官網redis.io

2.redis單點的性能也非常高效(利用項目中的數據測試優於memcache).

基於以上考慮,因此選用了redis來做為緩存應用。

二.分布式緩存的架構設計

1.架構設計

由於redis是單點,項目中需要使用,必須自己實現分布式。基本架構圖如下所示:



2.分布式實現

通過key做一致性哈希,實現key對應redis結點的分布。

一致性哈希的實現:

l        hash值計算:通過支持MD5與MurmurHash兩種計算方式,默認是采用MurmurHash,高效的hash計算。

l        一致性的實現:通過java的TreeMap來模擬環狀結構,實現均勻分布

3.client的選擇

對於jedis修改的主要是分區模塊的修改,使其支持了跟據BufferKey進行分 區,跟據不同的redis結點信息,可以初始化不同的ShardInfo,同時也修改了JedisPool的底層實現,使其連接pool池支持跟據 key,value的構造方法,跟據不同ShardInfos,創建不同的jedis連接客戶端,達到分區的效果,供應用層調用

4.模塊的說明

l        臟數據處理模塊,處理失敗執行的緩存操作。

l        屏蔽監控模塊,對於jedis操作的異常監控,當某結點出現異常可控制redis結點的切除等操作。

整個分布式模塊通過hornetq,來切除異常redis結點。對於新結點的增加,也可以通過reload方法實現增加。(此模塊對於新增結點也可以很方便實現)

對於以上分布式架構的實現滿足了項目的需求。另外使用中對於一些比較重要用途的緩存數據 可以單獨設置一些redis結點,設定特定的優先級。另外對於緩存接口的設計,也可以跟據需求,實現基本接口與一些特殊邏輯接口。對於cas相關操作,以 及一些事物操作可以通過其watch機制來實現。(參考我以前寫的redis事物介紹


以上是基於redis分布式架構的介紹!但是應用中讀寫都是在一起的。 相關寫是在應用操作后flush或者update的,有一定的耦合。為了使讀寫分離,以及緩存模塊跟應用的耦合更小,考慮使用mysql binlog來刷新緩存。以下是基於binlog刷新可性行分析以及實現過程中需要注意的地方。


三.采用binlog架構刷新緩存可行性分析

1.Mysql日志格式介紹可參考我以前的的介紹

2.對於使用MIXED日志格式,此日志格式,記錄的是對應數據庫操作的SQL語句,采用此日志方式存在的問題:

l        對於一些未任何更新操作的SQl語句,像條件不滿足,對應的sql也會記錄到binlog日志中。

l        SQL語句記錄的未必包括所有的更新操作。

l        對於一些分布式數據庫,對於SQL中的where條件指定的是非均衡字段,也許會存在多條SQL,跟設計有關!

基於以上考慮,采用MIXED的日志格式進行binlog解析是行不通的。(官網給出的指示是failed statementsare not logged ,但不包括語法沒錯誤,更新條件不符合對應的SQL)

3.采用ROW日志格式

對於此日志格式,每行變化都有對應的記錄,此日志格式,對於解析及采集數據都是非常方便的,也只有采用此日志格式,才能基於binlog修改,做刷新緩存相關方案的設計。但是基於此日志格式也存在一些問題:

l        需要考慮項目中是否有大量的批量的update操作,如果采用此日志格式,批量操作每一行修改都會記錄一條日志,大量的批量操作所產生的日志量,以及所帶來的IO開銷是否可以接受。

通過以上分析,最終項目中還是考慮基於ROW日志格式進行緩存刷新,還有一個問題需要考 慮,在應用層DB進行了相應的update操作后,所產生的Binlog是會帶來一定的延遲,如果Binlog處理模塊正常運行,數據是的延遲會非常 少,MS級別以內,對用戶體驗是沒有感知的,但是Binlog模塊是多點,異常,以及相應的延遲肯定會是存在的,這樣,緩存數據肯定會存在臟數據。

不過通過以上方案,數據能達到最終一致性,因此how to權衡,需要考慮。

 通過以上分析,是否采用Binlog來做緩存數據刷新相信大家有一個基本概念了

四.基於binlog刷新緩存的實現時注意的地方

1.如果是采用java做相關開發,可以使用開源的tungstenAPI

2.Binlog日志解析是按照mysql 的master/slave同步流程來實現,即一個線程同步,一個線程解析。

3.設計是可分Binlog處理模塊以及緩存處理SqlEvent兩部分,其中Binlog處理解析好對應的SqlEvent,然后對應的緩存刷新處理SqlEvent,一個簡單的生產者-消費者模式。

4.對於多個Binlog處理模塊可以是單點,也可以是通過一些協同工具來管理,看需求。可以使用ZooKeeper等。

5.對於分布式緩存中的數據,對於Binlog來刷新的緩存數據會存在load數據的問題,為了減輕DB的額外壓力,flush操作可在get緩存數據處完成。看需求,如果讀寫完全分享的話此DB的額外壓力可以接收的話也可行。

6.對於緩存數據性一致性要求比較高的,可以通過版本號來控制,即在應用層引入一定的耦合,在DB操作時帶mark ,緩存刷新是也mark,另外get操作時比較雙版本號來達到數據的一致性。(此跟5談論的一定的聯系,讀寫是否完全分離,以及相應一致性實現的一些方法)

五.一點心得

前前后后,對redis完成調研,以及相關的一些使用,分布式緩存的實現,基於binlog方式的修改等,接觸有一年多了,這段時間下來,學了很多,以上算是一點小記,這部分工作的一點小記。實現過程中存在更多的問題。

對於調研相關的一些工作,一定要做的仔細,相應的細節一定要了解透徹,否則也許一此小問題會導致整個方案的不可行,甚至更大的的問題。連鎖反應!

接下來有時間會寫一篇關於BloomFilter的的文章 ,以及D-Left_BloomFilter,在此說明,只為自己有更大的動力去完成它。項目中實現了D-Left_BloomFilter,但在網上沒有相關實現,在對其優化后,會在博文上做一些小小的記錄。


以上如果有什么不對的地方請指正,有什么相關問題也可以跟我聯系,可以一起交流學習!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM