无码a片

阅读 Redis 源码,学习缓存淘汰算法 W

发布日期:2022-06-18 17:09    点击次数:124

阅读 Redis 源码,学习缓存淘汰算法 W

 

文末本文转载自微信公众号「董泽润的技艺条记」,作家董泽润  。转载本文请臆度董泽润的技艺条记公众号。

统统 IT 从业者都战争过缓存,一定了解基本责任旨趣,业界流行一句话:缓存便是万金油,那处有问题那处抹一下。那他的施行是什么呢?

上图代表从 cpu 到底层硬盘不同脉络,不同模块的运行速率,表层多加一层 cache, 就能惩办基层的速率慢的问题,这里的慢是指两点:IO 慢和 cpu 访佛规画缓存中间端正

可是 cache 受限于资本,cache size 一般都是固定的,是以数据需要淘汰,由此引出一系列其它问题:缓存一致性、击穿、雪崩、稠浊等等,本文通过阅读 redis 源码,学习主流淘汰算法

如若不是 leetcode 146 LRU[1] 刷题需要,我想全球也不会手写 cache, 节略的已矣和工程践诺相距十万八千里,真确 production ready 的缓存库终点历练细节

Redis 缓存淘汰设立

一般 redis 不建义当成存储使用,只允许作为 cache, 并诱骗 max-memory, 当内存使用达到最大值时,redis-server 会凭证不同设立出手删除 keys. Redis 从 4.0 版块引进了 LFU[2], 即 Least Frequently Used,4.0 已往默许使用 LRU 即 Least Recently Used

volatile-lru 只针对诱骗 expire 落伍的 key 进行 lru 淘汰 allkeys-lru 对统统的 key 进行 lru 淘汰 volatile-lfu 只针对诱骗 expire 落伍的 key 进行 lfu 淘汰 allkeys-lfu 对统统的 key 进行 lfu 淘汰 volatile-random 只针对诱骗 expire 落伍的进行随即淘汰 allkeys-random 统统的 key 随即淘汰 volatile-ttl 淘汰 ttl 落伍时分最小的 key noeviction 什么都不做,如若此时内存已满,系统无法写入

默许战术是 noeviction, 也便是不驱散,此时如若写满,系统无法写入,建义诱骗为 LFU 臆度的。LRU 优先淘汰最近未被使用,无法应答冷数据,比如热 keys 短时分莫得看望, 小12萝8禁在线喷水观看就会被只使用一次的冷数据冲掉,无法反映竟然的使用情况

LFU 能幸免上述情况,可是朴素 LFU 已矣无法应答突发流量,无法驱散历史热 keys,是以 redis LFU 已矣类似于 W-TinyLFU[3], 其中 W 是 windows 的旨趣,即一定时分窗口后对频率进行减半,如若不减的话,cache 就成了对历史数据的统计,而不是缓存

上头还提到突发流量如若应答呢?谜底是给新看望的 key 一个运行频率值,不至于由于运行值为 0 无法更新频率

LRU 已矣
int processCommand(redisClient *c) {     ......     /* Handle the maxmemory directive.      *      * First we try to free some memory if possible (if there are volatile      * keys in the dataset). If there are not the only thing we can do      * is returning an error. */     if (server.maxmemory) {         int retval = freeMemoryIfNeeded();         if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {             flagTransaction(c);             addReply(c, shared.oomerr);             return REDIS_OK;         }     }     ...... } 

在每次处理 client 号召时都会调用 freeMemoryIfNeeded 检查是否有必有驱散某些 key, 当 redis 施行使用内存达到上限时出手淘汰。可是 redis 做的比拟取巧,并莫得对统统的 key 做 lru 队伍,而是按照 maxmemory_samples 参数进行采样,系统默许是 5 个 key

上头是很经典的一个图,jk制服白丝自慰无码自慰网站当到达 10 个 key 时恶果更接近表面上的 LRU 算法,可是 cpu 蹧跶会变高,是以系统默许值就够了。

LFU 已矣
robj *lookupKey(redisDb *db, robj *key, int flags) {     dictEntry *de = dictFind(db->dict,key->ptr);     if (de) {         robj *val = dictGetVal(de);          /* Update the access time for the ageing algorithm.          * Don't do it if we have a saving child, as this will trigger          * a copy on write madness. */         if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){             if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {                 updateLFU(val);             } else {                 val->lru = LRU_CLOCK();             }         }         return val;     } else {         returnNULL;     } } 

当 lookupKey 看望某 key 时,会更新 LRU. 从 redis 4.0 出手迟缓引入了 LFU 算法,由于复用了 LRU 字段,是以只可使用 24 bits

