欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis 2.8-4.0過(guò)期鍵優(yōu)化過(guò)程全紀(jì)錄

 更新時(shí)間:2019年04月10日 08:56:29   作者:白馨  
這篇文章主要給大家介紹了關(guān)于Redis 2.8-4.0過(guò)期鍵優(yōu)化的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

之前 白馨(陌陌-技術(shù)保障部存儲(chǔ)工程師 )在Redis技術(shù)交流群里,總結(jié)了一下Redis從2.8~4.0關(guān)于過(guò)期鍵相關(guān)的fix記錄,非常有幫助,但有些東西未盡詳細(xì),本文將進(jìn)行詳細(xì)說(shuō)明。

先從一個(gè)問(wèn)題來(lái)看,運(yùn)行環(huán)境如下:

Redis: 2.8.19
db0:keys=10000000,expires=10000000
主從結(jié)構(gòu)

從下圖中可以看到,在從節(jié)點(diǎn)get hello非空,在主節(jié)點(diǎn)get hello為空,之后從節(jié)點(diǎn)get hello為空,經(jīng)排查主從同步offset基本正常,但出現(xiàn)了主從不一致。

原因先不說(shuō),本文來(lái)探討下Redis2.8-4.0版本迭代中,針對(duì)過(guò)期鍵的fix,看看能不能找到答案。

一、過(guò)期功能回顧

當(dāng)你執(zhí)行了一條setex命令后,Redis會(huì)向內(nèi)部的dict和expires哈希結(jié)構(gòu)中分別插入數(shù)據(jù):

dict------dict[key]:value
expires---expires[key]:timeout

例如:

127.0.0.1:6379> setex hello 120 world
OK
127.0.0.1:6379> info
# 該數(shù)據(jù)庫(kù)中設(shè)置為過(guò)期鍵并且未被刪除的總量(如果曾設(shè)置為過(guò)期鍵且刪除則不計(jì)入)
db0:keys=1,expires=1,avg_ttl=41989
# 歷史上每一次刪除過(guò)期鍵就做一次加操作,記錄刪除過(guò)期鍵的總數(shù)。
expired_keys:0

二、Redis過(guò)期鍵的刪除策略:

當(dāng)鍵值過(guò)期后,Redis是如何處理呢?綜合考慮Redis的單線程特性,有兩種策略:惰性刪除和定時(shí)刪除。

1.惰性刪除策略:

在每次執(zhí)行key相關(guān)的命令時(shí),都會(huì)先從expires中查找key是否過(guò)期,下面是3.0.7的源碼(db.c):

下面是讀寫key相關(guān)的入口:

robj *lookupKeyRead(redisDb *db, robj *key) {
 robj *val;

 expireIfNeeded(db,key);
 val = lookupKey(db,key);
 ......
 return val;
}

robj *lookupKeyWrite(redisDb *db, robj *key) {
 expireIfNeeded(db,key);
 return lookupKey(db,key);
}

可以看到每次讀寫key前,所有的Redis命令在執(zhí)行之前都會(huì)調(diào)用expireIfNeeded函數(shù):

int expireIfNeeded(redisDb *db, robj *key) {
 mstime_t when = getExpire(db,key);
 mstime_t now;
 if (when < 0) return 0; /* No expire for this key */
 now = server.lua_caller ? server.lua_time_start : mstime();
 if (server.masterhost != NULL) return now > when;
 /* Return when this key has not expired */
 if (now <= when) return 0;
 /* Delete the key */
 server.stat_expiredkeys++;
 propagateExpire(db,key);
 notifyKeyspaceEvent(NOTIFY_EXPIRED,
  "expired",key,db->id);
 return dbDelete(db,key);
}

從代碼可以看出,主從邏輯略有不同:

(1) 主庫(kù):過(guò)期則expireIfNeeded會(huì)刪除過(guò)期鍵,刪除成功返回1,否則返回0。

(2) 從庫(kù):expireIfNeeded不會(huì)刪除key,而會(huì)返回一個(gè)邏輯刪除的結(jié)果,過(guò)期返回1,不過(guò)期返回0 。

但是從庫(kù)過(guò)期鍵刪除由主庫(kù)的synthesized DEL operations控制。

2.定時(shí)刪除策略:

