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 }

猜你喜欢

转载自www.cnblogs.com/lionels/p/10097926.html