* We split the 24 bits into two fields: * *     16 bits      8 bits * +----------------+--------+ * + Last decr time | LOG_C  | * +----------------+--------+ 

其中低 8 位 counter 用于计数频率,取值为从 0~255, 可是经由取对数的,是以不错暗意很大的看望频率

高 16 位 ldt (Last Decrement Time)暗意终末一次看望的 miniutes 时分戳, 用于衰减 counter 值,如若 counter 不衰减的话就酿成了对历史 key 看望次数的统计了,而不是 LFU

unsigned long LFUTimeElapsed(unsigned long ldt) {     unsigned long now = LFUGetTimeInMinutes();     if (now >= ldt) return now-ldt;     return 65535-ldt+now; } 

雅致由于 ldt 只用了 16位计数,最大值 65535,是以会出现回卷 rewind

LFU 取得己有计数
 * counter of the scanned objects if needed. */ unsigned long LFUDecrAndReturn(robj *o) {     unsigned long ldt = o->lru >> 8;     unsigned long counter = o->lru & 255;     unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;     if (num_periods)         counter = (num_periods > counter) ? 0 : counter - num_periods;     return counter; } 

num_periods 代表规画出来的待衰减计数,lfu_decay_time 代表衰减悉数,默许值是 1,如若 lfu_decay_time 大于 1 衰延缓率会变得很慢

终末复返的计数值为衰减之后的,也有可能是 0

LFU 计数更新并取对数
/* Logarithmically increment a counter. The greater is the current counter value  * the less likely is that it gets really implemented. Saturate it at 255. */ uint8_t LFULogIncr(uint8_t counter) {     if (counter == 255) return 255;     double r = (double)rand()/RAND_MAX;     double baseval = counter - LFU_INIT_VAL;     if (baseval < 0) baseval = 0;     double p = 1.0/(baseval*server.lfu_log_factor+1);     if (r < p) counter++;     return counter; } 

计数跨越 255, 就无谓算了,班师复返即可。LFU_INIT_VAL 是运行值,默许是 5

如若减去运行值后 baseval 小于 0 了,讲解快落伍了,就更倾向于递加 counter 值

double p = 1.0/(baseval*server.lfu_log_factor+1); 

这个概率算法中 lfu_log_factor 是对数底,默许是 10, 当 counter 值较小时自增的概率较大,如若 counter 较大,倾向于不做任何操作

counter 值从 0~255 不错暗意很大的看望频率,饱和用了

# +--------+------------+------------+------------+------------+------------+ # | factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   | # +--------+------------+------------+------------+------------+------------+ # | 0      | 104        | 255        | 255        | 255        | 255        | # +--------+------------+------------+------------+------------+------------+ # | 1      | 18         | 49         | 255        | 255        | 255        | # +--------+------------+------------+------------+------------+------------+ # | 10     | 10         | 18         | 142        | 255        | 255        | # +--------+------------+------------+------------+------------+------------+ # | 100    | 8          | 11         | 49         | 143        | 255        | # +--------+------------+------------+------------+------------+------------+ 

基于这个特点,咱们就不错用 redis-cli --hotkeys 号召,来稽察系统中的最近一段时分的热 key, 终点实用。老版块中是没这个功能的,需要人工统计

$ redis-cli --hotkeys # Scanning the entire keyspace to find hot keys as well as # average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec # per 100 SCAN commands (not usually needed). ...... [47.62%] Hot key 'key17' found so far with counter 6 [57.14%] Hot key 'key43' found so far with counter 7 [57.14%] Hot key 'key14' found so far with counter 6 [85.71%] Hot key 'key42' found so far with counter 7 [85.71%] Hot key 'key45' found so far with counter 8 [95.24%] Hot key 'key50' found so far with counter 7  -------- summary -------  Sampled 105 keys in the keyspace! hot key found with counter: 7 keyname: key40 hot key found with counter: 7 keyname: key42 hot key found with counter: 7 keyname: key50 
谈谈缓存的规画

前边提到的是 redis LFU 已矣,这是聚合式的缓存,咱们还有好多程度的土产货缓存。怎样评价一个缓存已矣的利害,有好多规画,细节更紧迫

浑沌量:常说的 QPS, 对标 bucket 已矣的 hashmap 复杂度是 O(1), 缓存复杂度要高一些,还有锁竞争要处理,总之缓存库已矣的效能要高

缓存掷中率:光有浑沌量还不够,缓存掷中率也终点要道,掷中率越高讲解引入缓存作用越大

 

高档特点:缓存规画统计,怎样应答缓存击穿等等

 



栏目分类



Powered by 无码a片 @2013-2022 RSS地图 HTML地图