Redis的架构模式
單機版
特點:簡單
問題:
1、內(nèi)存容量有限 2、處理能力有限 3、無法高可用。
主從復制
Redis 的復制(replication)功能允許用戶根據(jù)一個 Redis 服務器來創(chuàng)建任意多個該服務器的復制品,其中被復制的服務器為主服務器(master),而通過復制創(chuàng)建出來的服務器復制品則為從服務器(slave)。 只要主從服務器之間的網(wǎng)絡連接正常,主從服務器兩者會具有相同的數(shù)據(jù),主服務器就會一直將發(fā)生在自己身上的數(shù)據(jù)更新同步 給從服務器,從而一直保證主從服務器的數(shù)據(jù)相同。
特點:
1、master/slave 角色
2、master/slave 數(shù)據(jù)相同
3、降低 master 讀壓力在轉交從庫
問題:
無法保證高可用
沒有解決 master 寫的壓力
哨兵
Redis sentinel 是一個分布式系統(tǒng)中監(jiān)控 redis 主從服務器,并在主服務器下線時自動進行故障轉移。其中三個特性:
監(jiān)控(Monitoring): Sentinel 會不斷地檢查你的主服務器和從服務器是否運作正常。
提醒(Notification): 當被監(jiān)控的某個 Redis 服務器出現(xiàn)問題時, Sentinel 可以通過 API 向管理員或者其他應用程序發(fā)送通知。
自動故障遷移(Automatic failover): 當一個主服務器不能正常工作時, Sentinel 會開始一次自動故障遷移操作。
特點:
1、保證高可用
2、監(jiān)控各個節(jié)點
3、自動故障遷移
缺點:主從模式,切換需要時間丟數(shù)據(jù)
沒有解決 master 寫的壓力
集群模式:
業(yè)界主流的Redis集群化方案主要包括以下幾個:
- 客戶端分片
- 代理分片
- 服務端分片
代理分片包括:
-
Codis
-
Twemproxy
服務端分片包括:
- Redis Cluster
它們還可以用是否中心化來劃分,其中客戶端分片、Redis Cluster屬于無中心化的集群方案,Codis、Tweproxy屬于中心化的集群方案。
是否中心化是指客戶端訪問多個Redis節(jié)點時,是直接訪問還是通過一個中間層Proxy來進行操作,直接訪問的就屬于無中心化的方案,通過中間層Proxy訪問的就屬于中心化的方案,它們有各自的優(yōu)劣,下面分別來介紹。
1 客戶端分片
客戶端分片主要是說,我們只需要部署多個Redis節(jié)點,具體如何使用這些節(jié)點,主要工作在客戶端。
客戶端通過固定的Hash算法,針對不同的key計算對應的Hash值,然后對不同的Redis節(jié)點進行讀寫。
客戶端分片集群模式 需要業(yè)務開發(fā)人員事先評估業(yè)務的請求量和數(shù)據(jù)量,然后讓DBA部署足夠的節(jié)點交給開發(fā)人員使用即可。
這個方案的優(yōu)點是部署非常方便,業(yè)務需要多少個節(jié)點DBA直接部署交付即可,剩下的事情就需要業(yè)務開發(fā)人員根據(jù)節(jié)點數(shù)量來編寫key的請求路由邏輯,制定一個規(guī)則,一般采用固定的Hash算法,把不同的key寫入到不同的節(jié)點上,然后再根據(jù)這個規(guī)則進行數(shù)據(jù)讀取。
可見,它的缺點是業(yè)務開發(fā)人員使用Redis的成本較高,需要編寫路由規(guī)則的代碼來使用多個節(jié)點,而且如果事先對業(yè)務的數(shù)據(jù)量評估不準確,后期的擴容和遷移成本非常高,因為節(jié)點數(shù)量發(fā)生變更后,Hash算法對應的節(jié)點也就不再是之前的節(jié)點了。
所以后來又衍生出了一致性哈希算法,就是為了解決當節(jié)點數(shù)量變更時,盡量減少數(shù)據(jù)的遷移和性能問題。
這種客戶端分片的方案一般用于業(yè)務數(shù)據(jù)量比較穩(wěn)定,后期不會有大幅度增長的業(yè)務場景下使用,只需要前期評估好業(yè)務數(shù)據(jù)量即可。
客戶端分片的特點
這實際上是一種靜態(tài)分片技術。Redis 實例的增減,都得手工調(diào)整分片程序。基于此分片機制的開源產(chǎn)品,現(xiàn)在仍不多見。
這種分片機制的性能比代理式更好(少了一個中間分發(fā)環(huán)節(jié))。但缺點是升級麻煩,對研發(fā)人員的個人依賴性強——需要有較強的程序開發(fā)能力做后盾。如果主力程序員離職,可能新的負責人,會選擇重寫一遍。
所以,這種方式下,可運維性較差。出現(xiàn)故障,定位和解決都得研發(fā)和運維配合著解決,故障時間變長。
這種方案,難以進行標準化運維,不太適合中小公司(除非有足夠的 DevOPS)。
2 代理分片 (代理型集群)
這種方案,將分片工作交給專門的代理程序來做。代理程序接收到來自業(yè)務程序的數(shù)據(jù)請求,根據(jù)路由規(guī)則,將這些請求分發(fā)給正確的 Redis 實例并返回給業(yè)務程序。
這種機制下,一般會選用第三方代理程序(而不是自己研發(fā)),因為后端有多個 Redis 實例,所以這類程序又稱為分布式中間件。
這樣的好處是,業(yè)務程序不用關心后端 Redis 實例,運維起來也方便。雖然會因此帶來些性能損耗,但對于 Redis 這種內(nèi)存讀寫型應用,相對而言是能容忍的。
Codis
隨著業(yè)務和技術的發(fā)展,人們越發(fā)覺得,當我需要使用Redis時,我們不想關心集群后面有多少個節(jié)點,我們希望我們使用的Redis是一個大集群,當我們的業(yè)務量增加時,這個大集群可以增加新的節(jié)點來解決容量不夠用和性能問題。
這種方式就是服務端分片方案,客戶端不需要關心集群后面有多少個Redis節(jié)點,只需要像使用一個Redis的方式去操作這個集群,這種方案將大大降低開發(fā)人員的使用成本,開發(fā)人員可以只需要關注業(yè)務邏輯即可,不需要關心Redis的資源問題。
多個節(jié)點組成的集群,如何讓開發(fā)人員像操作一個Redis時那樣來使用呢?這就涉及到多個節(jié)點是如何組織起來提供服務的,一般我們會在客戶端和服務端中間增加一個代理層,客戶端只需要操作這個代理層,代理層實現(xiàn)了具體的請求轉發(fā)規(guī)則,然后轉發(fā)請求到后面的多個節(jié)點上,因此這種方式也叫做中心化方式的集群方案,Codis就是以這種方式實現(xiàn)的集群化方案。
Proxy集群模式
Codis是由國人前豌豆莢大神開發(fā)的,采用中心化方式的集群方案。因為需要代理層Proxy來進行所有請求的轉發(fā),所以對Proxy的性能要求很高,Codis采用Go語言開發(fā),兼容了開發(fā)效率和性能。
Codis包含了多個組件:
- codis-proxy:主要負責對請求的讀寫進行轉發(fā)
- codis-dashbaord:統(tǒng)一的控制中心,整合了數(shù)據(jù)轉發(fā)規(guī)則、故障自動恢復、數(shù)據(jù)在線遷移、節(jié)點擴容縮容、自動化運維API等功能
- codis-group:基于Redis 3.2.8版本二次開發(fā)的Redis Server,增加了異步數(shù)據(jù)遷移功能
- codis-fe:管理多個集群的UI界面
可見Codis的組件還是挺多的,它的功能非常全,除了請求轉發(fā)功能之外,還實現(xiàn)了在線數(shù)據(jù)遷移、節(jié)點擴容縮容、故障自動恢復等功能。
Codis的Proxy就是負責請求轉發(fā)的組件,它內(nèi)部維護了請求轉發(fā)的具體規(guī)則,Codis把整個集群劃分為1024個槽位,在處理讀寫請求時,采用crc32Hash算法計算key的Hash值,然后再根據(jù)Hash值對1024個槽位取模,最終找到具體的Redis節(jié)點。
Codis最大的特點就是可以在線擴容,在擴容期間不影響客戶端的訪問,也就是不需要停機。這對業(yè)務使用方是極大的便利,當集群性能不夠時,就可以動態(tài)增加節(jié)點來提升集群的性能。
為了實現(xiàn)在線擴容,保證數(shù)據(jù)在遷移過程中還有可靠的性能,Codis針對Redis進行了修改,增加了針對異步遷移數(shù)據(jù)相關命令,它基于Redis 3.2.8進行開發(fā),上層配合Dashboard和Proxy組件,完成對業(yè)務無損的數(shù)據(jù)遷移和擴容功能。
因此,要想使用Codis,必須使用它內(nèi)置的Redis,這也就意味著Codis中的Redis是否能跟上官方最新版的功能特性,可能無法得到保障,這取決于Codis的維護方,目前Codis已經(jīng)不再維護,所以使用Codis時只能使用3.2.8版的Redis,這是一個痛點。
另外,由于集群化都需要部署多個節(jié)點,因此操作集群并不能完全像操作單個Redis一樣實現(xiàn)所有功能,主要是對于操作多個節(jié)點可能產(chǎn)生問題的命令進行了禁用或限制,具體可參考Codis不支持的命令列表。
但這不影響它是一個優(yōu)秀的集群化方案,由于我司使用Redis集群方案較早,那時Redis Cluster還不夠成熟,所以我司使用的Redis集群方案就是Codis。目前我的工作主要是圍繞Codis展開的,我們公司對Codis進行了定制開發(fā),還對Redis進行了一些改造,讓Codis支持了跨多個數(shù)據(jù)中心的數(shù)據(jù)同步,因此我對Codis的代碼比較熟悉,后面會專門寫一些文章來剖析Codis的實現(xiàn)原理,學習它的原理,這對我們理解分布式存儲有很大的幫助!
Twemproxy
Twemproxy是由Twitter開源的集群化方案,它既可以做Redis Proxy,還可以做Memcached Proxy。
它的功能比較單一,只實現(xiàn)了請求路由轉發(fā),沒有像Codis那么全面有在線擴容的功能,它解決的重點就是把客戶端分片的邏輯統(tǒng)一放到了Proxy層而已,其他功能沒有做任何處理。
Tweproxy推出的時間最久,在早期沒有好的服務端分片集群方案時,應用范圍很廣,而且性能也極其穩(wěn)定。
但它的痛點就是無法在線擴容、縮容,這就導致運維非常不方便,而且也沒有友好的運維UI可以使用。
Twemproxy 是一個 Twitter 開源的一個 redis 和 memcache 快速/輕量級代理服務器; Twemproxy 是一個快速的單線程代理程序,支持 Memcached ASCII 協(xié)議和 redis 協(xié)議。
特點:
1、多種 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins
2、支持失敗節(jié)點自動刪除
3、后端 Sharding 分片邏輯對業(yè)務透明,業(yè)務方的讀寫方式和操作單個 Redis 一致
缺點:
增加了新的 proxy,需要維護其高可用。failover 邏輯需要自己實現(xiàn),其本身不能支持故障的自動轉移可擴展性差,進行擴縮容都需要手動干預
Codis就是因為在這種背景下才衍生出來的。
3 服務端分片(直連型集群)
采用中間加一層Proxy的中心化模式時,這就對Proxy的要求很高,因為它一旦出現(xiàn)故障,那么操作這個Proxy的所有客戶端都無法處理,要想實現(xiàn)Proxy的高可用,還需要另外的機制來實現(xiàn),例如Keepalive。
而且增加一層Proxy進行轉發(fā),必然會有一定的性能損耗,那么除了客戶端分片和上面提到的中心化的方案之外,還有比較好的解決方案么?
直連型集群。從redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用無中心結構,每個節(jié)點保存數(shù)據(jù)和整個集群狀態(tài),每個節(jié)點都和其他所有節(jié)點連接。
Redis Cluster
Redis官方推出的Redis Cluster另辟蹊徑,它沒有采用中心化模式的Proxy方案,而是把請求轉發(fā)邏輯一部分放在客戶端,一部分放在了服務端,它們之間互相配合完成請求的處理。
Redis Cluster是在Redis 3.0推出的,早起的Redis Cluster由于沒有經(jīng)過嚴格的測試和生產(chǎn)驗證,所以并沒有廣泛推廣開來。也正是在這樣的背景下,業(yè)界衍生了出了上面所說的中心化集群方案:Codis和Tweproxy。
但隨著Redis的版本迭代,Redis官方的Cluster也越來越穩(wěn)定,更多人開始采用官方的集群化方案。也正是因為它是官方推出的,所以它的持續(xù)維護性可以得到保障,這就比那些第三方的開源方案更有優(yōu)勢。
Redis Cluster沒有了中間的Proxy代理層,那么是如何進行請求的轉發(fā)呢?
Redis把請求轉發(fā)的邏輯放在了Smart Client中,要想使用Redis Cluster,必須升級Client SDK,這個SDK中內(nèi)置了請求轉發(fā)的邏輯,所以業(yè)務開發(fā)人員同樣不需要自己編寫轉發(fā)規(guī)則,Redis Cluster采用16384個槽位進行路由規(guī)則的轉發(fā)。
沒有了Proxy層進行轉發(fā),客戶端可以直接操作對應的Redis節(jié)點,這樣就少了Proxy層轉發(fā)的性能損耗。
Redis Cluster也提供了在線數(shù)據(jù)遷移、節(jié)點擴容縮容等功能,內(nèi)部還內(nèi)置了哨兵完成故障自動恢復功能,可見它是一個集成所有功能于一體的Cluster。因此它在部署時非常簡單,不需要部署過多的組件,對于運維極其友好。
Redis Cluster在節(jié)點數(shù)據(jù)遷移、擴容縮容時,對于客戶端的請求處理也做了相應的處理。當客戶端訪問的數(shù)據(jù)正好在遷移過程中時,服務端與客戶端制定了一些協(xié)議,來告知客戶端去正確的節(jié)點上訪問,幫助客戶端訂正自己的路由規(guī)則。
雖然Redis Cluster提供了在線數(shù)據(jù)遷移的功能,但它的遷移性能并不高,遷移過程中遇到大key時還有可能長時間阻塞遷移的兩個節(jié)點,這個功能相較于Codis來說,Codis數(shù)據(jù)遷移性能更好。關注公眾號Java技術棧可以閱讀一些集群搭建實戰(zhàn)的文章。
現(xiàn)在越來越多的公司開始采用Redis Cluster,有能力的公司還在它的基礎上進行了二次開發(fā)和定制,來解決Redis Cluster存在的一些問題,我們期待Redis Cluster未來有更好的發(fā)展。
特點:
1、無中心架構(不存在哪個節(jié)點影響性能瓶頸),少了 proxy 層。
2、數(shù)據(jù)按照 slot 存儲分布在多個節(jié)點,節(jié)點間數(shù)據(jù)共享,可動態(tài)調(diào)整數(shù)據(jù)分布。
3、可擴展性,可線性擴展到 1000 個節(jié)點,節(jié)點可動態(tài)添加或刪除。
4、高可用性,部分節(jié)點不可用時,集群仍可用。通過增加 Slave 做備份數(shù)據(jù)副本
5、實現(xiàn)故障自動 failover,節(jié)點之間通過 gossip 協(xié)議交換狀態(tài)信息,用投票機制完成 Slave到 Master 的角色提升。
缺點:
1、資源隔離性較差,容易出現(xiàn)相互影響的情況。
2、數(shù)據(jù)通過異步復制,不保證數(shù)據(jù)的強一致性
集群的必要性
所謂的集群,就是通過添加服務器的數(shù)量,提供相同的服務,從而讓服務器達到一個穩(wěn)定、高效的狀態(tài)。
1.1.1 使用 redis 集群的必要性
問題:我們已經(jīng)部署好了redis,并且能啟動一個redis,實現(xiàn)數(shù)據(jù)的讀寫,為什么還要學習redis集群?
答:(1)單個redis存在不穩(wěn)定性。當redis服務宕機了,就沒有可用的服務了。
(2)單個redis的讀寫能力是有限的。
總結:redis集群是為了強化redis的讀寫能力。
1.1.2 如何學習redis集群
說明:(1)redis集群中,每一個redis稱之為一個節(jié)點。
? (2)redis集群中,有兩種類型的節(jié)點:主節(jié)點(master)、從節(jié)點(slave)。
? (3)redis集群,是基于redis主從復制實現(xiàn)。
?
所以,學習redis集群,就是從學習redis主從復制模型開始的。
redis主從復制
1.1 概念
? 主從復制模型中,有多個redis節(jié)點。
? 其中,有且僅有一個為主節(jié)點Master。從節(jié)點Slave可以有多個。
只要網(wǎng)絡連接正常,Master會一直將自己的數(shù)據(jù)更新同步給Slaves,保持主從同步。
1.1 特點
(1)主節(jié)點Master可讀、可寫.
(2)從節(jié)點Slave只讀。(read-only)
因此,主從模型可以提高讀的能力,在一定程度上緩解了寫的能力。因為能寫仍然只有Master節(jié)點一個,可以將讀的操作全部移交到從節(jié)點上,變相提高了寫能力。
1.1 基于配置實現(xiàn)
1.1.1 需求
| 從節(jié)點(兩個) | 6381、6382 |
1.1.2 配置步驟
(1)在/usr/local目錄下,創(chuàng)建一個/redis/master-slave目錄
[root@node0719 local]# mkdir -p redis/master-slave
(2)在master-slave目錄下,創(chuàng)建三個子目錄6380、6381、6382
[root@node0719 master-slave]# mkdir 6380 6381 6382
(3)依次拷貝redis解壓目錄下的redis.conf配置文件,到這三個子目錄中。
[root@node0719 master-slave]# cp /root/redis-3.2.9/redis.conf ./6380/ [root@node0719 master-slave]# cp /root/redis-3.2.9/redis.conf ./6381/ [root@node0719 master-slave]# cp /root/redis-3.2.9/redis.conf ./6382/
(4)進入6380目錄,修改redis.conf,將port端口修改成6380即可。
[root@node0719 master-slave]# cd ./6380 [root@node0719 6380]# vim redis.conf
(5)進入6381目錄,修改redis.conf,將port端口改成6381,同時指定開啟主從復制。
[root@node0719 6380]# cd ../6381 [root@node0719 6381]# vim redis.conf
(6)進入6382目錄,修改redis.conf,將port端口改成6382,同時指定開啟主從復制。
[root@node0719 6380]# cd ../6382 [root@node0719 6381]# vim redis.conf
1.1.1 測試
(1)打開三個xshell窗口,在每一個窗口中,啟動一個redis節(jié)點。查看日志輸出。(不要改成后臺模式啟動,看不到日志,不直觀)
[root@node0719 master-slave]# cd 6380 && redis-server ./redis.conf [root@node0719 master-slave]# cd 6381 && redis-server ./redis.conf [root@node0719 master-slave]# cd 6382 && redis-server ./redis.conf
(2)另外再打開三個xshell窗口,在每一個窗口中,登陸一個redis節(jié)點
[root@node0719 ~]# redis-cli -p 6380 [root@node0719 ~]# redis-cli -p 6381 [root@node0719 ~]# redis-cli -p 6382
(3)在主節(jié)點6380上,進行讀寫操作,操作成功
[root@node0719 ~]# redis-cli -p 6380 127.0.0.1:6380> set user:name zs OK 127.0.0.1:6380> get user:name "zs" 127.0.0.1:6380>
(4)在從節(jié)點6381上
讀操作執(zhí)行成功,并且成功從6380上同步了數(shù)據(jù)
[root@node0719 ~]# redis-cli -p 6381 127.0.0.1:6381> get user:name "zs"
寫操作執(zhí)行失敗。(從節(jié)點,只能讀,不能寫)
127.0.0.1:6381> set user:age 18 (error) READONLY You can't write against a read only slave.
Sentinel哨兵模式
1.1 主從模式的缺陷
當主節(jié)點宕機了,整個集群就沒有可寫的節(jié)點了。
由于從節(jié)點上備份了主節(jié)點的所有數(shù)據(jù),那在主節(jié)點宕機的情況下,如果能夠將從節(jié)點變成一個主節(jié)點,是不是就可以解決這個問題了呢?
答:是的,這個就是Sentinel哨兵的作用。
1.2 哨兵的任務
Redis 的 Sentinel 系統(tǒng)用于管理多個 Redis 服務器(instance), 該系統(tǒng)執(zhí)行以下三個任務:
監(jiān)控(Monitoring****): Sentinel 會不斷地檢查你的主服務器和從服務器是否運作正常。
提醒(Notification****): 當被監(jiān)控的某個 Redis 服務器出現(xiàn)問題時, Sentinel 可以通過 API 向管理員或者其他應用程序發(fā)送通知。
自動故障遷移(Automatic failover****): 當一個主服務器不能正常工作時, Sentinel 會開始一次自動故障遷移操作, 它會進行選舉,將其中一個從服務器升級為新的主服務器, 并讓失效主服務器的其他從服務器改為復制新的主服務器; 當客戶端試圖連接失效的主服務器時, 集群也會向客戶端返回新主服務器的地址, 使得集群可以使用新主服務器代替失效服務器。
1.2.1 監(jiān)控(Monitoring)
(1)Sentinel可以監(jiān)控任意多個Master和該Master下的Slaves。(即多個主從模式)
(2)同一個哨兵下的、不同主從模型,彼此之間相互獨立。
(3)Sentinel會不斷檢查Master和Slaves是否正常。
1.2.2 自動故障切換(Automatic failover)
1.2.2.1 Sentinel網(wǎng)絡
監(jiān)控同一個Master的Sentinel會自動連接,組成一個分布式的Sentinel網(wǎng)絡,互相通信并交換彼此關于被監(jiān)視服務器的信息。下圖中,三個監(jiān)控s1的Sentinel,自動組成Sentinel網(wǎng)絡結構。
疑問:為什么要使用sentinel網(wǎng)絡呢?
答:當只有一個sentinel的時候,如果這個sentinel掛掉了,那么就無法實現(xiàn)自動故障切換了。
在sentinel網(wǎng)絡中,只要還有一個sentinel活著,就可以實現(xiàn)故障切換。
1.1.1.1 故障切換的過程
(1)投票(半數(shù)原則)
當任何一個Sentinel發(fā)現(xiàn)被監(jiān)控的Master下線時,會通知其它的Sentinel開會,投票確定該Master是否下線(半數(shù)以上,所以sentinel通常配奇數(shù)個)。
(2)選舉
當Sentinel確定Master下線后,會在所有的Slaves中,選舉一個新的節(jié)點,升級成Master節(jié)點。
其它Slaves節(jié)點,轉為該節(jié)點的從節(jié)點。
(3)原Master重新上線
當原Master節(jié)點重新上線后,自動轉為當前Master節(jié)點的從節(jié)點。
1.1 哨兵模式部署
1.1.1 需求
前提:已經(jīng)存在一個正在運行的主從模式。
另外,配置三個Sentinel實例,監(jiān)控同一個Master節(jié)點。
1.1.2 配置Sentinel
(1)在/usr/local目錄下,創(chuàng)建/redis/sentinels/目錄
[root@node0719 local]# mkdir -p redis/sentinels
(2)在/sentinels目錄下,以次創(chuàng)建s1、s2、s3三個子目錄中
[root@node0719 sentinels]# mkdir s1 s2 s3
(3)依次拷貝redis解壓目錄下的sentinel.conf文件,到這三個子目錄中
[root@node0719 sentinels]# cp /root/redis-3.2.9/sentinel.conf ./s1/ [root@node0719 sentinels]# cp /root/redis-3.2.9/sentinel.conf ./s2/ [root@node0719 sentinels]# cp /root/redis-3.2.9/sentinel.conf ./s3/
(4)依次修改s1、s2、s3子目錄中的sentinel.conf文件,修改端口,并指定要監(jiān)控的主節(jié)點。(從節(jié)點不需要指定,sentinel會自動識別)
S1哨兵配置如下:
S2哨兵配置如下:
S3哨兵配置如下:
(5)再打開三個xshell窗口,在每一個窗口中,啟動一個哨兵實例,并觀察日志輸出
[root@node0719 sentinels]# redis-sentinel ./s1/sentinel.conf [root@node0719 sentinels]# redis-sentinel ./s2/sentinel.conf [root@node0719 sentinels]# redis-sentinel ./s3/sentinel.conf
對于用redis-server啟動哨兵的方式如下:
[root@node0719 sentinels]# redis-server ./s1/sentinel.conf --sentinel
1.1.3 測試
(1)先關閉6380節(jié)點(kill掉)。發(fā)現(xiàn),確實重新指定了一個主節(jié)點
(2)再次上線6380節(jié)點。發(fā)現(xiàn),6380節(jié)點成為了新的主節(jié)點的從節(jié)點。
1.2 結論
Sentinel哨兵模式,確實能實現(xiàn)自動故障切換。提供穩(wěn)定的服務
redis集群搭建
Redis Cluster屬于無中心化的集群方案,
一、Redis Cluster(Redis集群)簡介
RedisCluster 是 Redis 的親兒子,它是 Redis 作者自己提供的 Redis 集群化方案。 相對于 Codis 的不同,它是去中心化的,如圖所示,該集群有三個 Redis 節(jié)點組成, 每個節(jié)點負責整個集群的一部分數(shù)據(jù),每個節(jié)點負責的數(shù)據(jù)多少可能不一樣。這三個節(jié)點相 互連接組成一個對等的集群,它們之間通過一種特殊的二進制協(xié)議相互交互集群信息。
Redis Cluster 將所有數(shù)據(jù)劃分為 16384 的 slots,它比 Codis 的 1024 個槽劃分得更為精細,每個節(jié)點負責其中一部分槽位。槽位的信息存儲于每個節(jié)點中,它不像 Codis,它不 需要另外的分布式存儲來存儲節(jié)點槽位信息。
當 Redis Cluster 的客戶端來連接集群時,它也會得到一份集群的槽位配置信息。這樣當客戶端要查找某個 key 時,可以直接定位到目標節(jié)點。
這點不同于 Codis,Codis 需要通過 Proxy 來定位目標節(jié)點,RedisCluster 是直接定 位。客戶端為了可以直接定位某個具體的 key 所在的節(jié)點,它就需要緩存槽位相關信息,這樣才可以準確快速地定位到相應的節(jié)點。同時因為槽位的信息可能會存在客戶端與服務器不一致的情況,還需要糾正機制來實現(xiàn)槽位信息的校驗調(diào)整。 另外,RedisCluster 的每個節(jié)點會將集群的配置信息持久化到配置文件中,所以必須確保配置文件是可寫的,而且盡量不要依靠人工修改配置文件。
- redis是一個開源的key value存儲系統(tǒng),受到了廣大互聯(lián)網(wǎng)公司的青睞。redis3.0版本之前只支持單例模式,在3.0版本及以后才支持集群,我這里用的是redis3.0.0版本;
- redis集群采用P2P模式,是完全去中心化的,不存在中心節(jié)點或者代理節(jié)點;
- redis集群是沒有統(tǒng)一的入口的,客戶端(client)連接集群的時候連接集群中的任意節(jié)點(node)即可,集群內(nèi)部的節(jié)點是相互通信的(PING-PONG機制),每個節(jié)點都是一個redis實例;
- 為了實現(xiàn)集群的高可用,即判斷節(jié)點是否健康(能否正常使用),redis-cluster有這么一個投票容錯機制:如果集群中超過半數(shù)的節(jié)點投票認為某個節(jié)點掛了,那么這個節(jié)點就掛了(fail)。這是判斷節(jié)點是否掛了的方法;
- 那么如何判斷集群是否掛了呢? -> 如果集群中任意一個節(jié)點掛了,而且該節(jié)點沒有從節(jié)點(備份節(jié)點),那么這個集群就掛了。這是判斷集群是否掛了的方法;
- 那么為什么任意一個節(jié)點掛了(沒有從節(jié)點)這個集群就掛了呢? -> 因為集群內(nèi)置了16384個slot(哈希槽),并且把所有的物理節(jié)點映射到了這16384[0-16383]個slot上,或者說把這些slot均等的分配給了各個節(jié)點。當需要在Redis集群存放一個數(shù)據(jù)(key-value)時,redis會先對這個key進行crc16算法,然后得到一個結果。再把這個結果對16384進行求余,這個余數(shù)會對應[0-16383]其中一個槽,進而決定key-value存儲到哪個節(jié)點中。所以一旦某個節(jié)點掛了,該節(jié)點對應的slot就無法使用,那么就會導致集群無法正常工作。
- 綜上所述,每個Redis集群理論上最多可以有16384個節(jié)點。
二、集群搭建需要的環(huán)境
防火墻設置
redis集群中的每個節(jié)點都需要建立2個tcp連接,監(jiān)聽這2個端口:一個端口稱之為“客戶端端口”,用于接受客戶端指令,與客戶端交互,比如6379;另一個端口稱之為“集群總線端口”,是在客戶端端口號上加10000,比如16379,用于節(jié)點之間通過二進制協(xié)議通訊。各節(jié)點通過集群總線檢測宕機節(jié)點、更新配置、故障轉移驗證等。客戶端只能使用客戶端端口,不能使用集群總線端口。請確保你的防火墻允許打開這兩個端口,否則redis集群沒法工作。客戶端端口和集群總線端口之間的差值是固定的,集群總線端口比客戶端端口高10000。
注意,關于集群的2個端口:
- 客戶端端口(一般是6379)需要對所有客戶端和集群節(jié)點開放,因為集群節(jié)點需要通過該端口轉移數(shù)據(jù)。
- 集群總線端口(一般是16379)只需對集群中的所有節(jié)點開放
這2個端口必須打開,否則集群沒法正常工作。
集群節(jié)點之間通過集群總線端口交互數(shù)據(jù),使用的協(xié)議不同于客戶端的協(xié)議,是二進制協(xié)議,這可以減少帶寬和處理時間。
Redis集群數(shù)據(jù)的分片
Redis集群不是使用一致性哈希,而是使用哈希槽。整個redis集群有16384個哈希槽,決定一個key應該分配到那個槽的算法是:計算該key的CRC16結果再模16834。
集群中的每個節(jié)點負責一部分哈希槽,比如集群中有3個節(jié)點,則:
- 節(jié)點A存儲的哈希槽范圍是:0 – 5500
- 節(jié)點B存儲的哈希槽范圍是:5501 – 11000
- 節(jié)點C存儲的哈希槽范圍是:11001 – 16384
這樣的分布方式方便節(jié)點的添加和刪除。比如,需要新增一個節(jié)點D,只需要把A、B、C中的部分哈希槽數(shù)據(jù)移到D節(jié)點。同樣,如果希望在集群中刪除A節(jié)點,只需要把A節(jié)點的哈希槽的數(shù)據(jù)移到B和C節(jié)點,當A節(jié)點的數(shù)據(jù)全部被移走后,A節(jié)點就可以完全從集群中刪除。
因為把哈希槽從一個節(jié)點移到另一個節(jié)點是不需要停機的,所以,增加或刪除節(jié)點,或更改節(jié)點上的哈希槽,也是不需要停機的。
如果多個key都屬于一個哈希槽,集群支持通過一個命令(或事務, 或lua腳本)同時操作這些key。通過“哈希標簽”的概念,用戶可以讓多個key分配到同一個哈希槽。哈希標簽在集群詳細文檔中有描述,這里做個簡單介紹:如果key含有大括號”{}”,則只有大括號中的字符串會參與哈希,比如”this{foo}”和”another{foo}”這2個key會分配到同一個哈希槽,所以可以在一個命令中同時操作他們。
Redis集群的一致性保證
Redis集群不能保證強一致性。一些已經(jīng)向客戶端確認寫成功的操作,會在某些不確定的情況下丟失。
產(chǎn)生寫操作丟失的第一個原因,是因為主從節(jié)點之間使用了異步的方式來同步數(shù)據(jù)。
一個寫操作是這樣一個流程:
-
1)客戶端向主節(jié)點B發(fā)起寫的操作
-
2)主節(jié)點B回應客戶端寫操作成功
-
3)主節(jié)點B向它的從節(jié)點B1,B2,B3同步該寫操作
從上面的流程可以看出來,主節(jié)點B并沒有等從節(jié)點B1,B2,B3寫完之后再回復客戶端這次操作的結果。所以,如果主節(jié)點B在通知客戶端寫操作成功之后,但同步給從節(jié)點之前,主節(jié)點B故障了,其中一個沒有收到該寫操作的從節(jié)點會晉升成主節(jié)點,該寫操作就這樣永遠丟失了。
就像傳統(tǒng)的數(shù)據(jù)庫,在不涉及到分布式的情況下,它每秒寫回磁盤。為了提高一致性,可以在寫盤完成之后再回復客戶端,但這樣就要損失性能。這種方式就等于Redis集群使用同步復制的方式。
基本上,在性能和一致性之間,需要一個權衡。
如果真的需要,Redis集群支持同步復制的方式,通過WAIT指令來實現(xiàn),這可以讓丟失寫操作的可能性降到很低。但就算使用了同步復制的方式,Redis集群依然不是強一致性的,在某些復雜的情況下,比如從節(jié)點在與主節(jié)點失去連接之后被選為主節(jié)點,不一致性還是會發(fā)生。
這種不一致性發(fā)生的情況是這樣的,當客戶端與少數(shù)的節(jié)點(至少含有一個主節(jié)點)網(wǎng)絡聯(lián)通,但他們與其他大多數(shù)節(jié)點網(wǎng)絡不通。比如6個節(jié)點,A,B,C是主節(jié)點,A1,B1,C1分別是他們的從節(jié)點,一個客戶端稱之為Z1。
當網(wǎng)絡出問題時,他們被分成2組網(wǎng)絡,組內(nèi)網(wǎng)絡聯(lián)通,但2組之間的網(wǎng)絡不通,假設A,C,A1,B1,C1彼此之間是聯(lián)通的,另一邊,B和Z1的網(wǎng)絡是聯(lián)通的。Z1可以繼續(xù)往B發(fā)起寫操作,B也接受Z1的寫操作。當網(wǎng)絡恢復時,如果這個時間間隔足夠短,集群仍然能繼續(xù)正常工作。如果時間比較長,以致B1在大多數(shù)的這邊被選為主節(jié)點,那剛才Z1發(fā)給B的寫操作都將丟失。
注意,Z1給B發(fā)送寫操作是有一個限制的,如果時間長度達到了大多數(shù)節(jié)點那邊可以選出一個新的主節(jié)點時,少數(shù)這邊的所有主節(jié)點都不接受寫操作。
這個時間的配置,稱之為節(jié)點超時(node timeout),對集群來說非常重要,當達到了這個節(jié)點超時的時間之后,主節(jié)點被認為已經(jīng)宕機,可以用它
Redis集群參數(shù)配置
我們后面會部署一個Redis集群作為例子,在那之前,先介紹一下集群在redis.conf中的參數(shù)。
- cluster-enabled?<yes/no>: 如果配置”yes”則開啟集群功能,此redis實例作為集群的一個節(jié)點,否則,它是一個普通的單一的redis實例。
- cluster-config-file?<filename>: 注意:雖然此配置的名字叫“集群配置文件”,但是此配置文件不能人工編輯,它是集群節(jié)點自動維護的文件,主要用于記錄集群中有哪些節(jié)點、他們的狀態(tài)以及一些持久化參數(shù)等,方便在重啟時恢復這些狀態(tài)。通常是在收到請求之后這個文件就會被更新。
- cluster-node-timeout?<milliseconds>: 這是集群中的節(jié)點能夠失聯(lián)的最大時間,超過這個時間,該節(jié)點就會被認為故障。如果主節(jié)點超過這個時間還是不可達,則用它的從節(jié)點將啟動故障遷移,升級成主節(jié)點。注意,任何一個節(jié)點在這個時間之內(nèi)如果還是沒有連上大部分的主節(jié)點,則此節(jié)點將停止接收任何請求。
- cluster-slave-validity-factor?<factor>: 如果設置成0,則無論從節(jié)點與主節(jié)點失聯(lián)多久,從節(jié)點都會嘗試升級成主節(jié)點。如果設置成正數(shù),則cluster-node-timeout乘以cluster-slave-validity-factor得到的時間,是從節(jié)點與主節(jié)點失聯(lián)后,此從節(jié)點數(shù)據(jù)有效的最長時間,超過這個時間,從節(jié)點不會啟動故障遷移。假設cluster-node-timeout=5,cluster-slave-validity-factor=10,則如果從節(jié)點跟主節(jié)點失聯(lián)超過50秒,此從節(jié)點不能成為主節(jié)點。注意,如果此參數(shù)配置為非0,將可能出現(xiàn)由于某主節(jié)點失聯(lián)卻沒有從節(jié)點能頂上的情況,從而導致集群不能正常工作,在這種情況下,只有等到原來的主節(jié)點重新回歸到集群,集群才恢復運作。
- cluster-migration-barrier?<count>:主節(jié)點需要的最小從節(jié)點數(shù),只有達到這個數(shù),主節(jié)點失敗時,它從節(jié)點才會進行遷移。更詳細介紹可以看本教程后面關于副本遷移到部分。
- cluster-require-full-coverage?<yes/no>:在部分key所在的節(jié)點不可用時,如果此參數(shù)設置為”yes”(默認值), 則整個集群停止接受操作;如果此參數(shù)設置為”no”,則集群依然為可達節(jié)點上的key提供讀操作。
port 7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes
開啟集群模式只需打開cluster-enabled配置項即可。每一個redis實例都包含一個配置文件,默認是nodes.conf,用于存儲此節(jié)點的一些配置信息。這個配置文件由redis集群的節(jié)點自行創(chuàng)建和更新,不能由人手動地去修改。
一個最小的集群需要最少3個主節(jié)點。第一次測試,強烈建議你配置6個節(jié)點:3個主節(jié)點和3個從節(jié)點
搭建準備
2.1 Redis集群至少需要3個節(jié)點,因為投票容錯機制要求超過半數(shù)節(jié)點認為某個節(jié)點掛了該節(jié)點才是掛了,所以2個節(jié)點無法構成集群。
2.2 要保證集群的高可用,需要每個節(jié)點都有從節(jié)點,也就是備份節(jié)點,所以Redis集群至少需要6臺服務器。因為我沒有那么多服務器,也啟動不了那么多虛擬機,所在這里搭建的是偽分布式集群,即一臺服務器虛擬運行6個redis實例,修改端口號為(7001-7006),當然實際生產(chǎn)環(huán)境的Redis集群搭建和這里是一樣的。
將redis-cluster/redis01文件復制5份到redis-cluster目錄下(redis02-redis06),創(chuàng)建6個redis實例,模擬Redis集群的6個節(jié)點。然后將其余5個文件下的redis.conf里面的端口號分別修改為7002-7006。分別如下圖所示:
創(chuàng)建redis02-06目錄
接著啟動所有redis節(jié)點,由于一個一個啟動太麻煩了,所以在這里創(chuàng)建一個批量啟動redis節(jié)點的腳本文件,命令為start-all.sh,文件內(nèi)容如下:
cd redis01 ./redis-server redis.conf cd .. cd redis02 ./redis-server redis.conf cd .. cd redis03 ./redis-server redis.conf cd .. cd redis04 ./redis-server redis.conf cd .. cd redis05 ./redis-server redis.conf cd .. cd redis06 ./redis-server redis.conf cd ..
創(chuàng)建好啟動腳本文件之后,需要修改該腳本的權限,使之能夠執(zhí)行,指令如下:
chmod +x start-all.sh
執(zhí)行start-all.sh腳本,啟動6個redis節(jié)點
至此6個redis節(jié)點啟動成功,接下來正式開啟搭建集群,
三:實戰(zhàn):搭建集群
Redis 官方提供了 redis-trib.rb 這個工具,作為搭建集群的專門工具。
Redis的實例全部運行之后,還需要redis-trib.rb工具來完成集群的創(chuàng)建,redis-trib.rb二進制文件在Redis包主目錄下的src目錄中,運行該工具依賴Ruby環(huán)境和gem,因此需要提前安裝Ruby。
因為這個工具是一個ruby腳本文件,所以這個工具的運行需要ruby的運行環(huán)境,該環(huán)境相當于JVM虛擬機環(huán)境,是的ruby腳本運行時,就相當于java語言的運行需要在jvm上。
安裝ruby,指令如下:
yum install ruby
/usr/local/redis/redis-4.0.9/src/redis-trib.rb create --replicas 1 \ 10.10.1.129:7001 10.10.1.129:7002 10.10.1.129:7003 \ 10.10.1.130:7004 10.10.1.130:7005 10.10.1.130:7006 \ 10.10.1.131:7007 10.10.1.131:7008 10.10.1.131:7009
--replicas 1 表示主從復制比例為 1:1,即一個主節(jié)點對應一個從節(jié)點;然后,默認給我們分配好了每個主節(jié)點和對應從節(jié)點服務,以及 solt 的大小,因為在 Redis 集群中有且僅有 16383 個 solt ,默認情況會給我們平均分配,當然你可以指定,后續(xù)的增減節(jié)點也可以重新分配。
/usr/local/redis/redis-4.0.9/src/redis-trib.rb create --replicas 1 \ > 10.10.1.129:7001 10.10.1.129:7002 10.10.1.129:7003 \ > 10.10.1.130:7004 10.10.1.130:7005 10.10.1.130:7006 \ > 10.10.1.131:7007 10.10.1.131:7008 10.10.1.131:7009 >>> Creating cluster >>> Performing hash slots allocation on 9 nodes... Using 4 masters: 10.10.1.129:7001 10.10.1.130:7004 10.10.1.131:7007 10.10.1.129:7002 Adding replica 10.10.1.131:7008 to 10.10.1.129:7001 Adding replica 10.10.1.129:7003 to 10.10.1.130:7004 Adding replica 10.10.1.130:7006 to 10.10.1.131:7007 Adding replica 10.10.1.131:7009 to 10.10.1.129:7002 Adding replica 10.10.1.130:7005 to 10.10.1.129:7001 M: 7a047cfaae70c30d0d7e1a5d9854eb7f11afe957 10.10.1.129:7001 slots:0-4095 (4096 slots) master M: 924d61969343b5cc2200bd3a2277e815dc76048c 10.10.1.129:7002 slots:12288-16383 (4096 slots) master S: b9ba251f575b5396da4bea307e25a98d85b3c504 10.10.1.129:7003 replicates a4a5de0be9bb5704eec17cbe0223076eb38fc4a4 M: a4a5de0be9bb5704eec17cbe0223076eb38fc4a4 10.10.1.130:7004 slots:4096-8191 (4096 slots) master S: bdc2f6b254459a6a6d038d93e5a3d3a67fe3e936 10.10.1.130:7005 replicates 7a047cfaae70c30d0d7e1a5d9854eb7f11afe957 S: 4ae20400d02e57e274f9b9f29d4ba120aa2b574c 10.10.1.130:7006 replicates 2b0f974e151cd798f474107ac68a47e188cc88a2 M: 2b0f974e151cd798f474107ac68a47e188cc88a2 10.10.1.131:7007 slots:8192-12287 (4096 slots) master S: b240d86fdf6abc73df059baf64b930387664da15 10.10.1.131:7008 replicates 7a047cfaae70c30d0d7e1a5d9854eb7f11afe957 S: 5b7989d5370aef41679e92a6bd34c30ac3be3581 10.10.1.131:7009 replicates 924d61969343b5cc2200bd3a2277e815dc76048c Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join........ >>> Performing Cluster Check (using node 10.10.1.129:7001) M: 7a047cfaae70c30d0d7e1a5d9854eb7f11afe957 10.10.1.129:7001 slots:0-4095 (4096 slots) master 2 additional replica(s) S: 4ae20400d02e57e274f9b9f29d4ba120aa2b574c 10.10.1.130:7006 slots: (0 slots) slave replicates 2b0f974e151cd798f474107ac68a47e188cc88a2 M: 924d61969343b5cc2200bd3a2277e815dc76048c 10.10.1.129:7002 slots:12288-16383 (4096 slots) master 1 additional replica(s) M: 2b0f974e151cd798f474107ac68a47e188cc88a2 10.10.1.131:7007 slots:8192-12287 (4096 slots) master 1 additional replica(s) S: 5b7989d5370aef41679e92a6bd34c30ac3be3581 10.10.1.131:7009 slots: (0 slots) slave replicates 924d61969343b5cc2200bd3a2277e815dc76048c S: b9ba251f575b5396da4bea307e25a98d85b3c504 10.10.1.129:7003 slots: (0 slots) slave replicates a4a5de0be9bb5704eec17cbe0223076eb38fc4a4 S: b240d86fdf6abc73df059baf64b930387664da15 10.10.1.131:7008 slots: (0 slots) slave replicates 7a047cfaae70c30d0d7e1a5d9854eb7f11afe957 S: bdc2f6b254459a6a6d038d93e5a3d3a67fe3e936 10.10.1.130:7005 slots: (0 slots) slave replicates 7a047cfaae70c30d0d7e1a5d9854eb7f11afe957 M: a4a5de0be9bb5704eec17cbe0223076eb38fc4a4 10.10.1.130:7004 slots:4096-8191 (4096 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
出現(xiàn)上述的輸出則代表集群搭建成功啦!!!
說明:
M: 7a047cfaae70c30d0d7e1a5d9854eb7f11afe957 為主節(jié)點id S: b240d86fdf6abc73df059baf64b930387664da15 為從節(jié)點的id
目前來看,7001、7002、7004、7007 為主節(jié)點,7003、7005、7008、7009 為從節(jié)點,并向你確認是否同意這么配置。輸入 yes 后,會開始集群創(chuàng)建。
四:redis cluster小結
redis cluster在設計的時候,就考慮到了去中心化、去中間件,也就是說,集群中的每個節(jié)點都是平等關系,都是對等的,每個節(jié)點都保存各自的數(shù)據(jù)和整個集群的狀態(tài)。每個節(jié)點都和其他所有節(jié)點連接,而且這些連接保持活躍,這樣就保證了我們只需要連接集群中的任意一個節(jié)點,就可以獲取到其他節(jié)點的數(shù)據(jù)。
Redis 集群沒有并使用傳統(tǒng)的一致性哈希來分配數(shù)據(jù),而是采用另外一種叫做哈希槽 (hash slot)的方式來分配的。redis cluster 默認分配了 16384 個 slot,當我們 set 一個 key 時,會用CRC16算法來取模得到所屬的 slot,然后將這個 key 分到哈希槽區(qū)間的節(jié)點上,具體算法就是:CRC16(key) % 16384。所以我們在測試的時候看到 set 和 get 的時候,直接跳轉到了7000端口的節(jié)點。
Redis 集群會把數(shù)據(jù)存在一個 master 節(jié)點,然后在這個 master 和其對應的 salve 之間進行數(shù)據(jù)同步。當讀取數(shù)據(jù)時,也根據(jù)一致性哈希算法到對應的 master 節(jié)點獲取數(shù)據(jù)。只有當一個 master 掛掉之后,才會啟動一個對應的 salve 節(jié)點,充當 master 。
需要注意的是:必須要3個或以上的主節(jié)點,否則在創(chuàng)建集群時會失敗,并且當存活的主節(jié)點數(shù)小于總節(jié)點數(shù)的一半時,整個集群就無法提供服務了。
Codis
Codis 是一個分布式?Redis?解決方案, 對于上層的應用來說, 連接到 Codis Proxy 和連接原生的 Redis Server 沒有明顯的區(qū)別 (有一些命令不支持), 上層應用可以像使用單機的 Redis 一樣使用, Codis 底層會處理請求的轉發(fā), 不停機的數(shù)據(jù)遷移等工作, 所有后邊的一切事情, 對于前面的客戶端來說是透明的, 可以簡單的認為后邊連接的是一個內(nèi)存無限大的 Redis 服務,當然,前段時間redis官方的3.0出了穩(wěn)定版,3.0支持集群功能,codis的實現(xiàn)原理和3.0的集群功能差不多,我了解的現(xiàn)在美團、阿里已經(jīng)用了3.0的集群功能了,
1.什么是Codis?
Codis 是一個分布式 Redis 解決方案, 對于上層的應用來說, 連接到 Codis Proxy 和連接原生的 Redis Server 沒有明顯的區(qū)別
(不支持的命令列表), 上層應用可以像使用單機的 Redis 一樣使用, Codis 底層會處理請求的轉發(fā), 不停機的數(shù)據(jù)遷移等工作,
所有后邊的一切事情, 對于前面的客戶端來說是透明的, 可以簡單的認為后邊連接的是一個內(nèi)存無限大的 Redis 服務。
2.codis介紹
Codis是一個分布式Redis解決方案,對于上層的應用來說,連接到Codis Proxy和連接原生的RedisServer沒有明顯的區(qū)別,有部分命令不支持。
Codis底層會處理請求的轉發(fā),不停機的數(shù)據(jù)遷移等工作,所有后邊的一切事情,
對于前面的客戶端來說是透明的,可以簡單的認為后邊連接的是一個內(nèi)存無限大的Redis服務.
Codis由四部分組成
| Codis-config | 是codis的管理工具,包括添加/刪除redis節(jié)點添加/刪除proxy節(jié)點,發(fā)起數(shù)據(jù)遷移等操作,自帶httpserver,支持管理后臺方式管理配置 |
| Codis-server | 是codis維護的redis分支,基于2.8.21分支,加入了slot的支持和原子的數(shù)據(jù)遷移指令; codis-proxy和codis-config只能和這個版本的redis交互才能正常運行 |
| Zookeeper | 用于codis集群元數(shù)據(jù)的存儲,維護codis集群節(jié)點 |
3.Codis的架構
4.Codis的優(yōu)缺點
(1)優(yōu)點
對客戶端透明,與codis交互方式和redis本身交互一樣
支持在線數(shù)據(jù)遷移,遷移過程對客戶端透明有簡單的管理和監(jiān)控界面
支持高可用,無論是redis數(shù)據(jù)存儲還是代理節(jié)點
自動進行數(shù)據(jù)的均衡分配
最大支持1024個redis實例,存儲容量海量
高性能
(2)缺點
采用自有的redis分支,不能與原版的redis保持同步
如果codis的proxy只有一個的情況下, redis的性能會下降20%左右
某些命令不支持,比如事務命令muti
國內(nèi)開源產(chǎn)品,活躍度相對弱一些
常見問題
1 如何解決緩存穿透與緩存雪崩
如何解決緩存穿透與緩存雪崩。這是基本問題也是面試常問問題。
作為一個內(nèi)存數(shù)據(jù)庫,redis也總是免不了有各種各樣的問題,這篇文章主要是針對其中兩個問題進行講解:緩存穿透和緩存雪崩。
一、緩存穿透
1、概念
緩存穿透的概念很簡單,用戶想要查詢一個數(shù)據(jù),發(fā)現(xiàn)redis內(nèi)存數(shù)據(jù)庫沒有,也就是緩存沒有命中,于是向持久層數(shù)據(jù)庫查詢。發(fā)現(xiàn)也沒有,于是本次查詢失敗。當用戶很多的時候,緩存都沒有命中,于是都去請求了持久層數(shù)據(jù)庫。這會給持久層數(shù)據(jù)庫造成很大的壓力,這時候就相當于出現(xiàn)了緩存穿透。
這里需要注意和緩存擊穿的區(qū)別,緩存擊穿,是指一個key非常熱點,在不停的扛著大并發(fā),大并發(fā)集中對這一個點進行訪問,當這個key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,就像在一個屏障上鑿開了一個洞。
為了避免緩存穿透其實有很多種解決方案。下面介紹幾種。
2、解決方案
(1)布隆過濾器
布隆過濾器是一種數(shù)據(jù)結構,垃圾網(wǎng)站和正常網(wǎng)站加起來全世界據(jù)統(tǒng)計也有幾十億個。網(wǎng)警要過濾這些垃圾網(wǎng)站,總不能到數(shù)據(jù)庫里面一個一個去比較吧,這就可以使用布隆過濾器。假設我們存儲一億個垃圾網(wǎng)站地址。
可以先有一億個二進制比特,然后網(wǎng)警用八個不同的隨機數(shù)產(chǎn)生器(F1,F2, …,F8) 產(chǎn)生八個信息指紋(f1, f2, …, f8)。接下來用一個隨機數(shù)產(chǎn)生器 G 把這八個信息指紋映射到 1 到1億中的八個自然數(shù) g1, g2, …,g8。最后把這八個位置的二進制全部設置為一。過程如下:
有一天網(wǎng)警查到了一個可疑的網(wǎng)站,想判斷一下是否是XX網(wǎng)站,首先將可疑網(wǎng)站通過哈希映射到1億個比特數(shù)組上的8個點。如果8個點的其中有一個點不為1,則可以判斷該元素一定不存在集合中。
那這個布隆過濾器是如何解決redis中的緩存穿透呢?很簡單首先也是對所有可能查詢的參數(shù)以hash形式存儲,當用戶想要查詢的時候,使用布隆過濾器發(fā)現(xiàn)不在集合中,就直接丟棄,不再對持久層查詢。
這個形式很簡單。
2、緩存空對象
當存儲層不命中后,即使返回的空對象也將其緩存起來,同時會設置一個過期時間,之后再訪問這個數(shù)據(jù)將會從緩存中獲取,保護了后端數(shù)據(jù)源;
但是這種方法會存在兩個問題:
如果空值能夠被緩存起來,這就意味著緩存需要更多的空間存儲更多的鍵,因為這當中可能會有很多的空值的鍵;即使對空值設置了過期時間,還是會存在緩存層和存儲層的數(shù)據(jù)會有一段時間窗口的不一致,這對于需要保持一致性的業(yè)務會有影響。
如何避免?
1:對查詢結果為空的情況也進行緩存,緩存時間設置短一點,或者該key對應的數(shù)據(jù)insert了之后清理緩存。
2:對一定不存在的key進行過濾。可以把所有的可能存在的key放到一個大的Bitmap中,查詢時通過該bitmap過濾。
二、緩存雪崩
1、概念
緩存雪崩是指,緩存層出現(xiàn)了錯誤,不能正常工作了。于是所有的請求都會達到存儲層,存儲層的調(diào)用量會暴增,造成存儲層也會掛掉的情況。
2、解決方案
(1)redis高可用
這個思想的含義是,既然redis有可能掛掉,那我多增設幾臺redis,這樣一臺掛掉之后其他的還可以繼續(xù)工作,其實就是搭建的集群。
(2)限流降級
這個解決方案的思想是,在緩存失效后,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待。
(3)數(shù)據(jù)預熱
數(shù)據(jù)加熱的含義就是在正式部署之前,我先把可能的數(shù)據(jù)先預先訪問一遍,這樣部分可能大量訪問的數(shù)據(jù)就會加載到緩存中。在即將發(fā)生大并發(fā)訪問前手動觸發(fā)加載緩存不同的key,設置不同的過期時間,讓緩存失效的時間點盡量均勻。
當緩存服務器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,會給后端系統(tǒng)帶來很大壓力。導致系統(tǒng)崩潰。
如何避免?
1:在緩存失效后,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待。
2:做二級緩存,A1為原始緩存,A2為拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置為短期,A2設置為長期
3:不同的key,設置不同的過期時間,讓緩存失效的時間點盡量均勻。
2 什么是Redis持久化?Redis有哪幾種持久化方式?優(yōu)缺點是什么?
持久化就是把內(nèi)存的數(shù)據(jù)寫到磁盤中去,防止服務宕機了內(nèi)存數(shù)據(jù)丟失。
Redis 提供了兩種持久化方式:RDB(默認) 和AOF
RDB:
rdb是Redis DataBase縮寫
功能核心函數(shù)rdbSave(生成RDB文件)和rdbLoad(從文件加載內(nèi)存)兩個函數(shù)
AOF:
Aof是Append-only file縮寫
每當執(zhí)行服務器(定時)任務或者函數(shù)時flushAppendOnlyFile 函數(shù)都會被調(diào)用, 這個函數(shù)執(zhí)行以下兩個工作
aof寫入保存:
WRITE:根據(jù)條件,將 aof_buf 中的緩存寫入到 AOF 文件
SAVE:根據(jù)條件,調(diào)用 fsync 或 fdatasync 函數(shù),將 AOF 文件保存到磁盤中。
存儲結構:
內(nèi)容是redis通訊協(xié)議(RESP )格式的命令文本存儲。
比較:
1、aof文件比rdb更新頻率高,優(yōu)先使用aof還原數(shù)據(jù)。
2、aof比rdb更安全也更大
3、rdb性能比aof好
4、如果兩個都配了優(yōu)先加載AOF
3 什么是一致性哈希算法
Redis 集群會把數(shù)據(jù)存在一個 master 節(jié)點,然后在這個 master 和其對應的 salve 之間進行數(shù)據(jù)同步。當讀取數(shù)據(jù)時,也根據(jù)一致性哈希算法到對應的 master 節(jié)點獲取數(shù)據(jù)。只有當一個 master 掛掉之后,才會啟動一個對應的 salve 節(jié)點,充當 master 。什么是一致性哈希算法?
一致哈希 是一種特殊的哈希算法。在使用一致哈希算法后,哈希表槽位數(shù)(大小)的改變平均只需要對 K/n 個關鍵字重新映射,其中K是關鍵字的數(shù)量, n是槽位數(shù)量。然而在傳統(tǒng)的哈希表中,添加或刪除一個槽位的幾乎需要對所有關鍵字進行重新映射。
簡單的說,一致性哈希是將整個哈希值空間組織成一個虛擬的圓環(huán),如假設哈希函數(shù)H的值空間為0-2^32-1(哈希值是32位無符號整形),整個哈希空間環(huán)如下:
整個空間按順時針方向組織,0和2^32-1在零點中方向重合。
接下來,把服務器按照IP或主機名作為關鍵字進行哈希,這樣就能確定其在哈希環(huán)的位置。
然后,我們就可以使用哈希函數(shù)H計算值為key的數(shù)據(jù)在哈希環(huán)的具體位置h,根據(jù)h確定在環(huán)中的具體位置,從此位置沿順時針滾動,遇到的第一臺服務器就是其應該定位到的服務器。
例如我們有A、B、C、D四個數(shù)據(jù)對象,經(jīng)過哈希計算后,在環(huán)空間上的位置如下:
根據(jù)一致性哈希算法,數(shù)據(jù)A會被定為到Server 1上,數(shù)據(jù)B被定為到Server 2上,而C、D被定為到Server 3上。
3.1 容錯性和擴展性
那么使用一致性哈希算法的容錯性和擴展性如何呢?
3.1.1 容錯性
假如RedisService2宕機了,那么會怎樣呢?
那么,數(shù)據(jù)B對應的節(jié)點保存到RedisService3中。因此,其中一臺宕機后,干擾的只有前面的數(shù)據(jù)(原數(shù)據(jù)被保存到順時針的下一個服務器),而不會干擾到其他的數(shù)據(jù)。
3.1.2 擴展性
下面考慮另一種情況,假如增加一臺服務器Redis4,具體位置如下圖所示:
原本數(shù)據(jù)C是保存到Redis3中,但由于增加了Redis4,數(shù)據(jù)C被保存到Redis4中。干擾的也只有Redis3而已,其他數(shù)據(jù)不會受到影響。
因此,一致性哈希算法對于節(jié)點的增減都只需重定位換空間的一小部分即可,具有較好的容錯性和可擴展性
3.2 虛擬節(jié)點
前面部分都是講述到Redis節(jié)點較多和節(jié)點分布較為均衡的情況,如果節(jié)點較少就會出現(xiàn)節(jié)點分布不均衡造成數(shù)據(jù)傾斜問題。
例如,我們的的系統(tǒng)有兩臺Redis,分布的環(huán)位置如下圖所示:
這會產(chǎn)生一種情況,Redis4的hash范圍比Redis3的hash范圍大,導致數(shù)據(jù)大部分都存儲在Redis4中,數(shù)據(jù)存儲不平衡。
為了解決這種數(shù)據(jù)存儲不平衡的問題,一致性哈希算法引入了虛擬節(jié)點機制,即對每個節(jié)點計算多個哈希值,每個計算結果位置都放置在對應節(jié)點中,這些節(jié)點稱為虛擬節(jié)點。
具體做法可以在服務器IP或主機名的后面增加編號來實現(xiàn),例如上面的情況,可以為每個服務節(jié)點增加三個虛擬節(jié)點,于是可以分為 RedisService1#1、 RedisService1#2、 RedisService1#3、 RedisService2#1、 RedisService2#2、 RedisService2#3,具體位置如下圖所示:
對于數(shù)據(jù)定位的hash算法仍然不變,只是增加了虛擬節(jié)點到實際節(jié)點的映射。例如,數(shù)據(jù)C保存到虛擬節(jié)點Redis1#2,實際上數(shù)據(jù)保存到Redis1中。這樣,就能解決服務節(jié)點少時數(shù)據(jù)不平均的問題。在實際應用中,通常將虛擬節(jié)點數(shù)設置為32甚至更大,因此即使很少的服務節(jié)點也能做到相對均勻的數(shù)據(jù)分布。
總結
以上是生活随笔為你收集整理的Redis的架构模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis--COW(Copy On W
- 下一篇: Redis cluster原理