單單靠惰性刪除,肯定不能刪除所有的過(guò)期key,考慮到Redis的單線程特性,Redis使用了定期刪除策略,采用策略是從一定數(shù)量的數(shù)據(jù)庫(kù)的過(guò)期庫(kù)中取出一定數(shù)量的隨機(jī)鍵進(jìn)行檢查,不為空則刪除。不保證實(shí)時(shí)刪除。有興趣的同學(xué)可以看看activeExpireCycle中具體實(shí)現(xiàn),還是挺有意思的,下圖是個(gè)示意圖


if (server->masterhost == NULL) activeExpireCycle();

(1)主庫(kù): 會(huì)定時(shí)刪除過(guò)期鍵。

(2)從庫(kù): 不執(zhí)行定期刪除。

綜上所述: 

主庫(kù):

(1) 在執(zhí)行所有操作之前調(diào)用expireIfNeeded惰性刪除。

(2) 定期執(zhí)行調(diào)用一次activeExpireCycle,每次隨機(jī)刪除部分鍵(定時(shí)刪除)。

從庫(kù):

過(guò)期鍵刪除由主庫(kù)的synthesized DEL operations控制。

三、過(guò)期讀寫問(wèn)題

Redis過(guò)期刪除策略帶來(lái)的問(wèn)題。我們只從用戶操作的角度來(lái)討論。

1、過(guò)期鍵讀操作

下面是Redis 2.8~4.0過(guò)期鍵讀操作的fix記錄

(1) Redis2.8主從不一致

2.8中的讀操作中都先調(diào)用lookupKeyRead函數(shù):

robj *lookupKeyRead(redisDb *db, robs *key) {
  robj *val;
  expireIfNeeded(db,key);
  val = lookupKey(db,key);
  if (val == NULL)
    server.stat_keyspace_misses++;
  else
    server.stat_keyspace_hits++;
  return val;
}

•對(duì)于主庫(kù),執(zhí)行expireIfNeeded時(shí),過(guò)期會(huì)刪除key。lookupKey返回 NULL。

•對(duì)于從庫(kù),執(zhí)行expireIfNeeded時(shí),過(guò)期不會(huì)刪除key。lookupKey返回value。

所以對(duì)于過(guò)期鍵的讀操作,主從返回就會(huì)存在不一致的情況,也就是開篇提到的問(wèn)題。

(2) Redis 3.2主從除exists之外都一致

https://github.com/antirez/redis/commit/06e76bc3e22dd72a30a8a614d367246b03ff1312

3.2-rc1讀操作中同樣先調(diào)用了lookupKeyRead,實(shí)際上調(diào)用的是lookupKeyReadWithFlags函數(shù):

