偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Redis 6.0更新放大招:客戶端緩存怎么用好

數(shù)據(jù)庫(kù) MySQL Redis
我們今天就依次聊一下客戶端緩存的必要性、具體使用、原理分析和實(shí)現(xiàn)。

近日 Redis 6.0.0 GA 版本發(fā)布,這是 Redis 歷史上最大的一次版本更新,包括了客戶端緩存 (Client side caching)、ACL、Threaded I/O 和 Redis Cluster Proxy 等諸多更新。

我們今天就依次聊一下客戶端緩存的必要性、具體使用、原理分析和實(shí)現(xiàn)。

為什么需要客戶端緩存

我們都知道,使用 Redis 進(jìn)行數(shù)據(jù)的緩存主要目的是減少對(duì) MySQL 等數(shù)據(jù)庫(kù)的訪問,提供更快的訪問速度,畢竟 《Redis in Action》中提到的, Redis 的性能大致是普通關(guān)系型數(shù)據(jù)庫(kù)的 10 ~ 100 倍。

所以,如下圖所示,Redis 用來(lái)存儲(chǔ)熱點(diǎn)數(shù)據(jù),Redis 未命中,再去訪問數(shù)據(jù)庫(kù),這樣可以應(yīng)付大多數(shù)情況下的性能要求。

但是,Redis 也有其性能上限,并且訪問 Redis 必然有一定的網(wǎng)絡(luò) I/O 以及序列化反序列化損耗。所以,往往會(huì)引入進(jìn)程緩存,將最熱的數(shù)據(jù)存儲(chǔ)在本地,進(jìn)一步加快訪問速度。

如上圖所示(示意圖,細(xì)節(jié)不必過度在意,下同),Guava Cache 等進(jìn)程緩存作為一級(jí)緩存,Redis 作為二級(jí)緩存:

  •  先去 Guava Cache 中查詢數(shù)據(jù),如果命中則直接返回。
  •  Guava Cache 中未命中,則再去 Redis 中查詢,如果命中則返回?cái)?shù)據(jù),并在 Guava Cache 中設(shè)置此數(shù)據(jù)。
  •  Redis 也未命中的話,只有去 MySQL 中查詢,然后依次將數(shù)據(jù)設(shè)置到 Redis 和 Guava Cache 中。

只使用 Redis 分布式緩存時(shí),遇到數(shù)據(jù)更新時(shí),應(yīng)用程序更新完 MySQL 中的數(shù)據(jù),可以直接將 Redis 中對(duì)應(yīng)緩存失效掉,保持?jǐn)?shù)據(jù)的一致性。

而進(jìn)程內(nèi)緩存的數(shù)據(jù)一致性比分布式的緩存面臨更大的挑戰(zhàn)。數(shù)據(jù)更新的時(shí)候,如何通知其他進(jìn)程也更新自己的緩存呢?

如果按照分布式緩存的思路,我們可以設(shè)置極短的緩存失效時(shí)間,這樣不必實(shí)現(xiàn)復(fù)雜的通知機(jī)制。

但是不同進(jìn)程內(nèi)的數(shù)據(jù)依然會(huì)面臨不一致的問題,并且不同進(jìn)程緩存失效時(shí)間不統(tǒng)一,同一個(gè)請(qǐng)求到了不同的進(jìn)程,可能出現(xiàn)反復(fù)幻讀的情況。

Ben 在 RedisConf18 給出了一個(gè)方案(視頻和 PPT 鏈接在文末),通過 Redis 的 Pub/Sub,可以通知其他進(jìn)程緩存對(duì)此緩存進(jìn)行刪除。如果 Redis 掛了或者訂閱機(jī)制不靠譜,依靠超時(shí)設(shè)定,依然可以做兜底處理。

Antirez(Redis 的作者)也正是聽取 Ben 這個(gè)方案后,才決定在 Redis Server 支持客戶端緩存的,因?yàn)樵谟蟹?wù)端參與的情況下可以更好的處理上述這些問題。

功能介紹和演示

下面使用 Docker 安裝 Redis 6.0.1,然后使用 telnet 來(lái)簡(jiǎn)單演示一下 Redis 6.0 的客戶端緩存功能。所有相關(guān)的功能如下圖所示,分別是使用RESP3 協(xié)議版本的普通模式和廣播模式,以及使用 RESP2 協(xié)議版本的轉(zhuǎn)發(fā)模式。我們先來(lái)看普通模式。

