命中率计算方式
使用redis管理的 info
命令:
# Stats
total_connections_received:24548538
total_commands_processed:61226225746
instantaneous_ops_per_sec:4410
total_net_input_bytes:5714615648992
total_net_output_bytes:19327841733488
instantaneous_input_kbps:340.53
instantaneous_output_kbps:1349.35
rejected_connections:0
sync_full:2
sync_partial_ok:0
sync_partial_err:0
expired_keys:1156849643
evicted_keys:0
keyspace_hits:54422405751
keyspace_misses:2707567737
pubsub_channels:1
pubsub_patterns:0
latest_fork_usec:395856
keyspace_hits
表示命中次数, keyspace_misses
表示未命中次数。
所以,命中率=keyspace_hits/(keyspace_hits+keyspace_misses)
上面系统的命中率也就是 54422405751/(54422405751+2707567737)=95%
命中率多少合适?
越高越好,倾向于95%以上。
哪些影响命中率?
1、业务场景,是读多写少还是读少写多。
读多写少,适合用缓存。
2、过期时间和缓存粒度的设定。
过期时间设置过短时,缓存命中的可能就会变小。 理论上,缓存的粒度越小,命中率越高。比如 缓存一个用户的一个工作地址信息,和缓存一个用户所有的工作地址信息列表相比,一个工作地址这种粒度的命中率会更高,因为更新的可能性小。但是对于一个列表而言,其中的一个工作地址变更,缓存信息就需要跟着变更,导致命中率就会下降。
3、容量的大小及失效策略配置
maxmemory
: 表示当前实例的最大内存容量。(maxmemory_human) 32位系统,最大支持4G。当设置为0的时候(不限制大小),默认最大内存是3G,防止redis崩溃。
当值为0的时候,表示不限制。
server.c
maxmemory_policy
: 到达最大内容的时候的策略。
1.volatile-lru
(least recently used):最近最少使用算法,从设置了过期时间的键中选择空转时间最长的键值对清除掉.
2.volatile-tt
l:从设置了过期时间的键中选择过期时间最早的键值对清除;
3.volatile-random
:从设置了过期时间的键中,随机选择键进行清除;
4.allkeys-lru
:最近最少使用算法,从所有的键中选择空转时间最长的键值对清除;
5.allkeys-random
:所有的键中,随机选择键进行删除;
6.noeviction:
不做任何的清理工作,在redis的内存超过限制之后,大部分的写入操作(DEL除外,因为DEL不会造成内存增长)都会返回错误;但是读操作都能正常的进行;
PS: volatile和allkeys的区别在于,volatile只淘汰设置了过期时间的key值,未设置过期时间的key值不会淘汰,如果没有设置过期时间的key值,效果等同与noeviction。allkeys淘汰的值范围则是所有的key值,不区分是否设置过过期时间。
LFU的过期策略是在redis4.0之后新增的策略,具体有以下两种。
1.volatile-lfu
(least frequently used):最近最不经常使用算法,从设置了过期时间的键中选择某段时间之内使用频次最小的键值对清除掉;
2.allkeys-lfu
:最近最不经常使用算法,从所有的键中选择某段时间之内使用频次最少的键值对清除;
当内存满了之后,根据淘汰策略设定,会淘汰一部分key值,所以可能导致命中率变低。 当淘汰策略为noeviction的时候,虽然内存不被淘汰,读不受影响,但是因为写操作会失败,所以可能会产生命中率高的key无法写入,一定程度上也会影响到命中率。
官方推荐的几种过期策略的设定:
-
当请求符合幂律分布的时候,适合使用allkeys-lru策略。 即:一部分数据会被频繁访问,或者说,大部分请求会命中在部分数据集上。类似于常说的28法则。
-
如果请求命中的数据概率基本相当,适合用allkeys-random策略。即,每个数据被访问到的概率差不多,所以,随机淘汰数据,对整体的命中率影响不大
-
如果想通过设置不同的ttl来过期数据,这种比较适合用volatile-ttl。即:业务方知道哪些对象适合什么时间淘汰。
-
volatile-lru和volatile-random一般用在想用一个redis实例来实现缓存和持久化目的的场景,但是最好是将缓存场景和持久化场景分开。也就是使用两个不同的实例。PS:volatile-lru和volatile-random能做到这一点的原因在于,当redis中没有已经设置了过期时间的key值时,则相当于noeviction的策略了,也就是相当于持久化了。
PS:给key值设置过期时间是要额外消耗一部分内存的, 所以,如果没有设置过期时间的诉求的话,推荐使用allkeys-lru的策略。 可以更好提升内存的使用。
过期策略如何运行?
-
客户端执行命令,并且这个命令执行后,会占用redis内存。
-
redis执行命令前,会先检查下当前内存的大小是否超过设定的maxmemory值,如果超过,则按照淘汰策略,释放内存
-
命令执行完成 (如果依旧没有内存,则执行失败,比如noevition策略下)
过期策略:
server.c 在执行redis命令的时候,都会去检测下是否到达内存上限,如果达到,则按照过期策略释放内存。
方法:int processCommand(client *c)
evict.c里面的freeMemoryIfNeededAndSafe()
方法,用来根据各种策略释放内存
注意:代码中有这么一段内容:
前面说,淘汰策略设置为noeviction的时候,缓存不会被释放。 这段代码上看,redis还是会尝试去看下是否有可能会释放内存。
can_free代码块中可以看到, 如果淘汰策略设置为noeviction的时候,当内存达到最大内存时,会去尝试看下是否存在lazy free thread(也就是延迟释放)线程中是否还有未执行完的释放任务。如果有的话,则等待内存释放。
lazy-free 在Redis4.0新增
关于 lazy free的介绍,可以看下: https://www.jianshu.com/p/e927e99e650d
TODO:
测试下内存满了的时候的性能:设置淘汰策略为allkeys-random。 理论上应该会降低很多。 因为要去释放内存,写入指令需要等待内存释放后才能写入。
如何提高命中率?
根据原因不同,具体方式不同。
-
如果是内存不够导致频繁淘汰内存,则需要增大最大内存配置,或者调整缓存的淘汰策略。
-
如果是粒度设计不合理,则更改粒度设计,使粒度变小。 但是注意,粒度变小可能同时带来的问题会是内存变大。 所以适当考虑对内容做压缩。
-
过期时间的设定,在业务允许的情况下,可以增加缓存的过期时间。
-
如果业务场景是访问并发很高,但对数据的时效性要求没有那么高的时候,可以考虑做缓存预热。如果并发度不是很高,访问比较分散的话, 缓存预热反而效果不好。
补充几个工具:
Redis可视化监控工具Redis-Stat:
有赞TMC多级缓存解决方案:
网址: https://tech.youzan.com/tmc/
简单介绍: 未开源,主要介绍了实现方式,可实现缓存发现,缓存预热等功能。
补充:
关于redis的LRU策略
Redis中的LRU并非严格的LRU,而是一种近似LRU的策略。LRU时,并非去全量的key值,而是每次取一小部分key值,然后从这部分key值里面选择最合适的数据淘汰(最近最少使用的key值)。目的是为了减少内存的使用。 但实际效果上, Redis使用的近似LRU和严格的LRU的效果差距不是很大。
下面这个是官方对比的LRU的效果情况:
说明:
亮灰色(最上面那个):表示的是被淘汰的key值。
深灰色(中间那个):表示的是未被淘汰的key值,也就是内存中存在的值。
绿色(最下面):表示的新增的值。
看图上的话, Redis3.0 下 10的采样率下,最接近严格的LRU策略。 5的采样率下和严格的差距也不大。 Redis2.8下的5的采样率表现相对差一些。
采样率越高,LRU会越接近与严格的LRU效果。 但是会增大CPU的消耗。
近似LRU的采样数是根据 maxmemory-samples 这个配置来的。默认是5 。 可以使用 CONFIG SET maxmemory-samples
PS:如果数据集符合幂律分布,那么Redis的近似LRU策略效果会更接近与严格的LRU。