robj *lookupKeyReadWithFlags(redisDb *db, robj *key) {
  robj *val;
  if (expireIfNeeded(db,key) == 1) { 
    if (server.masterhost == NULL) return NULL;
    if (server.current_client && //當(dāng)前客戶端存在
      server.current_client != server.master && //當(dāng)前客戶端不是master請(qǐng)求建立的(用戶請(qǐng)求的客戶端)
      server.current_client->cmd &&
      server.current_client->cmd->flags & REDIS_CMD_READONLY) { //讀命令
        return NULL;
       }
  val = lookupKey(db,key,flags);
  if (val == NULL)
    server.stat_keyspace_misses++;
  else
    server.stat_keyspace_hits++;
  return val;
  }

可以看到,相對(duì)于2.8,增加了對(duì)expireIfNeeded返回結(jié)果的判斷:

•對(duì)于主庫(kù),執(zhí)行expireIfNeeded時(shí),過(guò)期會(huì)刪除key,返回1。masterhost為空返回NULL。

•對(duì)于從庫(kù),執(zhí)行expireIfNeeded時(shí),過(guò)期不會(huì)刪除key,返回1。滿足當(dāng)前客戶端不為 master且為讀命令時(shí)返回NULL。

除非程序異常。正常情況下對(duì)于過(guò)期鍵的讀操作,主從返回一致。

(2) Redis 4.0.11解決exists不一致的情況

https://github.com/antirez/redis/commit/32a7a2c88a8b8cca8119b849eee7976b8ada8936

3.2并未解決exists這個(gè)命令的問(wèn)題,雖然它也是個(gè)讀操作。之后的4.0.11中問(wèn)題才得以解決.

2、過(guò)期鍵寫操作

在具體說(shuō)這個(gè)問(wèn)題之前,我們先說(shuō)一下可寫從庫(kù)的使用場(chǎng)景。

(1).主從分離場(chǎng)景中,利用從庫(kù)可寫執(zhí)行耗時(shí)操作提升性能。

作者在https://redis.io/topics/replication 中提到過(guò):

For example computing slow Set or Sorted set operations and storing them into local keys is an use case for writable slaves that was observed multiple times.

在 https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4 舉了一個(gè)更具體的例子:

For instance imagine having slaves replicating certain Sets keys from the master. When accessing the data on the slave, we want to peform intersections between
such Sets values. However we don't want to intersect each time: to cache the intersection for some time often is a good idea.

也就是說(shuō)在讀寫分離的場(chǎng)景中,可以使用過(guò)期鍵的機(jī)制將從庫(kù)作為一個(gè)緩存,去緩存從庫(kù)上耗時(shí)操作的結(jié)果,提升整體性能。

(2). 遷移數(shù)據(jù)時(shí),需要先將從庫(kù)設(shè)置為可寫。

比如下列場(chǎng)景:線上Redis服務(wù)正常,但可能遇到一些硬件的情況,需要對(duì)該機(jī)器上的Redis主從集群遷移。遷數(shù)據(jù)的方式就是搭建一個(gè)新的主從集群,讓新主成為舊主的從。

進(jìn)行如下操作:

•(1)主(舊主)從(新主)同步,rdb傳輸完畢90s之后,設(shè)置從庫(kù)(新主)可寫。

•(2)在主庫(kù)(舊主)完全沒有業(yè)務(wù)連接后,從庫(kù)(新主)執(zhí)行slaveof no one。

這種場(chǎng)景下,為了保證數(shù)據(jù)完全同步,并且盡量減少對(duì)業(yè)務(wù)的影響,就會(huì)先設(shè)置從庫(kù)可寫。

接著我們來(lái)做一個(gè)測(cè)試:

3.2版本主庫(kù)執(zhí)行的操作,主庫(kù)的過(guò)期鍵正常過(guò)期。

3.2版本可寫從庫(kù)執(zhí)行以下操作,從庫(kù)的過(guò)期鍵并不會(huì)過(guò)期。

4.0rc3版本可寫從庫(kù)執(zhí)行以下操作,從庫(kù)的過(guò)期鍵卻能夠過(guò)期。

其實(shí)可寫從庫(kù)過(guò)期鍵問(wèn)題包含兩個(gè)問(wèn)題:

•(1)從庫(kù)中的過(guò)期鍵由主庫(kù)同步過(guò)來(lái)的,過(guò)期操作由主庫(kù)執(zhí)行(未變更過(guò))。

•(2)從庫(kù)中的過(guò)期鍵的設(shè)置是從庫(kù)上操作的。

redis4.0rc3之前,存在過(guò)期鍵泄露的問(wèn)題。當(dāng)expire直接在從庫(kù)上操作,這個(gè)key是不會(huì)過(guò)期的。作者也在https://redis.io/topics/replication 提到過(guò):

However note that writable slaves before version 4.0 were incapable of expiring keys with a time to live set. This means that if you use EXPIRE or other commands that set a maximum TTL for a key, the key will leak, and while you may no longer see it while accessing it with read commands, you will see it in the count of keys and it will still use memory. So in general mixing writable slaves (previous version 4.0) and keys with TTL is going to create issues.

過(guò)期鍵泄露問(wèn)題在https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4中得到了解決。

四.總結(jié)

1、針對(duì)過(guò)期鍵讀操作

(1) Redis2.8主從不一致 

(2) Redis3.2-rc1主從除exists之外都一致: https://github.com/antirez/redis/commit/06e76bc3e22dd72a30a8a614d367246b03ff1312

(3) Redis4.0.11主從一致:

https://github.com/antirez/redis/commit/32a7a2c88a8b8cca8119b849eee7976b8ada8936

2、針對(duì)過(guò)期鍵的寫操作:

Redis2.8~4.0都只返回物理結(jié)果。

3、從庫(kù)中對(duì)key執(zhí)行expire操作,key不會(huì)過(guò)期。

Redis4.0 rc3解決從庫(kù)中設(shè)置的過(guò)期鍵不過(guò)期問(wèn)題 https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4

4、如果slave非讀寫分離、上述遷移使用,基本本文問(wèn)題不會(huì)出現(xiàn)。還有就是Redis 4非??孔V,后面也會(huì)有文章介紹相關(guān)內(nèi)容。(付磊)

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • 從原理到實(shí)踐分析?Redis?分布式鎖的多種實(shí)現(xiàn)方案

    從原理到實(shí)踐分析?Redis?分布式鎖的多種實(shí)現(xiàn)方案

    在分布式系統(tǒng)中,為了保證多個(gè)進(jìn)程或線程之間的數(shù)據(jù)一致性和正確性,需要使用鎖來(lái)實(shí)現(xiàn)互斥訪問(wèn)共享資源,然而,使用本地鎖在分布式系統(tǒng)中存在問(wèn)題,這篇文章主要介紹了從原理到實(shí)踐分析?Redis?分布式鎖的多種實(shí)現(xiàn)方案,需要的朋友可以參考下
    2024-07-07
  • RedisAPI原子性操作及原理解析

    RedisAPI原子性操作及原理解析

    這篇文章主要介紹了RedisAPI原子性操作及原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-12-12
  • Redis實(shí)現(xiàn)訂單自動(dòng)過(guò)期功能的示例代碼

    Redis實(shí)現(xiàn)訂單自動(dòng)過(guò)期功能的示例代碼

    這篇文章主要介紹了Redis實(shí)現(xiàn)訂單自動(dòng)過(guò)期功能的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • 基于session?Redis實(shí)現(xiàn)登錄

    基于session?Redis實(shí)現(xiàn)登錄

    這篇文章主要介紹了基于session?Redis實(shí)現(xiàn)登錄的相關(guān)資料,需要的朋友可以參考下
    2023-10-10
  • Redis和Nginx實(shí)現(xiàn)限制接口請(qǐng)求頻率的示例

    Redis和Nginx實(shí)現(xiàn)限制接口請(qǐng)求頻率的示例

    限流就是限制API訪問(wèn)頻率,當(dāng)訪問(wèn)頻率超過(guò)某個(gè)閾值時(shí)進(jìn)行拒絕訪問(wèn)等操作,本文主要介紹了Redis和Nginx實(shí)現(xiàn)限制接口請(qǐng)求頻率的示例,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • Redis?集群模式(Cluster)原理詳解

    Redis?集群模式(Cluster)原理詳解

    redis?cluster集群是一個(gè)由多個(gè)主從節(jié)點(diǎn)集群組成的分布式服務(wù)集群,它具有復(fù)制、高可用和分片特性,cluster集群不需要sentinel?哨兵也能完成節(jié)點(diǎn)移除和故障轉(zhuǎn)移的功能,本文就詳細(xì)的給大家介紹一下Redis?集群模式原理,感興趣的朋友跟著小編一起來(lái)看看吧
    2023-07-07
  • 一文詳解如何使用Redis實(shí)現(xiàn)分布式鎖

    一文詳解如何使用Redis實(shí)現(xiàn)分布式鎖

    這篇文章主要介紹了一文詳解如何使用Redis實(shí)現(xiàn)分布式鎖,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • redis中opsForList().range()的使用方法詳解

    redis中opsForList().range()的使用方法詳解

    這篇文章主要給大家介紹了關(guān)于redis中opsForList().range()的使用方法,文中通過(guò)實(shí)例代碼以及圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-03-03
  • 聊一聊redis奇葩數(shù)據(jù)類型與集群知識(shí)

    聊一聊redis奇葩數(shù)據(jù)類型與集群知識(shí)

    現(xiàn)在越來(lái)越多的項(xiàng)目都會(huì)利用到redis,多實(shí)例redis服務(wù)比單實(shí)例要復(fù)雜的多,這里面涉及到定位、容錯(cuò)、擴(kuò)容等技術(shù)問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于redis奇葩數(shù)據(jù)類型與集群知識(shí)的相關(guān)資料,需要的朋友可以參考下
    2022-01-01
  • Redis配置文件代碼講解

    Redis配置文件代碼講解

    在本篇文章里小編給大家整理的是一篇關(guān)于Redis配置文件的說(shuō)明內(nèi)容,需要的朋友們可以學(xué)習(xí)下。
    2020-03-03

最新評(píng)論