普通模式

先使用 redis-cli 設(shè)置緩存值 test=111,使用 telnet 連接上 Redis,然后發(fā)送 hello 3 開啟 RESP3 協(xié)議。  

  1. [root@VM_0_3_centos ~]# telnet 127.0.0.1 6379  
  2.    Trying 127.0.0.1...  
  3.    Connected to 127.0.0.1.  
  4.    Escape character is '^]'. 
  5.    hello 3  
  6.    // telnet 輸出結(jié)果格式化標(biāo)準(zhǔn)化后如下,否則換行太多并且是 RESP3 格式,不需要了解格式。  
  7.    > HELLO 3 
  8.    1# "server" => "redis"  
  9.    2# "version" => "6.0.1"  
  10.    3# "proto" => (integer) 3  
  11.    4# "id" => (integer) 10  
  12.    5# "mode" => "standalone"  
  13.    6# "role" => "master"  
  14.    7# "modules" => (empty array) 

這里需要注意,Redis 服務(wù)端只會(huì) track 客戶端在一個(gè)連接生命周期內(nèi)的獲取的只讀命令的 key值。Redis 客戶端默認(rèn)不開啟 track 模式,需要使用命令開啟,然后必須要先獲取一次 test 的值,這樣 Redis 服務(wù)器才會(huì)記錄它。 

  1. client tracking on  
  2.  +OK  
  3.  get test  
  4.  $3  
  5.  111 

當(dāng)鍵被修改,或者因?yàn)槭r(shí)間(expire time)和內(nèi)存上限 maxmemory 策略被驅(qū)除時(shí),Redis 服務(wù)端會(huì)通知這些客戶端。我們這里簡(jiǎn)單地更新 test 的值,telnet 則會(huì)收到如下通知: 

  1. >2 // RESP3 中的 PUSH 類型,標(biāo)志為 > 符號(hào)  
  2. $10  
  3. invalidate  
  4. *1  
  5. $4  
  6. test 

如果你再一次更新 test 值,這次 telnet 就不會(huì)再收到失效(invalidate)消息。除非 telnet 再進(jìn)行一次 get 操作,重新 tracking 對(duì)應(yīng)的鍵值。

也就是說(shuō) Redis 服務(wù)端記錄的客戶端 track 信息只生效一次,發(fā)送過失效消息后就會(huì)刪除,只有下次客戶端再次執(zhí)行只讀命令被 track,才會(huì)進(jìn)行下一次消息通知 。

取消 tracking 的命令如下所示: 

  1. client tracking off  
  2.   +OK 

廣播模式

Redis 還提供了一種廣播模式(BCAST),它是另外一種客戶端緩存的實(shí)現(xiàn)方式。這種方式下 Redis 服務(wù)端不再消耗過多內(nèi)存存儲(chǔ)信息,而是發(fā)送更多的失效消息給客戶端。

這是服務(wù)端存儲(chǔ)過多數(shù)據(jù),消耗內(nèi)存和客戶端收到過多消息,消耗網(wǎng)絡(luò)帶寬之間的權(quán)衡(tradeoff)。   

  1. // 已經(jīng) hello 3 開啟 RESP3 協(xié)議,不然無(wú)法收到失效消息,下同  
  2.     client tracking on bcast  
  3.     +OK  
  4.     // 此時(shí)設(shè)置 key 為 a 的鍵值,收到如下消息。  
  5.     > 
  6.     $10  
  7.     invalidate  
  8.     *1  
  9.     $1  
  10.     a 

如果你不想所有的鍵值的失效消息都收到,則可以限制 key 的前綴,如下命令則表示只關(guān)注前綴為 test 的鍵值的消息。一般來(lái)說(shuō),業(yè)務(wù)的緩存 key 都是根據(jù)業(yè)務(wù)擁有統(tǒng)一的前綴,所以這一特性十分方便。 

  1. client tracking on bcast prefix test 

與普通模式必須獲取一次鍵的規(guī)則不同,廣播模式下,只要鍵被修改或刪除,符合規(guī)則的客戶端都會(huì)收到失效消息,而且是可以多次獲取的。

與普通模式相比,雖然少存儲(chǔ)了一些數(shù)據(jù),但是由于需要對(duì)前綴規(guī)則進(jìn)行匹配,會(huì)消耗一定的 CPU 資源,所以注意別使用過長(zhǎng)的前綴。

