我们线上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将被删除。具体的算法如下:
- Redis配置项hz定义了serverCron任务的执行周期,默认为10,即CPU空闲时每秒执行10次;
- 每次过期key清理的时间不超过CPU时间的25%,即若hz=1,则一次清理时间最大为250ms,若hz=10,则一次清理时间最大为25ms;
- 清理时依次遍历所有的db;
- 从db中随机取20个key,判断是否过期,若过期,则逐出;
- 若有5个以上key过期,则重复步骤4,否则遍历下一个db;
- 在清理过程中,若达到了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 }