redis.c/exitFromChild函数:
1 void exitFromChild(int retcode) { 2 #ifdef COVERAGE_TEST 3 exit(retcode); 4 #else 5 _exit(retcode); 6 #endif 7 }
分了两种情况考虑,如果是执行完RDB dump, AOF rewrite这类退出操作,为防止影响到父进程文件(父进程子进程共享文件描述符, exit函数会刷新子进程拷贝的stdio缓冲区副本),所以调用_exit()函数
但如果是在做coverage test, 为了得到正确的coverage信息,那就应该调用exit()来先执行退出处理程序,刷新stdio流缓冲区,再终止进程
关于exit()与_exit()区别,更具体的信息可参考《The Linux Programming Interface》chapter25.4 以及这个stackoverflow上的问题
http://stackoverflow.com/questions/5422831/what-is-the-difference-between-using-exit-exit-in-a-conventional-linux-fo
redis.c/activeExpireCycle函数:
1 void activeExpireCycle(void) { 2 int j, iteration = 0; 3 long long start = ustime(), timelimit; 4 5 /* We can use at max REDIS_EXPIRELOOKUPS_TIME_PERC percentage of CPU time 6 * per iteration. Since this function gets called with a frequency of 7 * REDIS_HZ times per second, the following is the max amount of 8 * microseconds we can spend in this function. */ 9 10 timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/REDIS_HZ/100; 11 if (timelimit <= 0) timelimit = 1; 12 13 for (j = 0; j < server.dbnum; j++) { 14 int expired; 15 redisDb *db = server.db+j; 16 17 /* Continue to expire if at the end of the cycle more than 25% 18 * of the keys were expired. */ 19 do { 20 unsigned long num = dictSize(db->expires); 21 unsigned long slots = dictSlots(db->expires); 22 long long now = mstime(); 23 24 /* When there are less than 1% filled slots getting random 25 * keys is expensive, so stop here waiting for better times... 26 * The dictionary will be resized asap. */ 27 // 过期字典里只有 %1 位置被占用,调用随机 key 的消耗比较高 28 // 等 key 多一点再来 29 if (num && slots > DICT_HT_INITIAL_SIZE && 30 (num*100/slots < 1)) break; 31 32 /* The main collection cycle. Sample random keys among keys 33 * with an expire set, checking for expired ones. */ 34 // 从过期字典中随机取出 key ,检查它是否过期 35 expired = 0; // 被删除 key 计数 36 if (num > REDIS_EXPIRELOOKUPS_PER_CRON) // 最多每次可查找的次数 37 num = REDIS_EXPIRELOOKUPS_PER_CRON; 38 while (num--) { 39 dictEntry *de; 40 long long t; 41 42 // 随机查找带有 TTL 的 key ,看它是否过期 43 // 如果数据库为空,跳出 44 if ((de = dictGetRandomKey(db->expires)) == NULL) break; 45 46 t = dictGetSignedIntegerVal(de); 47 if (now > t) { 48 // 已过期 49 sds key = dictGetKey(de); 50 robj *keyobj = createStringObject(key,sdslen(key)); 51 52 propagateExpire(db,keyobj); 53 dbDelete(db,keyobj); 54 decrRefCount(keyobj); 55 expired++; 56 server.stat_expiredkeys++; 57 } 58 } 59 /* We can't block forever here even if there are many keys to 60 * expire. So after a given amount of milliseconds return to the 61 * caller waiting for the other active expire cycle. */ 62 // 每次进行 16 次循环之后,检查时间是否超过,如果超过,则退出 63 iteration++; 64 if ((iteration & 0xf) == 0 && /* check once every 16 cycles. */ 65 (ustime()-start) > timelimit) return; 66 67 } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4); 68 } 69 }
此函数作为cron函数,按照源码设定,需要在0.0025秒的时间内尽可能多的清除过期键,所以函数执行的效率极为关键。
这里面,如第64行所示, 对每个数据库检查过期键,在每经过16次循环后,需要检查时间是否超时,设iteration为循环计数
我的第一想法,是if(iteration%16 == 0), 但redis却使用了 if((iteration & 0xf) == 0) 的位运算方法,为什么要用这么“装逼”的写法呢?
究其原因,是因为位运算远快于取余运算,在处理高并发场景时,函数执行的时间尤为关键,而此函数作为cron函数,需要在0.0025秒的时间内尽可能多的清除过期键,
采取这种位运算方式,自然节省了大量时间用于查找和清除过期键。这也提示我们,在对运行时间要求十分苛刻的场合,要善用位运算。