轉(zhuǎn)發(fā)模式

上述操作時(shí)客戶端都需要先開啟 RESP3,Redis 為了兼容 RESP2 協(xié)議提供了轉(zhuǎn)發(fā)(Redirect)模式,不再使用 RESP3 原生支持 PUSH 消息,而是將消息通過 Pub/Sub 通知給另外一個(gè)客戶端,具體流程如下圖所示:

這里需要兩個(gè) telnet,其中一個(gè) telnet 需要訂閱 _redis_:invalidate 信道。然后另一個(gè) telnet 開啟 Redirect 模式,并制定將失效消息通過訂閱信道發(fā)送給第一個(gè) telnet。   

  1. # telent B  
  2.     client id  
  3.     :368  
  4.     subscribe _redis_:invalidate  
  5.     # telnet A,開啟 track 并指定轉(zhuǎn)發(fā)給 B  
  6.     client tracking on bcast redirect 368  
  7.     # telent B 此時(shí)有鍵值被修改,收到 __redis__:invalidate 信道的消息  
  8.     message  
  9.     $20  
  10.     __redis__:invalidate  
  11.     *1  
  12.     $1  
  13.     a 

你會(huì)發(fā)現(xiàn),轉(zhuǎn)發(fā)模式和文章開始提到的多級(jí)緩存中的更新機(jī)制很類似了,只不過那個(gè)方案中是業(yè)務(wù)系統(tǒng)修改完 key 后發(fā)送消息通知,而這里是 Redis 服務(wù)端代替業(yè)務(wù)系統(tǒng)發(fā)送消息通知。

OPTIN和OPTOUT選項(xiàng)

使用 OPTIN 可以選擇性的開啟 tracking。只有你發(fā)送 client caching yes (Redis 文檔中是 CACHING 命令,但是實(shí)驗(yàn)時(shí)發(fā)現(xiàn)無(wú)效)之后的下一條的只讀命令的 key 才會(huì) tracking,否則其他的只讀命令的 key 不會(huì)被 tracking。 

  1. client tracking on optin  
  2.  client caching yes  
  3.  get a  
  4.  get b  
  5.  // 此時(shí)修改 a 和 b 的值,發(fā)現(xiàn)只收到 a 的失效消息  
  6.  > 
  7.  $10  
  8.  invalidate  
  9.  *1  
  10.  $1  
  11.  a 

而 OPTOUT 參數(shù)與之相反,你可以有選擇的退出 tracking。發(fā)送 client caching off 之后的下一條只讀命令的 key 不會(huì)被 tracking,其他只讀命令都會(huì)被 tracking。

OPTIN 和 OPTOUT 是針對(duì)非 BCAST 模式,也就是只有發(fā)送了某個(gè) key 的只讀命令后,才會(huì)追蹤相應(yīng)的 key。而 BCAST 模式是無(wú)論你是否發(fā)送某個(gè) key 的只讀命令,只有 Redis 修改了 key,都會(huì)發(fā)送相應(yīng)的 key 的失效消息(前綴匹配的)。

NOLOOP選項(xiàng)

默認(rèn)情況下,失效消息會(huì)發(fā)送給所有需要的 Redis 客戶端,但是有些情況下觸發(fā)失效消息也就是更新 key 的客戶端不需要收到該消息。

設(shè)置 NOLOOP,可以避免這種情況,更新 Key 的客戶端將不再收到消息,該選項(xiàng)在普通模式和廣播模式下都適用。

trackingtablemax_keys

最大 tracking 上限 trackingtablemax_keys。

由上文可以知道,普通模式下需要存儲(chǔ)大量的被 tracking 的 key 和客戶端信息(具體存儲(chǔ)的數(shù)據(jù)下文中會(huì)講解),所以當(dāng) 10k 客戶端使用該模式處理百萬(wàn)個(gè)鍵時(shí),會(huì)消耗大量的內(nèi)存空間,所以 Redis 引入了 trackingtablemax_keys 配置,默認(rèn)為無(wú),不限制。

當(dāng)有一個(gè)新的鍵被 tracking 時(shí),如果當(dāng)前 tracking 的 key 的數(shù)量大于 trackingtablemax_keys,則會(huì)隨機(jī)刪除之前 tracking 的 key,并且向?qū)?yīng)的客戶端發(fā)送失效消息。

原理和源碼實(shí)現(xiàn)

