redis做为缓存场景使用,内存耗尽时,忽然出现大量的逐出,在这个逐出的过程当中阻塞正常的读写请求,致使 redis 短期不可用;git
redis 中的LRU是如何实现的?github
逐出qps突增很是大的缘由:一次须要逐出释放太多的空间会致使阻塞;具体的缘由是 mem_tofree 的计算逻辑有问题;
mem_tofree 统计的是:实际已分配的内存总量 - AOF 缓冲区相关的内存;
若是这时候有rehash,会临时分配一个桶来作rehash,这部份内存未排除,因此在rehash阶段,算出来的mem_tofree 就会很大,形成一个时刻须要逐出大量的key,逐出的loop是阻塞的,这个阶段会block redis的请求;redis
逐出qps的计算:算法
freeMemoryIfNeeded(...) // 计算出 Redis 目前占用的内存总数,但有两个方面的内存不会计算在内: // 1)从服务器的输出缓冲区的内存 // 2)AOF 缓冲区的内存 // 3)AOF 重写缓冲区中的内存 mem_used = zmalloc_used_memory(); if (slaves) { listIter li; listNode *ln; listRewind(server.slaves,&li); while((ln = listNext(&li))) { redisClient *slave = listNodeValue(ln); unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave); if (obuf_bytes > mem_used) mem_used = 0; else mem_used -= obuf_bytes; } } if (server.aof_state != REDIS_AOF_OFF) { mem_used -= sdslen(server.aof_buf); mem_used -= aofRewriteBufferSize(); } // 计算须要释放多少字节的内存 mem_tofree = mem_used - server.maxmemory; propagateExpire(db,keyobj); // 计算删除键所释放的内存数量 delta = (long long) zmalloc_used_memory(); dbDelete(db,keyobj); delta -= (long long) zmalloc_used_memory(); mem_freed += delta; // 对淘汰键的计数器增一 server.stat_evictedkeys++;
github上 @Rosanta 给出的解决方案:释放内存的循环逻辑中最多执行必定次数,达到阈值了就再也不逐出,到下个请求来时再释放一点空间;这个方案的好处是不会 block 整个进程,正常的业务读写请求无影响;潜在问题是可能单次写入的数据比释放的空间还大,致使总的内存是一直上升,而不是降低;缓存
@antirez 给的方案:一样是迭代删除,但会加个标志,保证在迭代删除的逻辑下内存是逐渐降低的,而若是是上升的,仍是会block住正常的请求(要控制主总的内存大小);
详见:
https://github.com/antirez/redis/pull/4583服务器
关于 redis 4.0的逐出算法优化
http://antirez.com/news/109函数