<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          分布式緩存之Redis

          共 25210字,需瀏覽 51分鐘

           ·

          2021-03-03 10:59

          點擊上方藍色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

            作者 |  MonsterZL

          來源 |  urlify.cn/nmuIZr

          76套java從入門到精通實戰(zhàn)課程分享

          緩存大致可以分為兩類,一種是應(yīng)用內(nèi)緩存,比如Map(簡單的數(shù)據(jù)結(jié)構(gòu)),以及EH Cache(Java第三方庫),另一種 就是緩存組件,比如Memached,Redis;Redis(remote dictionary server)是一個基于KEY-VALUE的高性能的 存儲系統(tǒng),通過提供多種鍵值數(shù)據(jù)類型來適應(yīng)不同場景下的緩存與存儲需求 

          存儲結(jié)構(gòu)

          大家一定對字典類型的數(shù)據(jù)結(jié)構(gòu)非常熟悉,比如map ,通過key value的方式存儲的結(jié)構(gòu)。redis的全稱是remote dictionary server(遠程字典服務(wù)器),它以字典結(jié)構(gòu)存儲數(shù)據(jù),并允許其他應(yīng)用通過TCP協(xié)議讀寫字典中的內(nèi)容。數(shù) 據(jù)結(jié)構(gòu)如下

           

          啟動停止redis

           Redis有哪些可執(zhí)行文件

          Redis-server                                        Redis服務(wù)器

          Redis-cli                                               Redis命令行客戶端

          Redis-benchmark          Redis性能測試工具

          Redis-check-aof                                    Aof文件修復(fù)工具

          Redis-check-dump                                Rdb文件檢查工具

          Redis-sentinel                                        Sentinel服務(wù)器(2.8以后)

          常用的命令是redis-server和redis-cli

          \1.直接啟動

          redis-server  ../redis.conf

          服務(wù)器啟動后默認使用的是6379的端口,通過--port可以自定義端口;

          redis-server --port 6380

          以守護進程的方式啟動,需要修改redis.conf配置文件中deemonize yes

          \2.停止redis

          redis-cli SHUTDOWN

          考慮到redis有可能正在將內(nèi)存的數(shù)據(jù)同步到硬盤中,強行終止redis進程可能會導(dǎo)致數(shù)據(jù)丟失,正確停止redis的方式應(yīng)該是向Redis發(fā)送SHUTDOWN命令

          當(dāng)redis收到SHUTDOWN命令后,先斷開所有客戶端連接,然后根據(jù)配置進行持久化,最終完成退出

          數(shù)據(jù)類型

          字符串類型(String)

          字符串類型是redis中最基本的數(shù)據(jù)類型,它能存儲任何形式的字符串,包括二進制數(shù)據(jù)。你可以用它存儲用戶的 郵箱、json化的對象甚至是圖片。一個字符類型鍵允許存儲的最大容量是512M

          列表類型(list)

          列表類型(list)可以存儲一個有序的字符串列表,常用的操作是向列表兩端添加元素或者獲得列表的某一個片段。

          列表類型內(nèi)部使用雙向鏈表實現(xiàn),所以向列表兩端添加元素的時間復(fù)雜度為O(1), 獲取越接近兩端的元素速度就越 快。這意味著即使是一個有幾千萬個元素的列表,獲取頭部或尾部的10條記錄也是很快的

           

           hasn類型

           

           集合類型

          集合類型中,每個元素都是不同的,也就是不能有重復(fù)數(shù)據(jù),同時集合類型中的數(shù)據(jù)是無序的。一個集合類型鍵可 以存儲至多232-1個 。集合類型和列表類型的最大的區(qū)別是有序性和唯一性 集合類型的常用操作是向集合中加入或刪除元素、判斷某個元素是否存在。由于集合類型在redis內(nèi)部是使用的值 為空的散列表(hash table),所以這些操作的時間復(fù)雜度都是O(1).

           

           有序集合

           

           

          有序集合類型,顧名思義,和前面講的集合類型的區(qū)別就是多了有序的功能
          在集合類型的基礎(chǔ)上,有序集合類型為集合中的每個元素都關(guān)聯(lián)了一個分數(shù),這使得我們不僅可以完成插入、刪除 和判斷元素是否存在等集合類型支持的操作,還能獲得分數(shù)最高(或最低)的前N個元素、獲得指定分數(shù)范圍內(nèi)的元 素等與分數(shù)有關(guān)的操作。雖然集合中每個元素都是不同的,但是他們的分數(shù)卻可以相同

          過期時間設(shè)置

          在Redis中提供了Expire命令設(shè)置一個鍵的過期時間,到期以后Redis會自動刪除它,這個在實際使用過程中用的非常多。

          EXPIRE命令的使用方法為

          EXPIRE key seconds

          其中seconds參數(shù)表示鍵的過期時間,單位為秒。

          EXPIRE返回值為1表示設(shè)置成功,0標(biāo)識設(shè)置失敗或鍵不存在

          如果想知道一個鍵還有多久時間被刪除,可以使用TTL命令

          TTL key

          當(dāng)鍵不存在時,TTL命令會返回-2
          而對于沒有給指定鍵設(shè)置過期時間的,通過TTL命令會返回-1

          如果想取消鍵的過期時間設(shè)置(使該鍵恢復(fù)成為永久的),可以使用PERSIST命令,如果該命令執(zhí)行成功或者成功 清除了過期時間,則返回1 。否則返回0(鍵不存在或者本身就是永久的) EXPIRE命令的seconds命令必須是整數(shù),所以小單位是1秒,如果向要更精確的控制鍵的過期時間可以使用 PEXPIRE命令,當(dāng)然實際過程中用秒的單位就夠了。PEXPIRE命令的單位是毫秒。即PEXPIRE key 1000與EXPIRE key 1相等;對應(yīng)的PTTL以毫秒單位獲取鍵的剩余有效時間
          還有一個針對字符串獨有的過期時間設(shè)置方式:
          setex(String key,int seconds,String value)

          過期刪除的原理

          Redis中的主鍵失效是如何實現(xiàn)的,即失效的主鍵是如何刪除的?實際上,Redis刪除失效主鍵的方法主要有兩種:

          消極方法(passive way)

          在主鍵被訪問時如果發(fā)現(xiàn)它已經(jīng)失效,那么就刪除它

          積極方法(active way)

          周期性地從設(shè)置了失效時間的主鍵中選擇一部分失效的主鍵刪除

          對于那些從未被查詢的key,即便他們已經(jīng)過期,被動方式也無法清楚。

          因此Redis會周期性地隨機測試一些key, 已過期的key將會被刪掉。Redis每秒會進行10次操作,具體的流程:

          \1.隨機測試20個帶有timeout信息的key:

          \2.刪除其中已經(jīng)過期的key;

          \3.如果超過25%的key被刪除,則重復(fù)執(zhí)行步驟1;

          這是一個簡單的概率算法(trivial probabilistic algorithm),基于假設(shè)我們隨機抽取的key空閑。

          Redis發(fā)布訂閱

          Redis提供了發(fā)布訂閱功能,可以用于消息的傳輸,Redis提供了一組命令可以讓開發(fā)者實現(xiàn)“發(fā)布/訂閱”模式 (publish/subscribe) . 該模式同樣可以實現(xiàn)進程間的消息傳遞,它的實現(xiàn)原理是:

          發(fā)布/訂閱模式包含兩種角色,分別是發(fā)布者和訂閱者。訂閱者可以訂閱一個或多個頻道,而發(fā)布者可以向指定的 頻道發(fā)送消息,所有訂閱此頻道的訂閱者都會收到該消息

          發(fā)布者發(fā)布消息的命令是PUBLISH,用法是

          PUBLISH channel message

          比如向channel.1發(fā)一條消息:hello

          PUBLISH channel.1 "hello"

          這樣就實現(xiàn)了消息的發(fā)送,該命令的返回值表示接受到這條消息的訂閱者數(shù)量。因為在執(zhí)行這條命令的時候還沒有訂閱者訂閱該頻道,所以返回為0,另外值得注意的是消息發(fā)送出去不會持久化,如果發(fā)送之前沒有訂閱者,那么后續(xù)再有訂閱者訂閱該頻道,之前的消息就收不到了

          訂閱者訂閱消息的命令是

          SUBSCRIBE channel [channel ...]

          該命令同時可以訂閱多個頻道,比如訂閱channel.1的頻道。SUBSCRIBE channel.1

          執(zhí)行SUBSCRIBE命令后客戶端就會進入訂閱狀態(tài)。

          結(jié)構(gòu)圖

          channel分兩類,一個是普通channel,另一個是pattern channel(規(guī)則匹配),producer1發(fā)布了一條消息 

          publish abc hello

          redis server 發(fā)給abc這個普通channel上的所有訂閱者,同時abc也匹配上了pattern channel的名字,所以這條消息也同時發(fā)送給pattern channel *bc上的所有訂閱者

           

           

           Redis的數(shù)據(jù)是如何持久化的?

          Redis支持兩種方式的持久化,一種是RDB方式,另一種是AOF(append-only-file)方式。前者會根據(jù)指定的規(guī)則“定時”將內(nèi)存中的數(shù)據(jù)存儲在硬盤上,而后者在每次執(zhí)行命令后將命令本身記錄下來。兩種持久化方式可以單獨使用其中一種,也可以將兩種方式結(jié)合使用

          RDB方式

          當(dāng)符合一定條件時,Redis會單獨創(chuàng)建(fork)一個子進程來進行持久化,會先將數(shù)據(jù)寫入到一個臨時文件中,等到持久化過程都結(jié)束了,再用這個臨時文件替換上次持久化好的文件。整個過程中,主進程是不進行任何IO操作的,這就確保了極高的性能。如果需要進行大規(guī)模數(shù)據(jù)的恢復(fù),且對于數(shù)據(jù)恢復(fù)的完整性不是非常的敏感,那RDB方式要比AOF方式更加的高效。RDB的缺點是后一次持久化后的數(shù)據(jù)可能丟失

          Redis會在以下幾種情況下對數(shù)據(jù)進行快照
          \1. 根據(jù)配置規(guī)則進行自動快照

          \2. 用戶執(zhí)行SAVE或者GBSAVE命令

          \3. 執(zhí)行FLUSHALL命令

          \4. 執(zhí)行復(fù)制(replication)時

          根據(jù)配置規(guī)則進行自動快照

          Redis允許用戶自定義快照條件,當(dāng)符合快照條件時,Redis會自動執(zhí)行快照操作。快照的條件可以由用戶在配置文 件中配置。配置格式如下
          save
          第一個參數(shù)是時間窗口,第二個是鍵的個數(shù),也就是說,在第一個時間參數(shù)配置范圍內(nèi)被更改的鍵的個數(shù)大于后面 的changes時,即符合快照條件。redis默認配置了三個規(guī)則

          save 900 1

          save 300 10

          save 60 10000

          每條快照規(guī)則占一行,每條規(guī)則之間是“或”的關(guān)系。在900秒(15分)內(nèi)有一個以上的鍵被更改則進行快照。

          用戶執(zhí)行SAVE或BGSAVE命令

          除了讓Redis自動進行快照以外,當(dāng)我們對服務(wù)進行重啟或者服務(wù)器遷移我們需要人工去干預(yù)備份。redis提供了兩 條命令來完成這個任務(wù)

          \1. save命令

          當(dāng)執(zhí)行save命令時,Redis同步做快照操作,在快照執(zhí)行過程中會阻塞所有來自客戶端的請求。當(dāng)redis內(nèi)存中的數(shù) 據(jù)較多時,通過該命令將導(dǎo)致Redis較長時間的不響應(yīng)。所以不建議在生產(chǎn)環(huán)境上使用這個命令,而是推薦使用 bgsave命令

          \2. bgsave命令

          bgsave命令可以在后臺異步地進行快照操作,快照的同時服務(wù)器還可以繼續(xù)響應(yīng)來自客戶端的請求。執(zhí)行BGSAVE 后,Redis會立即返回ok表示開始執(zhí)行快照操作。

          通過LASTSAVE命令可以獲取近一次成功執(zhí)行快照的時間;(自動快照采用的是異步快照操作) 

          執(zhí)行FLUSHALL命令

          該命令在前面講過,會清除redis在內(nèi)存中的所有數(shù)據(jù)。執(zhí)行該命令后,只要redis中配置的快照規(guī)則不為空,也就 是save 的規(guī)則存在。redis就會執(zhí)行一次快照操作。不管規(guī)則是什么樣的都會執(zhí)行。如果沒有定義快照規(guī)則,就不 會執(zhí)行快照操作 

          執(zhí)行復(fù)制時

          該操作主要是在主從模式下,redis會在復(fù)制初始化時進行自動快照。

          這里只需要了解當(dāng)執(zhí)行復(fù)制操作時,即使沒有定義自動快照規(guī)則,并且沒有手動執(zhí)行過快照操作,它仍然會生成 RDB快照文件 

          AOF方式

          當(dāng)使用Redis存儲非臨時數(shù)據(jù)時,一般需要打開AOF持久化來降低進程終止導(dǎo)致的數(shù)據(jù)丟失。AOF可以將Redis執(zhí)行 的每一條寫命令追加到硬盤文件中,這一過程會降低Redis的性能,但大部分情況下這個影響是能夠接受的,另外 使用較快的硬盤可以提高AOF的性能 

          開啟AOF

          默認情況下Redis沒有開啟AOF(append only file)方式的持久化,可以通過appendonly參數(shù)啟用,在redis.conf中找到appendonly yes

          開啟AOF持久化后每執(zhí)行一條會更改Redis中的數(shù)據(jù)的命令后,Redis就會將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數(shù)設(shè)置的,默認的文件名是apendonly.aof,可以在redis.conf中的屬性appendfilename appendonlyh.aof修改

          AOF的實現(xiàn)

          set foo 1

          set foo 2
          set foo 3

          get

          redis 會將前3條命令寫入AOF文件中,通過vim的方式可以看到aof文件中的內(nèi)容

          我們會發(fā)現(xiàn)AOF文件的內(nèi)容正是Redis發(fā)送的原始通信協(xié)議的內(nèi)容,從內(nèi)容中我們發(fā)現(xiàn)Redis只記錄了3 條命令。然后這時有一個問題是前面2條命令其實是冗余的,因為這兩條的執(zhí)行結(jié)果都會被第三條命令覆 蓋。隨著執(zhí)行的命令越來越多,AOF文件的大小也會越來越大,其實內(nèi)存中實際的數(shù)據(jù)可能沒有多少, 那這樣就會造成磁盤空間以及redis數(shù)據(jù)還原的過程比較長的問題。因此我們希望Redis可以自動優(yōu)化 AOF文件,就上面這個例子來說,前面兩條是可以被刪除的。 而實際上Redis也考慮到了,可以配置一 個條件,每當(dāng)達到一定條件時Redis就會自動重寫AOF文件,這個條件的配置問 auto-aof-rewritepercentage 100 auto-aof-rewrite-min-size 64mb

          auto-aof-rewrite-percentage 表示的是當(dāng)前的AOF文件大小超過上一次重寫時的AOF文件大小的百分之多少時會再次進行重寫,如果之前沒有重寫過,則以啟動時的AOF文件大小為依據(jù)

          auto-aof-rewrite-min-size 表示限制了允許重寫的小AOF文件大小,通常在AOF文件很小的情況下即使其中有很 多冗余的命令我們也并不太關(guān)心。
          另外,還可以通過BGREWRITEAOF 命令手動執(zhí)行AOF,執(zhí)行完以后冗余的命令已經(jīng)被刪除了
          在啟動時,Redis會逐個執(zhí)行AOF文件中的命令來將硬盤中的數(shù)據(jù)載入到內(nèi)存中,載入的速度相對于RDB會慢一些

          AOF的重寫原理

          Redis 可以在 AOF 文件體積變得過大時,自動地在后臺對 AOF 進行重寫:重寫后的新 AOF 文件包含了恢復(fù)當(dāng)前 數(shù)據(jù)集所需的小命令集合。
          重寫的流程是這樣,主進程會fork一個子進程出來進行AOF重寫,這個重寫過程并不是基于原有的aof文件來做 的,而是有點類似于快照的方式,全量遍歷內(nèi)存中的數(shù)據(jù),然后逐個序列到aof文件中。在fork子進程這個過程 中,服務(wù)端仍然可以對外提供服務(wù),那這個時候重寫的aof文件的數(shù)據(jù)和redis內(nèi)存數(shù)據(jù)不一致了怎么辦?不用擔(dān) 心,這個過程中,主進程的數(shù)據(jù)更新操作,會緩存到aof_rewrite_buf中,也就是單獨開辟一塊緩存來存儲重寫期間 收到的命令,當(dāng)子進程重寫完以后再把緩存中的數(shù)據(jù)追加到新的aof文件。當(dāng)所有的數(shù)據(jù)全部追加到新的aof文件中后,把新的aof文件重命名為,此后所有的操作都會被寫入新的aof文件。
          如果在rewrite過程中出現(xiàn)故障,不會影響原來aof文件的正常工作,只有當(dāng)rewrite完成后才會切換文件。因此這個 rewrite過程是比較可靠的

          Redis內(nèi)存回收策略

          Redis中提供了多種內(nèi)存回收策略,當(dāng)內(nèi)存容量不足時,為了保證程序的運行,這時就不得不淘汰內(nèi)存中的一些對 象,釋放這些對象占用的空間,那么選擇淘汰哪些對象呢?

          其中,默認的策略為noeviction策略,當(dāng)內(nèi)存使用達到閾值的時候,所有引起申請內(nèi)存的命令會報錯

          allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選近少使用的數(shù)據(jù)淘汰

          適合的場景:如果我們的應(yīng)用對緩存的訪問都是相對熱點數(shù)據(jù),那么可以選擇這個策略

          allkeys-random:隨機移除某個key。

          適合的場景:如果我們的應(yīng)用對于緩存key的訪問概率相等,則可以使用這個策略

          volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰。volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選近少使用的數(shù)據(jù)淘汰。volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰

          適合場景:這種策略使得我們可以向Redis提示哪些key更適合被淘汰,我們可以自己控制 

          實際上Redis實現(xiàn)的LRU并不是可靠的LRU,也就是名義上我們使用LRU算法淘汰內(nèi)存數(shù)據(jù),但是實際上被淘汰的鍵并不一定是真正的最少使用的數(shù)據(jù),這里涉及到一個權(quán)衡的問題,如果需要在所有的數(shù)據(jù)中搜索最符合條件的數(shù)據(jù),那么一定會增加系統(tǒng)的開銷,Redis是單線程的,所以耗時的操作會謹慎一些。為了在一定成本內(nèi)實現(xiàn)相對的 LRU,早期的Redis版本是基于采樣的LRU,也就是放棄了從所有數(shù)據(jù)中搜索解改為采樣空間搜索優(yōu)解。Redis3.0 版本之后,Redis作者對于基于采樣的LRU進行了一些優(yōu)化,目的是在一定的成本內(nèi)讓結(jié)果更靠近真實的LRU。 

          Redis是單進程單線程?性能為什么這么快

          Redis采用了一種非常簡單的做法,單線程來處理來自所有客戶端的開發(fā)請求,Redis把任務(wù)封閉在一個線程中從而避免了線程安全問題;redis為什么是單線程?

          官方的解釋是,CPU并不是Redis的瓶頸所在,Redis的瓶頸主要在機器的內(nèi)存和網(wǎng)絡(luò)的帶寬。那么Redis能不能處 理高并發(fā)請求呢?當(dāng)然是可以的,至于怎么實現(xiàn)的,我們來具體了解一下?!咀⒁獠l(fā)不等于并行,并發(fā)性I/O 流,意味著能夠讓一個計算單元來處理來自多個客戶端的流請求。并行性,意味著服務(wù)器能夠同時執(zhí)行幾個事情, 具有多個計算單元】 

          多路復(fù)用

          Redis是跑在單線程中的,所有的操作都是按照順序線性執(zhí)行的,但是由于讀寫操作等待用戶輸入或輸出都是阻塞的,所以I/O操作在一般情況下往往不能直接返回,這會導(dǎo)致某一文件的I/O阻塞導(dǎo)致整個進程無法對其他客戶提供服務(wù),而I/O多路復(fù)用就是為了解決這個問題而出現(xiàn)的。

          了解多路復(fù)用之前,先簡單了解下幾種I/O模型

          (1)同步阻塞IO(Blocking IO):即傳統(tǒng)的IO模型。

          (2)同步非阻塞IO(Non-blocking IO):默認創(chuàng)建的socket都是阻塞的,非阻塞IO要求socket被設(shè)置為 NONBLOCK。
          (3)IO多路復(fù)用(IO Multiplexing):即經(jīng)典的Reactor設(shè)計模式,也稱為異步阻塞IO,Java中的Selector和 Linux中的epoll都是這種模型。

          (4)異步IO(Asynchronous IO):即經(jīng)典的Proactor設(shè)計模式,也稱為異步非阻塞IO。

          同步和異步、阻塞和非阻塞,到底是什么意思,感覺原理都差不多,我來簡單解釋一下
          同步和異步,指的是用戶線程和內(nèi)核的交互方式
          阻塞和非阻塞,指用戶線程調(diào)用內(nèi)核IO操作的方式是阻塞還是非阻塞
          就像在Java中使用多線程做異步處理的概念,通過多線程去執(zhí)行一個流程,主線程可以不用等待。而阻塞和非阻塞 我們可以理解為假如在同步流程或者異步流程中做IO操作,如果緩沖區(qū)數(shù)據(jù)還沒準(zhǔn)備好,IO的這個過程會阻塞,這 個在之前講TCP協(xié)議的時候有講過.

          在Redis中使用Lua腳本

          我們在使用redis的時候,會面臨一些問題,比如

          原子性問題

          前面我們講過,redis雖然是單一線程的,當(dāng)時仍然會存在線程安全問題,當(dāng)然,這個線程安全問題不是來源安于 Redis服務(wù)器內(nèi)部。而是Redis作為數(shù)據(jù)服務(wù)器,是提供給多個客戶端使用的。多個客戶端的操作就相當(dāng)于同一個進 程下的多個線程,如果多個客戶端之間沒有做好數(shù)據(jù)的同步策略,就會產(chǎn)生數(shù)據(jù)不一致的問題。舉個簡單的例子:

          多個客戶端的命令之間沒有做請求同步,導(dǎo)致實際執(zhí)行順序可能會不一致,終的結(jié)果也就無法滿足原子性了。 

          效率問題

          redis本身的吞吐量是非常高的,因為它首先是基于內(nèi)存的數(shù)據(jù)庫。在實際使用過程中,有一個非常重要的因素影 響redis的吞吐量,那就是網(wǎng)絡(luò)。我們在使用redis實現(xiàn)某些特定功能的時候,很可能需要多個命令或者多個數(shù)據(jù)類 型的交互才能完成,那么這種多次網(wǎng)絡(luò)請求對性能影響比較大。當(dāng)然redis也做了一些優(yōu)化,比如提供了pipeline管 道操作,但是它有一定的局限性,就是執(zhí)行的多個命令和響應(yīng)之間是不存在相互依賴關(guān)系的。所以我們需要一種機 制能夠編寫一些具有業(yè)務(wù)邏輯的命令,減少網(wǎng)絡(luò)請求 

          Lua

          Redis中內(nèi)嵌了對Lua環(huán)境的支持,允許開發(fā)者使用Lua語言編寫腳本傳到Redis中執(zhí)行,Redis客戶端可以使用Lua 腳本,直接在服務(wù)端原子的執(zhí)行多個Redis命令。
          使用腳本的好處:
          \1. 減少網(wǎng)絡(luò)開銷,在Lua腳本中可以把多個命令放在同一個腳本中運行

          \2. 原子操作,redis會將整個腳本作為一個整體執(zhí)行,中間不會被其他命令插入。換句話說,編寫腳本的過程中無 需擔(dān)心會出現(xiàn)競態(tài)條件

          \3. 復(fù)用性,客戶端發(fā)送的腳本會永遠存儲在redis中,這意味著其他客戶端可以復(fù)用這一腳本來完成同樣的邏輯 Lua是一個高效的輕量級腳本語言(javascript、shell、sql、python、ruby…),用標(biāo)準(zhǔn)C語言編寫并以源代碼形式開 放, 其設(shè)計目的是為了嵌入應(yīng)用程序中,從而為應(yīng)用程序提供靈活的擴展和定制功能;

          Redis與Lua 

          先初步的認識一下在redis中如何結(jié)合lua來完成一些簡單的操作 

          在Lua腳本中調(diào)用Redis命令

          在Lua腳本中調(diào)用Redis命令,可以使用redis.call函數(shù)調(diào)用。比如我們調(diào)用string類型的命令 redis.call(‘set’,’hello’,’world’)
          local value=redis.call(‘get’,’hello’)

          redis.call 函數(shù)的返回值就是redis命令的執(zhí)行結(jié)果。前面我們介紹過redis的5中類型的數(shù)據(jù)返回的值的類型也都不 一樣。redis.call函數(shù)會將這5種類型的返回值轉(zhuǎn)化對應(yīng)的Lua的數(shù)據(jù)類型

          從Lua腳本中獲得返回值

          在很多情況下我們都需要腳本可以有返回值,畢竟這個腳本也是一個我們所編寫的命令集,我們可以像調(diào)用其他 redis內(nèi)置命令一樣調(diào)用我們自己寫的腳本,所以同樣redis會自動將腳本返回值的Lua數(shù)據(jù)類型轉(zhuǎn)化為Redis的返回 值類型。在腳本中可以使用return 語句將值返回給redis客戶端,通過return語句來執(zhí)行,如果沒有執(zhí)行return, 默認返回為nil。 

          EVAL命令的格式

          [EVAL][腳本內(nèi)容] [key參數(shù)的數(shù)量][key …] [arg …] 可以通過key和arg這兩個參數(shù)向腳本中傳遞數(shù)據(jù),他們的值可以在腳本中分別使用KEYS和ARGV 這兩個類型的全 局變量訪問。比如我們通過腳本實現(xiàn)一個set命令,通過在redis客戶端中調(diào)用,那么執(zhí)行的語句是:
          lua腳本的內(nèi)容為:

          return redis.call(‘set’,KEYS[1],ARGV[1]) //KEYS和ARGV必須大寫

          eval "return redis.call('set',KEYS[1],ARGV[1])" 1 lua1 hello

          注意:EVAL命令是根據(jù) key參數(shù)的數(shù)量-也就是上面例子中的1來將后面所有參數(shù)分別存入腳本中KEYS和ARGV兩個 表類型的全局變量。當(dāng)腳本不需要任何參數(shù)時也不能省略這個參數(shù)。如果沒有參數(shù)則為0

           

           

          EVALSHA命令

          考慮到我們通過eval執(zhí)行l(wèi)ua腳本,腳本比較長的情況下,每次調(diào)用腳本都需要把整個腳本傳給redis,比較占用帶 寬。為了解決這個問題,redis提供了EVALSHA命令允許開發(fā)者通過腳本內(nèi)容的SHA1摘要來執(zhí)行腳本。該命令的用 法和EVAL一樣,只不過是將腳本內(nèi)容替換成腳本內(nèi)容的SHA1摘要

          \1. Redis在執(zhí)行EVAL命令時會計算腳本的SHA1摘要并記錄在腳本緩存中

          \2. 執(zhí)行EVALSHA命令時Redis會根據(jù)提供的摘要從腳本緩存中查找對應(yīng)的腳本內(nèi)容,如果找到了就執(zhí)行腳本,否則 返回“NOSCRIPT No matching script,Please use EVAL”

          通過以下案例來演示EVALSHA命令的效果

          script load "return redis.call('get','lua1')" 將腳本加入緩存并生成sha1命令 evalsha"a5a402e90df3eaeca2?03d56d99982e05cf6574" 0

          我們在調(diào)用eval命令之前,先執(zhí)行evalsha命令,如果提示腳本不存在,則再調(diào)用eval命令

          集群

          先來簡單了解下redis中提供的集群策略, 雖然redis有持久化功能能夠保障redis服務(wù)器宕機也能恢復(fù)并且只有少量 的數(shù)據(jù)損失,但是由于所有數(shù)據(jù)在一臺服務(wù)器上,如果這臺服務(wù)器出現(xiàn)硬盤故障,那就算是有備份也仍然不可避免 數(shù)據(jù)丟失的問題。
          在實際生產(chǎn)環(huán)境中,我們不可能只使用一臺redis服務(wù)器作為我們的緩存服務(wù)器,必須要多臺實現(xiàn)集群,避免出現(xiàn) 單點故障;

          主從復(fù)制

          復(fù)制的作用是把redis的數(shù)據(jù)庫復(fù)制多個副本部署在不同的服務(wù)器上,如果其中一臺服務(wù)器出現(xiàn)故障,也能快速遷 移到其他服務(wù)器上提供服務(wù)。復(fù)制功能可以實現(xiàn)當(dāng)一臺redis服務(wù)器的數(shù)據(jù)更新后,自動將新的數(shù)據(jù)同步到其他服 務(wù)器上
          主從復(fù)制就是我們常見的master/slave模式, 主數(shù)據(jù)庫可以進行讀寫操作,當(dāng)寫操作導(dǎo)致數(shù)據(jù)發(fā)生變化時會自動將 數(shù)據(jù)同步給從數(shù)據(jù)庫。而一般情況下,從數(shù)據(jù)庫是只讀的,并接收主數(shù)據(jù)庫同步過來的數(shù)據(jù)。一個主數(shù)據(jù)庫可以有 多個從數(shù)據(jù)庫

           

           配置

          在redis中配置master/slave是非常容易的,只需要在從數(shù)據(jù)庫的配置文件中加入slaveof主數(shù)據(jù)庫地址端口。而master數(shù)據(jù)庫不需要做任何改變

          準(zhǔn)備兩臺服務(wù)器,分別安裝redis , server1  server2
           
          \1.    在server2的redis.conf文件中增加 slaveof server1-ip 6379 、 同時將bindip注釋掉,允許所 有ip訪問
           
          \2.  啟動server2
           
          \3.  訪問server2的redis客戶端,輸入 INFO replication \4.  通過在master機器上輸入命令,比如set foo bar 、 在slave服務(wù)器就能看到該值已經(jīng)同步過來了

          原理

          1.全量復(fù)制

          Redis全量復(fù)制一般發(fā)生在slave初始化階段,這時slave需要將Master上的所有數(shù)據(jù)都復(fù)制一份。具體步驟

           

           

          完成上面幾個步驟后就完成了slave服務(wù)器數(shù)據(jù)初始化的所有操作,savle服務(wù)器此時可以接收來自用戶的讀請求。
          master/slave 復(fù)制策略是采用樂觀復(fù)制,也就是說可以容忍在一定時間內(nèi)master/slave數(shù)據(jù)的內(nèi)容是不同的,但是 兩者的數(shù)據(jù)會最終同步。具體來說,redis的主從同步過程本身是異步的,意味著master執(zhí)行完客戶端請求的命令 后會立即返回結(jié)果給客戶端,然后異步的方式把命令同步給slave。這一特征保證啟用master/slave后 master的性能不會受到影響。但是另一方面,如果在這個數(shù)據(jù)不一致的窗口期間,master/slave因為網(wǎng)絡(luò)問題斷開連接,而這個時候,master 是無法得知某個命令最終同步給了多少個slave數(shù)據(jù)庫。不過redis提供了一個配置項來限制只有數(shù)據(jù)至少同步給多 少個slave的時候,master才是可寫的:
          min-slaves-to-write 3 表示只有當(dāng)3個或以上的slave連接到master,master才是可寫的
          min-slaves-max-lag 10 表示允許slave最長失去連接的時間,如果10秒還沒收到slave的響應(yīng),則master認為該 slave以斷開

          2.增量復(fù)制

          從redis2.8開始,就支持主從復(fù)制的斷點續(xù)傳,如果主從復(fù)制過程中,網(wǎng)絡(luò)連接斷掉了,那么可以接著上次復(fù)制的地方,繼續(xù)復(fù)制下去,而不是從頭開始復(fù)制一份

          master node會在內(nèi)存中創(chuàng)建一個backlog,master和slave都會保存一個replica o?set還有一個master id,o?set 就是保存在backlog中的。如果master和slave網(wǎng)絡(luò)連接斷掉了,slave會讓master從上次的replica o?set開始繼續(xù) 復(fù)制

          但是如果沒有找到對應(yīng)的offset,那么就會執(zhí)行一次全量同步

          3.無硬盤復(fù)制

           前面我們說過,Redis復(fù)制的工作原理基于RDB方式的持久化實現(xiàn)的,也就是master在后臺保存RDB快照,slave接 收到rdb文件并載入,但是這種方式會存在一些問題 

          \1. 當(dāng)master禁用RDB時,如果執(zhí)行了復(fù)制初始化操作,Redis依然會生成RDB快照,當(dāng)master下次啟動時執(zhí)行該 RDB文件的恢復(fù),但是因為復(fù)制發(fā)生的時間點不確定,所以恢復(fù)的數(shù)據(jù)可能是任何時間點的。就會造成數(shù)據(jù)出現(xiàn)問 題

          \2. 當(dāng)硬盤性能比較慢的情況下(網(wǎng)絡(luò)硬盤),那初始化復(fù)制過程會對性能產(chǎn)生影響 因此2.8.18以后的版本,Redis引入了無硬盤復(fù)制選項,可以不需要通過RDB文件去同步,直接發(fā)送數(shù)據(jù),通過以 下配置來開啟該功能
          repl-diskless-sync yes master**在內(nèi)存中直接創(chuàng)建rdb,然后發(fā)送給slave,不會在自己本地落地磁盤了

          哨兵機制

          在前面講的master/slave模式,在一個典型的一主多從的系統(tǒng)中,slave在整個體系中起到了數(shù)據(jù)冗余備份和讀寫 分離的作用。當(dāng)master遇到異常終端后,需要從slave中選舉一個新的master繼續(xù)對外提供服務(wù),這種機制在前面 提到過N次,比如在zk中通過leader選舉、kafka中可以基于zk的節(jié)點實現(xiàn)master選舉。所以在redis中也需要一種 機制去實現(xiàn)master的決策,redis并沒有提供自動master選舉功能,而是需要借助一個哨兵來進行監(jiān)控 

          什么是哨兵

          顧名思義,哨兵的作用就是監(jiān)控Redis系統(tǒng)的運行狀況,他的功能包括兩個

          \1. 監(jiān)控master和slave是否正常運行 

          \2. master出現(xiàn)故障時自動將slave數(shù)據(jù)庫升級為master

          哨兵是一個獨立的進程,使用哨兵后的架構(gòu)圖

           

           

           

          為了解決master選舉問題,又引出了一個單點問題,也就是哨兵的可用性如何解決,在一個一主多從的Redis系統(tǒng) 中,可以使用多個哨兵進行監(jiān)控任務(wù)以保證系統(tǒng)足夠穩(wěn)定。此時哨兵不僅會監(jiān)控master和slave,同時還會互相監(jiān) 控;這種方式稱為哨兵集群,哨兵集群需要解決故障發(fā)現(xiàn)、和master決策的協(xié)商機制問題

           

           

          sentinel之間的相互感知

           sentinel節(jié)點之間會因為共同監(jiān)視同一個master從而產(chǎn)生了關(guān)聯(lián),一個新加入的sentinel節(jié)點需要和其他監(jiān)視相同 master節(jié)點的sentinel相互感知,首先

          \1. 需要相互感知的sentinel都向他們共同監(jiān)視的master節(jié)點訂閱channel:sentinel:hello
          \2. 新加入的sentinel節(jié)點向這個channel發(fā)布一條消息,包含自己本身的信息,這樣訂閱了這個channel的sentinel 就可以發(fā)現(xiàn)這個新的sentinel

          \3. 新加入得sentinel和其他sentinel節(jié)點建立長連接

           

           

           master的故障發(fā)現(xiàn)

          sentinel節(jié)點會定期向master節(jié)點發(fā)送心跳包來判斷存活狀態(tài),一旦master節(jié)點沒有正確響應(yīng),sentinel會把 master設(shè)置為“主觀不可用狀態(tài)”,然后它會把“主觀不可用”發(fā)送給其他所有的sentinel節(jié)點去確認,當(dāng)確認的 sentinel節(jié)點數(shù)大于>quorum時,則會認為master是“客觀不可用”,接著就開始進入選舉新的master流程;但是 這里又會遇到一個問題,就是sentinel中,本身是一個集群,如果多個節(jié)點同時發(fā)現(xiàn)master節(jié)點達到客觀不可用狀 態(tài),那誰來決策選擇哪個節(jié)點作為maste呢?這個時候就需要從sentinel集群中選擇一個leader來做決策。而這里 用到了一致性算法Raft算法、它和Paxos算法類似,都是分布式一致性算法。但是它比Paxos算法要更容易理解;Raft和Paxos算法一樣,也是基于投票算法,只要保證過半數(shù)節(jié)點通過提議即可; 

          動畫演示地址:http://thesecretlivesofdata.com/raft/ 

          配置實現(xiàn)

          通過在這個配置的基礎(chǔ)上增加哨兵機制。在其中任意一臺服務(wù)器上創(chuàng)建一個sentinel.conf文件,文件內(nèi)容 :

          sentinel monitor name ip port quorum

          其中name表示要監(jiān)控的master的名字,這個名字是自己定義。ip和port表示master的ip和端口號。最后一個表示最低 通過票數(shù),也就是說至少需要幾個哨兵節(jié)點統(tǒng)一才可以

          port 6040 sentinel monitor mymaster 192.168.11.131 6379 1
          sentinel down-after-milliseconds mymaster 5000 --表示如果5s內(nèi)mymaster沒響應(yīng),就認為SDOWN sentinel failover-timeout mymaster 15000 --表示如果15秒后,mysater仍沒活過來,則啟動failover,從剩下的 slave中選一個升級為master

          兩種方式啟動哨兵
          redis-sentinel sentinel.conf

          redis-server /path/to/sentinel.conf --sentinel

          哨兵監(jiān)控一個系統(tǒng)時,只需要配置監(jiān)控master即可,哨兵會自動發(fā)現(xiàn)所有slave;

          這時候,我們把master關(guān)閉,等待指定時間后(默認是30秒),會自動進行切換

          +sdown表示哨兵主管認為master已經(jīng)停止服務(wù)了,+odown表示哨兵客觀認為master停止服務(wù)了。接著哨兵開始進行故障恢復(fù),挑選一個slave升級為master
          +try-failover表示哨兵開始進行故障恢復(fù) +failover-end 表示哨兵完成故障恢復(fù) +slave表示列出新的master和slave服務(wù)器,我們?nèi)匀豢梢钥吹揭呀?jīng)停掉的master,哨兵并沒有清楚已停止的服務(wù) 的實例,這是因為已經(jīng)停止的服務(wù)器有可能會在某個時間進行恢復(fù),恢復(fù)以后會以slave角色加入到整個集群中

          Redis-Cluster

          即使是使用哨兵,此時的Redis集群的每個數(shù)據(jù)庫依然存有集群中的所有數(shù)據(jù),從而導(dǎo)致集群的總數(shù)據(jù)存儲量受限 于可用存儲內(nèi)存最小的節(jié)點,形成了木桶效應(yīng)。而因為Redis是基于內(nèi)存存儲的,所以這一個問題在redis中就顯得 尤為突出了
          在redis3.0之前,我們是通過在客戶端去做的分片,通過hash環(huán)的方式對key進行分片存儲。分片雖然能夠解決各 個節(jié)點的存儲壓力,但是導(dǎo)致維護成本高、增加、移除節(jié)點比較繁瑣。因此在redis3.0以后的版本最大的一個好處 就是支持集群功能,集群的特點在于擁有和單機實例一樣的性能,同時在網(wǎng)絡(luò)分區(qū)以后能夠提供一定的可訪問性以 及對主數(shù)據(jù)庫故障恢復(fù)的支持。
          哨兵和集群是兩個獨立的功能,當(dāng)不需要對數(shù)據(jù)進行分片使用哨兵就夠了,如果要進行水平擴容,集群是一個比較 好的方式

          拓撲結(jié)構(gòu)

          一個Redis Cluster由多個Redis節(jié)點構(gòu)成。不同節(jié)點組服務(wù)的數(shù)據(jù)沒有交集,也就是每個一節(jié)點組對應(yīng)數(shù)據(jù) sharding的一個分片。節(jié)點組內(nèi)部分為主備兩類節(jié)點,對應(yīng)master和slave節(jié)點。兩者數(shù)據(jù)準(zhǔn)實時一致,通過異步 化的主備復(fù)制機制來保證。一個節(jié)點組有且只有一個master節(jié)點,同時可以有0到多個slave節(jié)點,在這個節(jié)點組中 只有master節(jié)點對用戶提供些服務(wù),讀服務(wù)可以由master或者slave提供 

          redis-cluster是基于gossip協(xié)議實現(xiàn)的無中心化節(jié)點的集群,因為去中心化的架構(gòu)不存在統(tǒng)一的配置中心,各個節(jié) 點對整個集群狀態(tài)的認知來自于節(jié)點之間的信息交互。在Redis Cluster,這個信息交互是通過Redis Cluster Bus來 完成的 

          Redis的數(shù)據(jù)分區(qū)

          分布式數(shù)據(jù)庫首要解決把整個數(shù)據(jù)集按照分區(qū)規(guī)則映射到多個節(jié)點的問題,即把數(shù)據(jù)集劃分到多個節(jié)點上,每個節(jié) 點負責(zé)整個數(shù)據(jù)的一個子集, Redis Cluster采用哈希分區(qū)規(guī)則,采用虛擬槽分區(qū)。

          虛擬槽分區(qū)巧妙地使用了哈??臻g,使用分散度良好的哈希函數(shù)把所有的數(shù)據(jù)映射到一個固定范圍內(nèi)的整數(shù)集合, 整數(shù)定義為槽(slot)。比如Redis Cluster槽的范圍是0 ~ 16383。槽是集群內(nèi)數(shù)據(jù)管理和遷移的基本單位。采用 大范圍的槽的主要目的是為了方便數(shù)據(jù)的拆分和集群的擴展,每個節(jié)點負責(zé)一定數(shù)量的槽。

          計算公式:slot = CRC16(key)%16383。每一個節(jié)點負責(zé)維護一部分槽以及槽所映射的鍵值數(shù)據(jù)。

           

           

           HashTags

          通過分片手段,可以將數(shù)據(jù)合理的劃分到不同的節(jié)點上,這本來是一件好事。但是有的時候,我們希望對相關(guān)聯(lián)的 業(yè)務(wù)以原子方式進行操作。舉個簡單的例子
          我們在單節(jié)點上執(zhí)行MSET , 它是一個原子性的操作,所有給定的key會在同一時間內(nèi)被設(shè)置,不可能出現(xiàn)某些指定 的key被更新另一些指定的key沒有改變的情況。但是在集群環(huán)境下,我們?nèi)匀豢梢詧?zhí)行MSET命令,但它的操作不 在是原子操作,會存在某些指定的key被更新,而另外一些指定的key沒有改變,原因是多個key可能會被分配到不 同的機器上。
          所以,這里就會存在一個矛盾點,及要求key盡可能的分散在不同機器,又要求某些相關(guān)聯(lián)的key分配到相同機器。這個也是在面試的時候會容易被問到的內(nèi)容。怎么解決呢?
          從前面的分析中我們了解到,分片其實就是一個hash的過程,對key做hash取模然后劃分到不同的機器上。所以為 了解決這個問題,我們需要考慮如何讓相關(guān)聯(lián)的key得到的hash值都相同呢?如果key全部相同是不現(xiàn)實的,所以 怎么解決呢?在redis中引入了HashTag的概念,可以使得數(shù)據(jù)分布算法可以根據(jù)key的某一個部分進行計算,然后 讓相關(guān)的key落到同一個數(shù)據(jù)分片
          舉個簡單的例子,加入對于用戶的信息進行存儲, user:user1:id、user:user1:name/ 那么通過hashtag的方式, user:{user1}:id、user:{user1}.name; 表示 當(dāng)一個key包含 {} 的時候,就不對整個key做hash,而僅對 {} 包括的字符串做hash。

          重定向客戶端

          Redis Cluster并不會代理查詢,那么如果客戶端訪問了一個key并不存在的節(jié)點,這個節(jié)點是怎么處理的呢?比如 我想獲取key為msg的值,msg計算出來的槽編號為254,當(dāng)前節(jié)點正好不負責(zé)編號為254的槽,那么就會返回客戶 端下面信息:
          -MOVED 254 127.0.0.1:6381
          表示客戶端想要的254槽由運行在IP為127.0.0.1,端口為6381的Master實例服務(wù)。如果根據(jù)key計算得出的槽恰好 由當(dāng)前節(jié)點負責(zé),則當(dāng)期節(jié)點會立即返回結(jié)果

          分片遷移

          在一個穩(wěn)定的Redis cluster下,每一個slot對應(yīng)的節(jié)點是確定的,但是在某些情況下,節(jié)點和分片對應(yīng)的關(guān)系會發(fā) 生變更
          \1. 新加入master節(jié)點
          \2. 某個節(jié)點宕機 也就是說當(dāng)動態(tài)添加或減少node節(jié)點時,需要將16384個槽做個再分配,槽中的鍵值也要遷移。當(dāng)然,這一過程, 在目前實現(xiàn)中,還處于半自動狀態(tài),需要人工介入

          新增一個主節(jié)點
          新增一個節(jié)點D,redis cluster的這種做法是從各個節(jié)點的前面各拿取一部分slot到D上。大致就會變成這樣:節(jié)點A覆蓋1365-5460
          節(jié)點B覆蓋6827-10922 節(jié)點C覆蓋12288-16383
          節(jié)點D覆蓋0-1364,5461-6826,10923-12287
          刪除一個主節(jié)點
          先將節(jié)點的數(shù)據(jù)移動到其他節(jié)點上,然后才能執(zhí)行刪除

          槽遷移的過程

          槽遷移的過程中有一個不穩(wěn)定狀態(tài),這個不穩(wěn)定狀態(tài)會有一些規(guī)則,這些規(guī)則定義客戶端的行為,從而使得Redis Cluster不必宕機的情況下可以執(zhí)行槽的遷移。下面這張圖描述了我們遷移編號為1、2、3的槽的過程中,他們在 MasterA節(jié)點和MasterB節(jié)點中的狀態(tài)。

           

           

           

          簡單的工作流程
          \1. 向MasterB發(fā)送狀態(tài)變更命令,吧Master B對應(yīng)的slot狀態(tài)設(shè)置為IMPORTING
          \2. 向MasterA發(fā)送狀態(tài)變更命令,將Master對應(yīng)的slot狀態(tài)設(shè)置為MIGRATING 當(dāng)MasterA的狀態(tài)設(shè)置為MIGRANTING后,表示對應(yīng)的slot正在遷移,為了保證slot數(shù)據(jù)的一致性,MasterA此時 對于slot內(nèi)部數(shù)據(jù)提供讀寫服務(wù)的行為和通常狀態(tài)下是有區(qū)別的,

          MIGRATING狀態(tài) 

           \1. 如果客戶端訪問的Key還沒有遷移出去,則正常處理這個key

          \2. 如果key已經(jīng)遷移或者根本就不存在這個key,則回復(fù)客戶端ASK信息讓它跳轉(zhuǎn)到MasterB去執(zhí)行 

          IMPORTING狀態(tài)

          當(dāng)MasterB的狀態(tài)設(shè)置為IMPORTING后,表示對應(yīng)的slot正在向MasterB遷入,及時Master仍然能對外提供該slot 的讀寫服務(wù),但和通常狀態(tài)下也是有區(qū)別的
          \1. 當(dāng)來自客戶端的正常訪問不是從ASK跳轉(zhuǎn)過來的,說明客戶端還不知道遷移正在進行,很有可能操作了一個目前 還沒遷移完成的并且還存在于MasterA上的key,如果此時這個key在A上已經(jīng)被修改了,那么B和A的修改則會發(fā)生 沖突。所以對于MasterB上的slot上的所有非ASK跳轉(zhuǎn)過來的操作,MasterB都不會uu出去護理,而是通過MOVED 命令讓客戶端跳轉(zhuǎn)到MasterA上去執(zhí)行

          這樣的狀態(tài)控制保證了同一個key在遷移之前總是在源節(jié)點上執(zhí)行,遷移后總是在目標(biāo)節(jié)點上執(zhí)行,防止出現(xiàn)兩邊 同時寫導(dǎo)致的沖突問題。而且遷移過程中新增的key一定會在目標(biāo)節(jié)點上執(zhí)行,源節(jié)點也不會新增key,是的整個遷 移過程既能對外正常提供服務(wù),又能在一定的時間點完成slot的遷移。

          Redis Java客戶端介紹

          已有的客戶端支持

          Redis Java客戶端有很多的開源產(chǎn)品比如Redission、Jedis、lettuce 

          差異

          Jedis是Redis的Java實現(xiàn)的客戶端,其API提供了比較全面的Redis命令的支持;
          Redisson實現(xiàn)了分布式和可擴展的Java數(shù)據(jù)結(jié)構(gòu),和Jedis相比,功能較為簡單,不支持字符串操作,不支持排 序、事務(wù)、管道、分區(qū)等Redis特性。Redisson主要是促進使用者對Redis的關(guān)注分離,從而讓使用者能夠?qū)⒕Ω?集中地放在處理業(yè)務(wù)邏輯上。
          lettuce是基于Netty構(gòu)建的一個可伸縮的線程安全的Redis客戶端,支持同步、異步、響應(yīng)式模式。多個線程可以 共享一個連接實例,而不必擔(dān)心多線程并發(fā)問題;

          jedis-sentinel原理分析

          原理

          客戶端通過連接到哨兵集群,通過發(fā)送Protocol.SENTINEL_GET_MASTER_ADDR_BY_NAME 命令,從哨兵機器中 詢問master節(jié)點的信息,拿到master節(jié)點的ip和端口號以后,再到客戶端發(fā)起連接。連接以后,需要在客戶端建 立監(jiān)聽機制,當(dāng)master重新選舉之后,客戶端需要重新連接到新的master節(jié)點 

          jedis-cluster原理分析

          連接方式

          Set<HostAndPort> hostAndPorts=new HashSet<>(); HostAndPort hostAndPort=new HostAndPort("192.168.11.153",7000); HostAndPort hostAndPort1=new HostAndPort("192.168.11.153",7001); HostAndPort hostAndPort2=new HostAndPort("192.168.11.154",7003); HostAndPort hostAndPort3=new HostAndPort("192.168.11.157",7006); hostAndPorts.add(hostAndPort); hostAndPorts.add(hostAndPort1); hostAndPorts.add(hostAndPort2); hostAndPorts.add(hostAndPort3); JedisCluster jedisCluster=new JedisCluster(hostAndPorts,6000); jedisCluster.set("mic","hello");

          原理分析

          程序啟動初始化集群環(huán)境

          1)、讀取配置文件中的節(jié)點配置,無論是主從,無論多少個,只拿第一個,獲取redis連接實例 2)、用獲取的redis連接實例執(zhí)行clusterNodes()方法,實際執(zhí)行redis服務(wù)端cluster nodes命令,獲取主從配置信 息
          3)、解析主從配置信息,先把所有節(jié)點存放到nodes的map集合中,key為節(jié)點的ip:port,value為當(dāng)前節(jié)點的 jedisPool

          4)、解析主節(jié)點分配的slots區(qū)間段,把slot對應(yīng)的索引值作為key,第三步中拿到的jedisPool作為value,存儲在 slots的map集合中 就實現(xiàn)了slot槽索引值與jedisPool的映射,這個jedisPool包含了master的節(jié)點信息,所以槽和幾點是對應(yīng)的,與 redis服務(wù)端一致

          從集群環(huán)境存取值

          1)、把key作為參數(shù),執(zhí)行CRC16算法,獲取key對應(yīng)的slot值
          2)、通過該slot值,去slots的map集合中獲取jedisPool實例

          3)、通過jedisPool實例獲取jedis實例,最終完成redis數(shù)據(jù)存取工作 

          Redisson客戶端的操作方式

          redis-cluster連接方式

          Config config=new Config(); config.useClusterServers().setScanInterval(2000).        
          addNodeAddress("redis://192.168.11.153:7000",                
                    "redis://192.168.11.153:7001",                
                    "redis://192.168.11.154:7003",
                    "redis://192.168.11.157:7006"); 
          RedissonClient redissonClient= Redisson.create(config); 
          RBucket<String> rBucket=redissonClient.getBucket("mic"); 
          System.out.println(rBucket.get());

          常規(guī)操作命令

          getBucket-> 獲取字符串對象; 
          getMap -> 獲取map對象 
          getSortedSet->獲取有序集合 
          getSet -> 獲取集合 
          getList ->獲取列表

          分布式鎖的實現(xiàn)

          關(guān)于鎖,其實我們或多或少都有接觸過一些,比如synchronized、 Lock這些,這類鎖的目的很簡單,在多線程環(huán) 境下,對共享資源的訪問造成的線程安全問題,通過鎖的機制來實現(xiàn)資源訪問互斥。那么什么是分布式鎖呢?或者 為什么我們需要通過Redis來構(gòu)建分布式鎖,其實最根本原因就是Score(范圍),因為在分布式架構(gòu)中,所有的應(yīng) 用都是進程隔離的,在多進程訪問共享資源的時候我們需要滿足互斥性,就需要設(shè)定一個所有進程都能看得到的范 圍,而這個范圍就是Redis本身。所以我們才需要把鎖構(gòu)建到Redis中。
          Redis里面提供了一些比較具有能夠?qū)崿F(xiàn)鎖特性的命令,比如SETEX(在鍵不存在的情況下為鍵設(shè)置值),那么我們可 以基于這個命令來去實現(xiàn)一些簡單的鎖的操作

          Redisson實現(xiàn)分布式鎖

          Redisson它除了常規(guī)的操作命令以外,還基于redis本身的特性去實現(xiàn)了很多功能的封裝,比如分布式鎖、原子操 作、布隆過濾器、隊列等等。我們可以直接利用這個api提供的功能去實現(xiàn)

          Config config=new Config(); 
          config.useSingleServer().setAddress("redis://192.168.11.152:6379"); 
          RedissonClient redissonClient=Redisson.create(config);

          RLock rLock=redissonClient.getLock("updateOrder");//最多等待100秒、上鎖10s以后自動解鎖 
          if(rLock.tryLock(100,10,TimeUnit.SECONDS)){ 
              System.out.println("獲取鎖成功"); 
          }

          管道模式

          Redis服務(wù)是一種C/S模型,提供請求-響應(yīng)式協(xié)議的TCP服務(wù),所以當(dāng)客戶端發(fā)起請求,服務(wù)端處理并返回結(jié)果到 客戶端,一般是以阻塞形式等待服務(wù)端的響應(yīng),但這在批量處理連接時延遲問題比較嚴重,所以Redis為了提升或 彌補這個問題,引入了管道技術(shù):可以做到服務(wù)端未及時響應(yīng)的時候,客戶端也可以繼續(xù)發(fā)送命令請求,做到客戶 端和服務(wù)端互不影響,服務(wù)端并最終返回所有服務(wù)端的響應(yīng),大大提高了C/S模型交互的響應(yīng)速度上有了質(zhì)的提高 

          使用方法

          Jedis jedis=new Jedis("192.168.11.152",6379); 
          Pipeline pipeline=jedis.pipelined(); 

          for(int i=0;i<1000;i++){    
            pipeline.incr("test"); 

          pipeline.sync();

          Redis的應(yīng)用架構(gòu)

          對于讀多寫少的高并發(fā)場景,我們會經(jīng)常使用緩存來進行優(yōu)化。比如說支付寶的余額展示功能,實際上99%的時候 都是查詢,1%的請求是變更(除非是土豪,每秒鐘都有收入在不斷更改余額),所以,我們在這樣的場景下,可 以加入緩存,用戶->余額

           

           Redis緩存與數(shù)據(jù)一致性問題 

          那么基于上面的這個出發(fā)點,問題就來了,當(dāng)用戶的余額發(fā)生變化的時候,如何更新緩存中的數(shù)據(jù),也就是說。
          \1. 我是先更新緩存中的數(shù)據(jù)再更新數(shù)據(jù)庫的數(shù)據(jù);
          \2. 還是修改數(shù)據(jù)庫中的數(shù)據(jù)再更新緩存中的數(shù)據(jù)
          這就是我們經(jīng)常會在面試遇到的問題,數(shù)據(jù)庫的數(shù)據(jù)和緩存中的數(shù)據(jù)如何達到一致性?首先,可以肯定的是, redis中的數(shù)據(jù)和數(shù)據(jù)庫中的數(shù)據(jù)不可能保證事務(wù)性達到統(tǒng)一的,這個是毫無疑問的,所以在實際應(yīng)用中,我們都 是基于當(dāng)前的場景進行權(quán)衡降低出現(xiàn)不一致問題的出現(xiàn)概率

          更新緩存還是讓緩存失效

          更新緩存表示數(shù)據(jù)不但會寫入到數(shù)據(jù)庫,還會同步更新緩存;而讓緩存失效是表示只更新數(shù)據(jù)庫中的數(shù)據(jù),然后刪 除緩存中對應(yīng)的key。那么這兩種方式怎么去選擇?這塊有一個衡量的指標(biāo)。\1. 如果更新緩存的代價很小,那么可以先更新緩存,這個代價很小的意思是我不需要很復(fù)雜的計算去獲得最新的 余額數(shù)字。
          \2. 如果是更新緩存的代價很大,意味著需要通過多個接口調(diào)用和數(shù)據(jù)查詢才能獲得最新的結(jié)果,那么可以先淘汰 緩存。淘汰緩存以后后續(xù)的請求如果在緩存中找不到,自然去數(shù)據(jù)庫中檢索。

          先操作數(shù)據(jù)庫還是先操作緩存?

          當(dāng)客戶端發(fā)起事務(wù)類型請求時,假設(shè)我們以讓緩存失效作為緩存的的處理方式,那么又會存在兩個情況,

          \1. 先更新數(shù)據(jù)庫再讓緩存失效
          \2. 先讓緩存失效,再更新數(shù)據(jù)庫

          前面我們講過,更新數(shù)據(jù)庫和更新緩存這兩個操作,是無法保證原子性的,所以我們需要根據(jù)當(dāng)前業(yè)務(wù)的場景的容 忍性來選擇。也就是如果出現(xiàn)不一致的情況下,哪一種更新方式對業(yè)務(wù)的影響最小,就先執(zhí)行影響最小的方案 

          最終一致性的解決方案

           

           關(guān)于緩存雪崩的解決方案 

          當(dāng)緩存大規(guī)模滲透在整個架構(gòu)中以后,那么緩存本身的可用性講決定整個架構(gòu)的穩(wěn)定性。那么接下來我們來討論下 緩存在應(yīng)用過程中可能會導(dǎo)致的問題。 

          緩存雪崩

          緩存雪崩是指設(shè)置緩存時采用了相同的過期時間,導(dǎo)致緩存在某一個時刻同時失效,或者緩存服務(wù)器宕機宕機導(dǎo)致 緩存全面失效,請求全部轉(zhuǎn)發(fā)到了DB層面,DB由于瞬間壓力增大而導(dǎo)致崩潰。緩存失效導(dǎo)致的雪崩效應(yīng)對底層系 統(tǒng)的沖擊是很大的。 

          解決方式

          \1. 對緩存的訪問,如果發(fā)現(xiàn)從緩存中取不到值,那么通過加鎖或者隊列的方式保證緩存的單進程操作,從而避免 失效時并發(fā)請求全部落到底層的存儲系統(tǒng)上;但是這種方式會帶來性能上的損耗
          \2. 將緩存失效的時間分散,降低每一個緩存過期時間的重復(fù)率

          \3. 如果是因為緩存服務(wù)器故障導(dǎo)致的問題,一方面需要保證緩存服務(wù)器的高可用、另一方面,應(yīng)用程序中可以采 用多級緩存

          緩存穿透

           緩存穿透是指查詢一個根本不存在的數(shù)據(jù),緩存和數(shù)據(jù)源都不會命中。出于容錯的考慮,如果從數(shù)據(jù)層查不到數(shù)據(jù) 則不寫入緩存,即數(shù)據(jù)源返回值為 null 時,不緩存 null。緩存穿透問題可能會使后端數(shù)據(jù)源負載加大,由于很多后 端數(shù)據(jù)源不具備高并發(fā)性,甚至可能造成后端數(shù)據(jù)源宕掉

          解決方式

          \1. 如果查詢數(shù)據(jù)庫也為空,直接設(shè)置一個默認值存放到緩存,這樣第二次到緩沖中獲取就有值了,而不會繼續(xù)訪 問數(shù)據(jù)庫,這種辦法最簡單粗暴。比如,”key” , “&&”。在返回這個&&值的時候,我們的應(yīng)用就可以認為這是不存在的key,那我們的應(yīng)用就可以決定是否繼續(xù)等待繼續(xù)訪 問,還是放棄掉這次操作。如果繼續(xù)等待訪問,過一個時間輪詢點后,再次請求這個key,如果取到的值不再是 &&,則可以認為這時候key有值了,從而避免了透傳到數(shù)據(jù)庫,從而把大量的類似請求擋在了緩存之中。
          \2. 根據(jù)緩存數(shù)據(jù)Key的設(shè)計規(guī)則,將不符合規(guī)則的key進行過濾 采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個足夠大的BitSet中,不存在的數(shù)據(jù)將會被攔截掉,從而避免了 對底層存儲系統(tǒng)的查詢壓力

          布隆過濾器

          布隆過濾器是Burton Howard Bloom在1970年提出來的,一種空間效率極高的概率型算法和數(shù)據(jù)結(jié)構(gòu),主要用來 判斷一個元素是否在集合中存在。因為他是一個概率型的算法,所以會存在一定的誤差,如果傳入一個值去布隆過 濾器中檢索,可能會出現(xiàn)檢測存在的結(jié)果但是實際上可能是不存在的,但是肯定不會出現(xiàn)實際上不存在然后反饋存 在的結(jié)果。因此,Bloom Filter不適合那些“零錯誤”的應(yīng)用場合。而在能容忍低錯誤率的應(yīng)用場合下,Bloom Filter 通過極少的錯誤換取了存儲空間的極大節(jié)省。

          bitmap

          所謂的Bit-map就是用一個bit位來標(biāo)記某個元素對應(yīng)的Value,通過Bit為單位來存儲數(shù)據(jù),可以大大節(jié)省存儲空間. 所以我們可以通過一個int型的整數(shù)的32比特位來存儲32個10進制的數(shù)字,那么這樣所帶來的好處是內(nèi)存占用少、 效率很高(不需要比較和位移)比如我們要存儲5(101)、3(11)四個數(shù)字,那么我們申請int型的內(nèi)存空間,會有32 個比特位。這四個數(shù)字的二進制分別對應(yīng)
          從右往左開始數(shù),比如第一個數(shù)字是5,對應(yīng)的二進制數(shù)據(jù)是101, 那么從右往左數(shù)到第5位,把對應(yīng)的二進制數(shù)據(jù) 存儲到32個比特位上。
          第一個5就是 00000000000000000000000000101000

          輸入3時候 00000000000000000000000000001100

          布隆過濾器原理

          布隆過濾器(Bloom Filter)的核心實現(xiàn)是一個超大的位數(shù)組和幾個哈希函數(shù)。假設(shè)位數(shù)組的長度為m,哈希函數(shù)的個數(shù)為k

           

           

          以上圖為例,具體的操作流程:假設(shè)集合里面有3個元素{x, y, z},哈希函數(shù)的個數(shù)為3。首先將位數(shù)組進行初始化,將里面每個位都設(shè)置位0。對于集合里面的每一個元素,將元素依次通過3個哈希函數(shù)進行映射,每次映射都會產(chǎn)生一個哈希值,這個值對應(yīng)位數(shù)組上面的一個點,然后將位數(shù)組對應(yīng)的位置標(biāo)記為1。查詢W元素是否存在集合中的時候,同樣的方法將W通過哈希映射到位數(shù)組上的3個點。如果3個點的其中有一個點不為1,則可以判斷該元素一定不存在集合中。反之,如果3個點都為1,則該元素可能存在集合中。注意:此處不能判斷該元素是否一定存在集合中,可能存在一定的誤判率。可以從圖中可以看到:假設(shè)某個元素通過映射對應(yīng)下標(biāo)為4,5,6這3個點。雖然這3個點都為1,但是很明顯這3個點是不同元素經(jīng)過哈希得到的位置,因此這種情況說明元素雖然不在集合中,也可能對應(yīng)的都是1,這是誤判率存在的原因。

           

           


          鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布

          ??????

          ??長按上方微信二維碼 2 秒





          感謝點贊支持下哈 

          瀏覽 79
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产美女久久久久久 | 在线播放国产精品 | 亚洲黄色片免费看 | 国产成人自拍视频在线 | 乱伦一区二区三区 |