普通模式原理

我們也先講解普通模式的原理,Redis 服務(wù)端使用 TrackingTable 存儲(chǔ)普通模式的客戶端數(shù)據(jù),它的數(shù)據(jù)類型是基數(shù)樹(radix tree)。

基數(shù)樹是針對(duì)稀疏的長(zhǎng)整型數(shù)據(jù)查找的多叉搜索樹,能快速且節(jié)省空間的完映射,一般用于解決 Hash沖突和 Hash表大小的設(shè)計(jì)問題,Linux 的內(nèi)存管理就使用了它。

Redis 用它存儲(chǔ)鍵的指針和客戶端 ID 的映射關(guān)系。因?yàn)殒I對(duì)象的指針就是內(nèi)存地址,也就是長(zhǎng)整型數(shù)據(jù)。客戶端緩存的相關(guān)操作就是對(duì)該數(shù)據(jù)的增刪改查:

  •  當(dāng)開啟 track 功能的客戶端獲取某一個(gè)鍵值時(shí),Redis 會(huì)調(diào)用 enableTracking 方法使用基數(shù)樹記錄下該 key 和 clientId 的映射關(guān)系。
  •  當(dāng)某一個(gè) key 被修改或刪除時(shí),Redis 會(huì)調(diào)用 trackingInvalidateKey 方法根據(jù) key 從 TrackingTable 中查找所有對(duì)應(yīng)的客戶端ID,然后調(diào)用 sendTrackingMessage 方法發(fā)送失效消息給這些客戶端(會(huì)檢查 CLIENT_TRACKING 相關(guān)標(biāo)志位是否開啟和是否開啟了 NOLOOP)。
  •  發(fā)送完失效消息后,根據(jù)鍵的指針值將映射關(guān)系從 TrackingTable中刪除。
  •  客戶端關(guān)閉 track 功能后,因?yàn)閯h除需要進(jìn)行大量操作,所以 Redis 使用懶刪除方式,只是將該客戶端的 CLIENT_TRACKING 相關(guān)標(biāo)志位刪除掉。

廣播模式原理

廣播模式與普通模式類似,Redis 同樣使用 PrefixTable 存儲(chǔ)廣播模式下的客戶端數(shù)據(jù),它存儲(chǔ)前綴字符串指針和(需要通知的key和客戶端ID)的映射關(guān)系。它和廣播模式最大的區(qū)別就是真正發(fā)送失效消息的時(shí)機(jī)不同:

  •  當(dāng)客戶端開啟廣播模式時(shí),會(huì)在 PrefixTable的前綴對(duì)應(yīng)的客戶端列表中加入該客戶端ID。
  •  當(dāng)某一個(gè) key 被修改或刪除時(shí),Redis 會(huì)調(diào)用 trackingInvalidateKey 方法, trackingInvalidateKey 方法中如果發(fā)現(xiàn) PrefixTable 不為空,則調(diào)用 trackingRememberKeyToBroadcast 依次遍歷所有前綴,如果key 符合前綴規(guī)則,則記錄到 PrefixTable 對(duì)應(yīng)的位置。
  •  在 Redis 的事件處理周期函數(shù) beforeSleep 函數(shù)里會(huì)調(diào)用 trackingBroadcastInvalidationMessages 函數(shù)來(lái)真正發(fā)送消息。

處理最大tracking上限

Redis 會(huì)在每次執(zhí)行過命令后(processCommand方法)調(diào)用 trackingLimitUsedSlots 來(lái)判斷是否需要進(jìn)行清理:

  •  判斷 TrackingTable 中鍵的數(shù)量是否大于 trackingtablemax_keys;
  •  在一定時(shí)間段內(nèi)(不能太長(zhǎng),阻塞主流程),隨機(jī)從 TrackingTable 中選出一個(gè)鍵刪除,直到數(shù)量小于或者時(shí)間用完為止。

具體源碼

