81

Redis 内存管理赏析

 5 years ago
source link: https://mp.weixin.qq.com/s/fHBLuN9218FS0Y9YTmORaQ?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

声明:本文是对 4.0.6版本 Redis的 内存管理部分的(xuexi)总结,有些YY的成分,作者本意不想误导, 如有错误, 敬请谅解。

一、凡事先问个为什么

Redis是互联网公司主流的分布式缓存解决方案,缓存的本质是“ 就近暂存 ”,解决的是“ 减少去较远的地方拿我想要的东西要耗费较大成本 ”的问题。本小节我们要探究的不是为什么要用缓存,而是Redis为什么要做内存管理这件事情。决定一件事情做不做的理由 绝对不是 “这件事情可以做”,我认为Redis做内存管理的主要原因是: 内存(RAM)资源宝贵,内存管理让内存的使用情况可控

二、遵循底线、条条大路通罗马

那么怎么做内存管理呢?管理包括对个体的管理和对总体的管理,个体上比如对Redis中存储的每个对象的大小,质量等进行管理;Redis的做法遵循 简单原则 ,在内存使用的总量上划一道“ 允许最大使用内存量 ”这条底线。为了遵循这条底线,Redis在实现上是这么做的:当使用内存量高于这条底线的时候,所有的写操作将会直接失败,绝不手软,但是这个时候仍然理智地支持读操作。在底线的基础之上Redis提供的管理手段是“淘汰机制”,注意 淘汰机制是管理的一种手段 。那么怎么淘汰呢?按大小?按年龄?Redis提供以下几种选择淘汰KEY的策略:

LRU: 看看哪个KEY最长时间没有被使用啦,就选你吧,你看你这么长时间都没被使用了,就别站着地方了。

LFU: 如果说上面的方法较为片面,那这里就加上一个期限,看看哪个KEY在一段时间内访问的频率最低好吧,谁最低,淘汰谁。

RANDOM: 不想那么多了,整个世界都是随机的,随机的世界就随机选择一个KEY吧,爱咋咋地。

TTL: 选择最近即将过期的KEY进行淘汰。

最后一种比较有意思,那就是—— 不淘汰 ,哈哈, 对于一个问题不去解决往往也是一种解决方案

三、难易相成、长短相形

在主从模式且主库开启maxmemory策略的情况下,对于与主库来说,如果用于从库同步的缓冲区也被作为内存使用量计算进来,在某些极端情况下,比如:主从之间网络出现异常,这个时候内存使用又达到阈值,显然Redis此时会进行eviction操作,eviction操作本身会产生大量的DELs操作日志用于同步给从库,这部分日志有会填充在主从同步缓存中,由于这部分内存会被计算进内存使用量,因此又会触发eviction操作,然后继续恶性循环……,直至最后内存被清空。 引入一种方案时长会伴随着引入新的问题和忧患,这需要我们提高警惕 ,那么怎么解决这个问题呢?Redis的做法很简单,就是用于从库同步的缓冲区内存大小不作为使用内存计算的,这样就不会存在上述问题了。在实际使用中,当以主从模式使用Redis且开启了maxmemory策略的时候,建议maxmemory这个值设置稍微小一些,预留一部分给主动同步缓冲区使用。

四、损有余、补不足

在实际实现LRU、LFU和最小TTL算法的时候面临一些问题,比如LRU算法要求淘汰最长时间没有使用的KEY,如果要精准满足“ 最长时间没有使用 ”这个条件,我们必须要遍历所有的KEY然后才能根据其上次使用的时间计算出哪个KEY是最长时间没有被访问的。问题来了,实际上Redis中可能存在成千上万个KEY,每次都要挨个遍历岂不是要疯了? 实际想一想精确真的是我们想要的吗? 实际Redis实现的LRU、LFU和最小TTL算法都是基于近似算法计算的,用 精确度换时间 (扩展一下hyperloglog是用准确性换空间),关键算法是:默认情况下Redis每次只随机选择5(可以通过maxmemory-samples参数设置,maxmemory-samples越大的时候越消耗内存,maxmemory-samples越小的时候越快,但是越不精确)个KEY,然后从这5个KEY中找到最符合条件的,这个思想其实也类似选择择局部最优,放弃全局最优的贪心算法。

五、精益求精、追求卓越

无论是LRU、LFU还是TTL都是基于系统时间的,在一般操作系统中,读取系统的当前时间需要进行system call,system call意味着操作系统需要陷入内核态,陷入内核态意味着用户态与内核态的上下文切换,上下文切换意味着成本……。为了实现内存淘汰,Redis需要为每个KEY都维护着一个这样的时间,而且在KEY被访问的时候需要读取操作系统当前时间将其更新,试想一下,在上W QPS的应用场景下,每次都要去system call在高性能的场景下是一件多么恐怖的事情。实际Redis是怎么处理的呢?这里有个背景,Redis所谓的单线程指的是Redis对于客户端读写的处理和内部的一些日常事务处理都是由一个eventloop线程来处理的,这里的日常事务包括一项:每次读取当前系统时间记录在一个变量里面。一般情况Redis所读取的时间都是从这里读取的,也就是由于eventloop线程提前计算好的,当然如果eventloop的loop频率比较低的话这个值将会具有一定延迟。

qmUNNf3.jpg!web

qQzA3in.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK