关键词搜索

源码搜索 ×
×

漫话Redis源码之六十二

发布2022-01-16浏览575次

详情内容

这里主要是讲述activeExpireCycle的实现,不是非常核心的内容。

  1. /* Helper function for the activeExpireCycle() function.
  2. * This function will try to expire the key that is stored in the hash table
  3. * entry 'de' of the 'expires' hash table of a Redis database.
  4. *
  5. * If the key is found to be expired, it is removed from the database and
  6. * 1 is returned. Otherwise no operation is performed and 0 is returned.
  7. *
  8. * When a key is expired, server.stat_expiredkeys is incremented.
  9. *
  10. * The parameter 'now' is the current time in milliseconds as is passed
  11. * to the function to avoid too many gettimeofday() syscalls. */
  12. int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
  13. long long t = dictGetSignedIntegerVal(de);
  14. if (now > t) {
  15. sds key = dictGetKey(de);
  16. robj *keyobj = createStringObject(key,sdslen(key));
  17. deleteExpiredKeyAndPropagate(db,keyobj);
  18. decrRefCount(keyobj);
  19. return 1;
  20. } else {
  21. return 0;
  22. }
  23. }
  24. /* Try to expire a few timed out keys. The algorithm used is adaptive and
  25. * will use few CPU cycles if there are few expiring keys, otherwise
  26. * it will get more aggressive to avoid that too much memory is used by
  27. * keys that can be removed from the keyspace.
  28. *
  29. * Every expire cycle tests multiple databases: the next call will start
  30. * again from the next db. No more than CRON_DBS_PER_CALL databases are
  31. * tested at every iteration.
  32. *
  33. * The function can perform more or less work, depending on the "type"
  34. * argument. It can execute a "fast cycle" or a "slow cycle". The slow
  35. * cycle is the main way we collect expired cycles: this happens with
  36. * the "server.hz" frequency (usually 10 hertz).
  37. *
  38. * However the slow cycle can exit for timeout, since it used too much time.
  39. * For this reason the function is also invoked to perform a fast cycle
  40. * at every event loop cycle, in the beforeSleep() function. The fast cycle
  41. * will try to perform less work, but will do it much more often.
  42. *
  43. * The following are the details of the two expire cycles and their stop
  44. * conditions:
  45. *
  46. * If type is ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a
  47. * "fast" expire cycle that takes no longer than ACTIVE_EXPIRE_CYCLE_FAST_DURATION
  48. * microseconds, and is not repeated again before the same amount of time.
  49. * The cycle will also refuse to run at all if the latest slow cycle did not
  50. * terminate because of a time limit condition.
  51. *
  52. * If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
  53. * executed, where the time limit is a percentage of the REDIS_HZ period
  54. * as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. In the
  55. * fast cycle, the check of every database is interrupted once the number
  56. * of already expired keys in the database is estimated to be lower than
  57. * a given percentage, in order to avoid doing too much work to gain too
  58. * little memory.
  59. *
  60. * The configured expire "effort" will modify the baseline parameters in
  61. * order to do more work in both the fast and slow expire cycles.
  62. */
  63. #define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */
  64. #define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds. */
  65. #define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
  66. #define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
  67. we do extra efforts. */
  68. void activeExpireCycle(int type) {
  69. /* Adjust the running parameters according to the configured expire
  70. * effort. The default effort is 1, and the maximum configurable effort
  71. * is 10. */
  72. unsigned long
  73. effort = server.active_expire_effort-1, /* Rescale from 0 to 9. */
  74. config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
  75. ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
  76. config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION +
  77. ACTIVE_EXPIRE_CYCLE_FAST_DURATION/4*effort,
  78. config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +
  79. 2*effort,
  80. config_cycle_acceptable_stale = ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE-
  81. effort;
  82. /* This function has some global state in order to continue the work
  83. * incrementally across calls. */
  84. static unsigned int current_db = 0; /* Next DB to test. */
  85. static int timelimit_exit = 0; /* Time limit hit in previous call? */
  86. static long long last_fast_cycle = 0; /* When last fast cycle ran. */
  87. int j, iteration = 0;
  88. int dbs_per_call = CRON_DBS_PER_CALL;
  89. long long start = ustime(), timelimit, elapsed;
  90. /* When clients are paused the dataset should be static not just from the
  91. * POV of clients not being able to write, but also from the POV of
  92. * expires and evictions of keys not being performed. */
  93. if (checkClientPauseTimeoutAndReturnIfPaused()) return;
  94. if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
  95. /* Don't start a fast cycle if the previous cycle did not exit
  96. * for time limit, unless the percentage of estimated stale keys is
  97. * too high. Also never repeat a fast cycle for the same period
  98. * as the fast cycle total duration itself. */
  99. if (!timelimit_exit &&
  100. server.stat_expired_stale_perc < config_cycle_acceptable_stale)
  101. return;
  102. if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
  103. return;
  104. last_fast_cycle = start;
  105. }
  106. /* We usually should test CRON_DBS_PER_CALL per iteration, with
  107. * two exceptions:
  108. *
  109. * 1) Don't test more DBs than we have.
  110. * 2) If last time we hit the time limit, we want to scan all DBs
  111. * in this iteration, as there is work to do in some DB and we don't want
  112. * expired keys to use memory for too much time. */
  113. if (dbs_per_call > server.dbnum || timelimit_exit)
  114. dbs_per_call = server.dbnum;
  115. /* We can use at max 'config_cycle_slow_time_perc' percentage of CPU
  116. * time per iteration. Since this function gets called with a frequency of
  117. * server.hz times per second, the following is the max amount of
  118. * microseconds we can spend in this function. */
  119. timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;
  120. timelimit_exit = 0;
  121. if (timelimit <= 0) timelimit = 1;
  122. if (type == ACTIVE_EXPIRE_CYCLE_FAST)
  123. timelimit = config_cycle_fast_duration; /* in microseconds. */
  124. /* Accumulate some global stats as we expire keys, to have some idea
  125. * about the number of keys that are already logically expired, but still
  126. * existing inside the database. */
  127. long total_sampled = 0;
  128. long total_expired = 0;
  129. for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
  130. /* Expired and checked in a single loop. */
  131. unsigned long expired, sampled;
  132. redisDb *db = server.db+(current_db % server.dbnum);
  133. /* Increment the DB now so we are sure if we run out of time
  134. * in the current DB we'll restart from the next. This allows to
  135. * distribute the time evenly across DBs. */
  136. current_db++;
  137. /* Continue to expire if at the end of the cycle there are still
  138. * a big percentage of keys to expire, compared to the number of keys
  139. * we scanned. The percentage, stored in config_cycle_acceptable_stale
  140. * is not fixed, but depends on the Redis configured "expire effort". */
  141. do {
  142. unsigned long num, slots;
  143. long long now, ttl_sum;
  144. int ttl_samples;
  145. iteration++;
  146. /* If there is nothing to expire try next DB ASAP. */
  147. if ((num = dictSize(db->expires)) == 0) {
  148. db->avg_ttl = 0;
  149. break;
  150. }
  151. slots = dictSlots(db->expires);
  152. now = mstime();
  153. /* When there are less than 1% filled slots, sampling the key
  154. * space is expensive, so stop here waiting for better times...
  155. * The dictionary will be resized asap. */
  156. if (slots > DICT_HT_INITIAL_SIZE &&
  157. (num*100/slots < 1)) break;
  158. /* The main collection cycle. Sample random keys among keys
  159. * with an expire set, checking for expired ones. */
  160. expired = 0;
  161. sampled = 0;
  162. ttl_sum = 0;
  163. ttl_samples = 0;
  164. if (num > config_keys_per_loop)
  165. num = config_keys_per_loop;
  166. /* Here we access the low level representation of the hash table
  167. * for speed concerns: this makes this code coupled with dict.c,
  168. * but it hardly changed in ten years.
  169. *
  170. * Note that certain places of the hash table may be empty,
  171. * so we want also a stop condition about the number of
  172. * buckets that we scanned. However scanning for free buckets
  173. * is very fast: we are in the cache line scanning a sequential
  174. * array of NULL pointers, so we can scan a lot more buckets
  175. * than keys in the same time. */
  176. long max_buckets = num*20;
  177. long checked_buckets = 0;
  178. while (sampled < num && checked_buckets < max_buckets) {
  179. for (int table = 0; table < 2; table++) {
  180. if (table == 1 && !dictIsRehashing(db->expires)) break;
  181. unsigned long idx = db->expires_cursor;
  182. idx &= db->expires->ht[table].sizemask;
  183. dictEntry *de = db->expires->ht[table].table[idx];
  184. long long ttl;
  185. /* Scan the current bucket of the current table. */
  186. checked_buckets++;
  187. while(de) {
  188. /* Get the next entry now since this entry may get
  189. * deleted. */
  190. dictEntry *e = de;
  191. de = de->next;
  192. ttl = dictGetSignedIntegerVal(e)-now;
  193. if (activeExpireCycleTryExpire(db,e,now)) expired++;
  194. if (ttl > 0) {
  195. /* We want the average TTL of keys yet
  196. * not expired. */
  197. ttl_sum += ttl;
  198. ttl_samples++;
  199. }
  200. sampled++;
  201. }
  202. }
  203. db->expires_cursor++;
  204. }
  205. total_expired += expired;
  206. total_sampled += sampled;
  207. /* Update the average TTL stats for this database. */
  208. if (ttl_samples) {
  209. long long avg_ttl = ttl_sum/ttl_samples;
  210. /* Do a simple running average with a few samples.
  211. * We just use the current estimate with a weight of 2%
  212. * and the previous estimate with a weight of 98%. */
  213. if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
  214. db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
  215. }
  216. /* We can't block forever here even if there are many keys to
  217. * expire. So after a given amount of milliseconds return to the
  218. * caller waiting for the other active expire cycle. */
  219. if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
  220. elapsed = ustime()-start;
  221. if (elapsed > timelimit) {
  222. timelimit_exit = 1;
  223. server.stat_expired_time_cap_reached_count++;
  224. break;
  225. }
  226. }
  227. /* We don't repeat the cycle for the current database if there are
  228. * an acceptable amount of stale keys (logically expired but yet
  229. * not reclaimed). */
  230. } while (sampled == 0 ||
  231. (expired*100/sampled) > config_cycle_acceptable_stale);
  232. }
  233. elapsed = ustime()-start;
  234. server.stat_expire_cycle_time_used += elapsed;
  235. latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
  236. /* Update our estimate of keys existing but yet to be expired.
  237. * Running average with this sample accounting for 5%. */
  238. double current_perc;
  239. if (total_sampled) {
  240. current_perc = (double)total_expired/total_sampled;
  241. } else
  242. current_perc = 0;
  243. server.stat_expired_stale_perc = (current_perc*0.05)+
  244. (server.stat_expired_stale_perc*0.95);
  245. }

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载