1. 程式人生 > >redis master-slave key過期原理分析

redis master-slave key過期原理分析

我們線上redis做的是讀寫分離  master寫 slave讀,  我們發現master中的Key已經過期30分鐘以上,但是slave上的TTL為0 (-1永久,-2不存在)。master上已經不存在了 slave還能查出來。  這屬於髒資料的範疇了,在很多及時性的場景下 出現這種方式是很致命的  接下來 我們分析一下 其中的原理

redis主從同步流程圖如下

總結 :當master的key過期時候,slave不會自動過期,master 會模擬一條del命令傳送給slave 。

 

問題:如果正常的執行上述邏輯,主從同步應該是一致的。但是為什麼slave 沒有同步到這個操作呢。接下來分析一下redis的過期策略

 

redis的過期策略

redis過期Key清理的機制對清理的頻率和最大時間都有限制,在儘量不影響正常服務的情況下,進行過期Key的清理,以達到長時間服務的效能最優.

Redis會週期性的隨機測試一批設定了過期時間的key並進行處理。測試到的已過期的key將被刪除。具體的演算法如下:

  1. Redis配置項hz定義了serverCron任務的執行週期,預設為10,即CPU空閒時每秒執行10次;
  2. 每次過期key清理的時間不超過CPU時間的25%,即若hz=1,則一次清理時間最大為250ms,若hz=10,則一次清理時間最大為25ms;
  3. 清理時依次遍歷所有的db;
  4. 從db中隨機取20個key,判斷是否過期,若過期,則逐出;
  5. 若有5個以上key過期,則重複步驟4,否則遍歷下一個db;
  6. 在清理過程中,若達到了25%CPU時間,退出清理過程;

 

最後結論: 1.在已經過期的情況下 由於master負載過大,在一個CPU視窗內隨機挑選一部分key檢測 ,這裡很有可能過期的key太多沒有被挑選著。同時有過期資源佔用的限制 可能被挑選中了以後 因為佔用資源超過25%退出

     2.不排除因為網路原因導致slave沒有接受到del命令

 

 

附上過期策略原始碼

 1 void activeExpireCycle(int type) {
 2     ...
3 /* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time 4 * per iteration. Since this function gets called with a frequency of 5 * server.hz times per second, the following is the max amount of 6 * microseconds we can spend in this function. */ 7 // 最多允許25%的CPU時間用於過期Key清理 8 // 若hz=1,則一次activeExpireCycle最多隻能執行250ms 9 // 若hz=10,則一次activeExpireCycle最多隻能執行25ms 10 timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100; 11 ... 12 // 遍歷所有db 13 for (j = 0; j < dbs_per_call; j++) { 14 int expired; 15 redisDb *db = server.db+(current_db % server.dbnum); 16 17 /* Increment the DB now so we are sure if we run out of time 18 * in the current DB we'll restart from the next. This allows to 19 * distribute the time evenly across DBs. */ 20 current_db++; 21 22 /* Continue to expire if at the end of the cycle more than 25% 23 * of the keys were expired. */ 24 do { 25 ... 26 // 一次取20個Key,判斷是否過期 27 if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) 28 num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP; 29 30 while (num--) { 31 dictEntry *de; 32 long long ttl; 33 34 if ((de = dictGetRandomKey(db->expires)) == NULL) break; 35 ttl = dictGetSignedIntegerVal(de)-now; 36 if (activeExpireCycleTryExpire(db,de,now)) expired++; 37 } 38 39 if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */ 40 long long elapsed = ustime()-start; 41 latencyAddSampleIfNeeded("expire-cycle",elapsed/1000); 42 if (elapsed > timelimit) timelimit_exit = 1; 43 } 44 if (timelimit_exit) return; 45 /* We don't repeat the cycle if there are less than 25% of keys 46 * found expired in the current DB. */ 47 // 若有5個以上過期Key,則繼續直至時間超過25%的CPU時間 48 // 若沒有5個過期Key,則跳過。 49 } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4); 50 } 51 }