Redis 是一个开源、高性能的 Key-Value 数据库,被广泛应用在服务器各种场景中。Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多。所以,监控 Redis 的内存消耗并了解 Redis 内存模型对高效并长期稳定使用 Redis 至关重要。
在介绍之前先说明下,一般生产环境下,对开发同事不会开放直连 redis 集群的权限,一般是提供 daas 平台,通过可视化命令窗口,输入 redis 命令,一般只有 read 权限;对于 write 操作,需要提 redis 数据变更单,而对于 redis 内存、大 key、慢命令,一般都会将信息集成及中显示在监控看板,而不需要开发同事自己去输入命令;但是基本的相关知识还是要具备的。
reids 内存分析
redis 内存使用情况:info memory示例:可以看到,当前节点内存碎片率为 226893824/209522728 ≈ 1.08,使用的内存分配器是 jemalloc。
used_memory_rss 通常情况下是大于 used_memory 的,因为内存碎片的存在。
但是当操作系统把 redis 内存 swap 到硬盘时,memory_fragmentation_ratio 会小于 1。redis 使用硬盘作为内存,因为硬盘的速度,redis 性能会受到极大的影响。
redis 内存使用
redis 的内存使用分布:自身内存,键值对象占用、缓冲区内存占用及内存碎片占用。
redis 空进程自身消耗非常的少,可以忽略不计,优化内存可以不考虑此处的因素。
对象内存
对象内存,也即真实存储的数据所占用的内存。
redis k-v 结构存储,对象占用可以简单的理解为 k-size + v-size。
redis 的键统一都为字符串类型,值包含多种类型:string、list、hash、set、zset五种基本类型及基于 string 的 Bitmaps 和 HyperLogLog 类型等。
在实际的应用中,一定要做好 kv 的构建形式及内存使用预期,。
缓冲内存
缓冲内存包括三部分:客户端缓存、复制积压缓存及 AOF 缓冲区。
客户端缓存
接入redis服务器的TCP连接输入输出缓冲内存占用,TCP 输入缓冲占用是不受控制的,最大允许空间为 1G。输出缓冲占用可以通过 client-output-buffer-limit 参数配置。
redis 客户端主要分为从客户端、订阅客户端和普通客户端。
- 从客户端连接占用
也就是我们所说的 slave,主节点会为每一个从节点建立一条连接用于命令复制,缓冲配置为:client-output-buffer-limit slave 256mb 64mb 60。
主从之间的间络延迟及挂载的从节点数量是影响内存占用的主要因素。因此在涉及需要异地部署主从时要特别注意,另外,也要避免主节点上挂载过多的从节点( used_memory。
另外,可以通过动态配置 maxmemory 来主动触发内存回收。更多关于 Redis 学习的文章,请参阅:NoSQL 数据库系列之 Redis,本系列持续更新中。
内存回收策略
内存回收触发有两种情况,也就是内存使用达到maxmemory上限时候触发的溢出回收,还有一种是我们设置了过期的对象到期的时候触发的到期释放的内存回收。Redis内存使用达到maxmemory上限时候触发的溢出回收;Redis 提供了几种策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务:
- (1)volatile-lru:从已设置过期时间的数据集(
server.db[i].expires
)中挑选最近最少使用的数据淘汰 - (2)volatile-ttl:从已设置过期时间的数据集(
server.db[i].expires
)中挑选将要过期的数据淘汰 - (3)volatile-random:从已设置过期时间的数据集(
server.db[i].expires
)中任意选择数据淘汰 - (4)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
- (5)allkeys-random:从数据集(
server.db[i].dict
)中任意选择数据淘汰 - (6)no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
- 4.0版本后增加以下两种:
- (7)volatile-lfu:从已设置过期时间的数据集(
server.db[i].expires
)中挑选最不经常使用的数据淘汰 - (8)allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
redis默认的策略就是noeviction策略,如果想要配置的话,需要在配置文件中写这个配置:
maxmemory-policy volatile-lru
Redis 的 LRU 算法
LRU是Least Recently Used 近期最少使用算法,很多缓存策略都使用了这种策略进行空间的释放,在学习操作系统的内存回收的时候也用到了这种机制进行内存的回收,类似的还有LFU(Least Frequently Used)最不经常使用算法,这种算法。
我们在上面的描述中也可以了解到,redis使用的是一种类似LRU的算法进行内存溢出回收的,其算法的代码:
/* volatile-lru and allkeys-lru policy */
else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
{
struct evictionPoolEntry *pool = db->eviction_pool;
while(bestkey == NULL) {
evictionPoolPopulate(dict, db->dict, db->eviction_pool);
/* Go backward from best to worst element to evict. */
for (k = REDIS_EVICTION_POOL_SIZE-1; k >= 0; k--) {
if (pool[k].key == NULL) continue;
de = dictFind(dict,pool[k].key);
/* Remove the entry from the pool. */
sdsfree(pool[k].key);
/* Shift all elements on its right to left. */
memmove(pool+k,pool+k+1,
sizeof(pool[0])*(REDIS_EVICTION_POOL_SIZE-k-1));
/* Clear the element on the right which is empty
* since we shifted one position to the left. */
pool[REDIS_EVICTION_POOL_SIZE-1].key = NULL;
pool[REDIS_EVICTION_POOL_SIZE-1].idle = 0;
/* If the key exists, is our pick. Otherwise it is
* a ghost and we need to try the next element. */
if (de) {
bestkey = dictGetKey(de);
break;
} else {
/* Ghost... */
continue;
}
}
}
}
Redis会基于server.maxmemory_samples配置选取固定数目的key,然后比较它们的lru访问时间,然后淘汰最近最久没有访问的key,maxmemory_samples的值越大,Redis的近似LRU算法就越接近于严格LRU算法,但是相应消耗也变高。所以,频繁的进行这种内存回收是会降低redis性能的,主要是查找回收节点和删除需要回收节点的开销。
所以一般我们在配置redis的时候,尽量不要让它进行这种内存溢出的回收操作,redis是可以配置maxmemory,used_memory指的是redis真实占用的内存,但是由于操作系统还有其他软件以及内存碎片还有swap区的存在,所以我们实际的内存应该比redis里面设置的maxmemory要大,具体大多少视系统环境和软件环境来定。maxmemory也要比used_memory大,一般由于碎片的存在需要做1~2个G的富裕。
来源:cnblogs.com/niejunlei/p/12898225.html