“叮……”,美好的周六就这么被一阵钉钉消息吵醒了 。
业务组的同学告诉我说很多用户的帐号今天被强制下线 。我们的帐号系统正常的逻辑是用户登录一次后,token的有效期可以维持一天的时间 。现在的问题是用户大概每10分钟左右就需要重新登录一次 。这种情况一般有两种原因:
- 1、token生成时出问题 。
- 2、验证token时出现问题 。
于是又去检查了Redis的监控,发现在那段时间Redis由于内存占用过高强制清理了几次key 。但从日志上来看,这段时间并没有出现流量暴涨的情况,而且Redis中key的数量也没有显著增加 。那是什么原因导致Redis内存占用过高呢?确定了Redis内存升高不是我们造成的之后,我们又联系了业务组的同学协助他们,他们表示最近确实有上线,并且新上线的功能有使用到Redis 。但我仍然感觉很奇怪,为什么Redis中的key没有增多,并且没看到有其他业务的key 。经过一番询问,才了解到,业务组同学使用的是这个Redis的db1,而我用的(和刚查的)是db0 。这里确实是我在排查问题时出现了疏忽 。
那么Redis的不同db之间会互相影响吗?通常情况下,我们使用不同的db进行数据隔离,这没问题 。但Redis进行清理时,并不是只清理数据量占用最大的那个db,而是会对所有的db进行清理 。在这之前我并不是很了解这方面知识,这里也只是根据现象进行的猜测 。
好奇心驱使我来验证一下这个想法 。于是我决定直接来看Redis的源码 。清理key相关的代码在evict.c文件中 。
Redis中会保存一个“过期key池”,这个池子中存放了一些可能会被清理的key 。其中保存的数据结构如下:
struct evictionPoolEntry {unsigned long long idle;/* Object idle time (inverse frequency for LFU) */sds key;/* Key name. */sds cached;/* Cached SDS object for key name. */int dbid;/* Key DB number. */};其中idle是对象空闲时间,在Reids中,key的过期算法有两种:一种是近似LRU,一种是LFU 。默认使用的是近似LRU 。近似LRU在解释近似LRU之前,先来简单了解一下LRU 。当Redis的内存占用超过我们设置的maxmemory时,会把长时间没有使用的key清理掉 。按照LRU算法,我们需要对所有key(也可以设置成只淘汰有过期时间的key)按照空闲时间进行排序,然后淘汰掉空闲时间最大的那部分数据,使得Redis的内存占用降到一个合理的值 。
LRU算法的缺点是,我们需要维护一个全部(或只有过期时间)key的列表,还要按照最近使用时间排序 。这会消耗大量内存,并且每次使用key时更新排序也会占用额外的CPU资源 。对于Redis这样对性能要求很高的系统来说是不被允许的 。
因此,Redis采用了一种近似LRU的算法 。当Redis接收到新的写入命令,而内存又不够时,就会触发近似LRU算法来强制清理一些key 。具体清理的步骤是,Redis会对key进行采样,通常是取5个,然后会把过期的key放到我们上面说的“过期池”中,过期池中的key是按照空闲时间来排序的,Redis会优先清理掉空闲时间最长的key,直到内存小于maxmemory 。
近似LRU算法的清理效果图如图(图片来自Redis官方文档)

文章插图
这么说可能不够清楚,我们直接上代码 。
源码分析

文章插图
上图展示了代码中近似LRU算法的主要逻辑调用路径 。
其中主要逻辑是在freeMemoryIfNeeded函数中
首先调用getMaxmemoryState函数判断当前内存的状态
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level) {size_t mem_reported, mem_used, mem_tofree;mem_reported = zmalloc_used_memory();if (total) *total = mem_reported;int return_ok_asap = !server.maxmemory || mem_reported <= server.maxmemory;if (return_ok_asap && !level) return C_OK;mem_used = mem_reported;size_t overhead = freeMemoryGetNotCountedMemory();mem_used = (mem_used > overhead) ? mem_used-overhead : 0;if (level) {if (!server.maxmemory) {*level = 0;} else {*level = (float)mem_used / (float)server.maxmemory;}}if (return_ok_asap) return C_OK;if (mem_used <= server.maxmemory) return C_OK;mem_tofree = mem_used - server.maxmemory;if (logical) *logical = mem_used;if (tofree) *tofree = mem_tofree;return C_ERR;}如果使用内存低于maxmemory的话,就返回C_OK,否则返回C_ERR 。另外,这个函数还通过传递指针型的参数来返回一些额外的信息 。
推荐阅读
- 如何开一个代购网店 怎样开淘宝店铺代卖
- 干玫瑰花如何喝,茉莉花茶如何喝
- vscode如何优雅的拥抱eslint
- 淘宝联盟怎么推广赚钱技巧 淘宝联盟如何做推广
- 网上如何找到更多的高清图片素材?这6个免版权图库收藏好
- 如何运营10个500人大群?
- 怎样开农村淘宝店 如何开农村淘宝网店
- 粉玫瑰花茶如何泡茶喝,孕妇能喝玫瑰花茶吗
- 华美月饼档次如何?
- 淘宝测图用精准还是广泛 淘宝如何测图测款