關(guān)于源碼,在 tracking.c 文件下,我們這里只看一下最為關(guān)鍵的 trackingInvalidateKey 函數(shù)和 sendTrackingMessage 函數(shù),理解了這兩個(gè)函數(shù),廣播模式和處理最大 tracking 上限等相關(guān)函數(shù)都與之類似。 

  1. void trackingInvalidateKey(client *c, robj *keyobj) {  
  2.     if (TrackingTable == NULL) return;  
  3.     sds sdskey = keyobj->ptr;  
  4.     // 省略,如果廣播模式的記錄基數(shù)樹不為空,則先處理廣播模式  
  5.     // 1 根據(jù)鍵的指針去 TrackingTable 查找  
  6.     rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));  
  7.     if (ids == raxNotFound) return;  
  8.     // 2 使用迭代器遍歷  
  9.     raxIterator ri;raxStart(&ri,ids);raxSeek(&ri,"^",NULL,0);  
  10.     while(raxNext(&ri)) {  
  11.         // 3 根據(jù) clientId 查找 client 實(shí)例  
  12.         client *target = lookupClientByID(id);  
  13.         // 4 如果未開啟 track 或者是廣播模式則跳過。  
  14.         if (target == NULL ||  
  15.             !(target->flags & CLIENT_TRACKING)||  
  16.             target->flags & CLIENT_TRACKING_BCAST)  
  17.         {   continue;  }  
  18.         // 5 如果開啟了 NOLOOP 并且是導(dǎo)致key發(fā)生變化的client則跳過。  
  19.         if (target->flags & CLIENT_TRACKING_NOLOOP &&  
  20.             target == c)  
  21.         {   continue;  }  
  22.         // 6 發(fā)送失效消息  
  23.         sendTrackingMessage(target,sdskey,sdslen(sdskey),0);  
  24.     }  
  25.     // 7 減少數(shù)據(jù)統(tǒng)計(jì),根據(jù)sdskey刪除對(duì)應(yīng)的記錄  
  26.     TrackingTableTotalItems -raxSize(ids);  
  27.     raxFree(ids); 
  28.      raxRemove(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey),NULL);  

源碼如上所示,trackingInvalidateKey 方法主要做了 7 件事情:

  •  根據(jù)鍵的指針去 TrackingTable 查找客戶端ID列表;
  •  使用迭代器遍歷列表;
  •  根據(jù) clientId 查找 client 實(shí)例;
  •  如果 client 實(shí)例未開啟 track 或者是廣播模式則跳過;
  •  如果 client 實(shí)例開啟了 NOLOOP 并且是導(dǎo)致key發(fā)生變化的client則跳過;
  •  調(diào)用 sendTrackingMessage 方法發(fā)送失效消息;
  •  減少數(shù)據(jù)統(tǒng)計(jì),根據(jù)sdskey刪除對(duì)應(yīng)的記錄

下面來(lái)看真正發(fā)送消息的 sendTrackingMessage 函數(shù),它主要做了6件事:

  •  如果 clienttrackingredirection 不為空,則開啟了轉(zhuǎn)發(fā)模式;
  •  找到轉(zhuǎn)發(fā)的客戶端實(shí)例;
  •  如果轉(zhuǎn)發(fā)客戶端關(guān)閉了,則必須通知原客戶端;
  •  如果是客戶端使用 RESP3 則發(fā) PUSH 消息;
  •  如果是轉(zhuǎn)發(fā)模式,往 TrackingChannelName 也就是 _redis_:invalidate 信道中發(fā)送失效消息的頭部信息;
  •  發(fā)送鍵等信息。 

 

責(zé)任編輯:龐桂玉 來(lái)源: DBAplus社群
相關(guān)推薦

2020-05-11 21:31:02

Redis 6.0緩存客戶端

2022-05-11 13:56:08

Java項(xiàng)目redis客戶端

2022-04-28 13:58:41

Redis6客戶端服務(wù)端

2009-08-06 17:12:13

C# WebServi

2015-08-17 09:48:29

C#客戶端分布式緩存

2021-09-22 15:46:29

虛擬桌面瘦客戶端胖客戶端

2011-08-17 10:10:59

2013-08-28 13:36:04

2022-09-23 08:02:42

Kafka消息緩存

2016-11-14 10:07:19

Dynatrace

2011-03-24 13:00:31

配置nagios客戶端

2011-03-02 14:36:24

Filezilla客戶端

2010-12-21 11:03:15

獲取客戶端證書

2011-10-26 13:17:05

2010-05-31 10:11:32

瘦客戶端

2011-03-21 14:53:36

Nagios監(jiān)控Linux

2011-04-06 14:24:20

Nagios監(jiān)控Linux

2013-05-09 09:33:59

2009-03-04 10:27:50

客戶端組件桌面虛擬化Xendesktop

2021-08-01 23:18:21

Redis Golang命令
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)