Redis优化

Redis 设计优化

1 估算Redis内存使用量

要估算redis中的数据占据的内存大小,需要对redis的内存模型有比较全面的了解,包括hashtable、sds、redisobject、各种对象类型的编码方式等。

下面以最简单的字符串类型来进行说明。

假设有90000个键值对,每个key的长度是10个字节,每个value的长度也是10个字节(且key和
value都不是整数);
在这里插入图片描述

下面来估算这90000个键值对所占用的空间。在估算占据空间之前,首先可以判定字符串类型使用的编码方式:embstr。

90000个键值对占据的内存空间主要可以分为两部分:一部分是90000个dictEntry占据的空间;一部分是键值对所需要的bucket空间。

每个dictEntry占据的空间包括:

  1. 一个dictEntry结构,24字节,jemalloc会分配32字节的内存块(64位操作系统下,一个指针8字
    节,一个dictEntry由三个指针组成)
  2. 一个key,12字节,所以SDS(key)需要12+4=16 个字节([SDS的长度=4+字符串长度),jemalloc会分配16字节的内存块
  3. 一个redisObject,16字节,jemalloc会分配16字节的内存(4bit+4bit+24bit+4Byte+8Byte=16Byte)
  4. 一个value,12字节,所以SDS(value)需要12+4=16个字节([SDS的长度=4+字符串长度),jemalloc会分配16字节的内存块
  5. 综上,一个dictEntry所占据的空间需要32+16+16+16=80个字节。

bucket空间:
bucket数组的大小为大于90000的最小的2^n,是131072;每个bucket元素(bucket中存储的都是指针 元素)为8字节(因为64位系统中指针大小为8字节)。

因此,可以估算出这90000个键值对占据的内存大小为:[9000080 + 1310728 = 8248576]

作为对比将key和value的长度由12字节增加到13字节,则对应的SDS变为17个字节,jemalloc会分配32个字节,因此每个dictEntry占用的字节数也由80字节变为112字节。

此时估算这90000个键值对占据内存大小为:90000112 + 1310728 = 11128576。

2 优化内存占用

了解redis的内存模型,对优化redis内存占用有很大帮助。下面介绍几种优化场景和方式

(1)利用jemalloc特性进行优化

上一小节所讲述的90000个键值便是一个例子。由于jemalloc分配内存时数值是不连续的,因此
key/value字符串变化一个字节,可能会引起占用内存很大的变动;在设计时可以利用这一点。

例如,如果key的长度如果是13个字节,则SDS为17字节,jemalloc分配32字节;此时将key长度
缩减为12个字节,则SDS为16字节,jemalloc分配16字节;则每个key所占用的空间都可以缩小一 半。

(2)使用整型/长整型

如果是整型/长整型,Redis会使用int类型(8字节)存储来代替字符串,可以节省更多空间。因此在可以使用长整型/整型代替字符串的场景下,尽量使用长整型/整型。

(3)共享对象

利用共享对象,可以减少对象的创建(同时减少了redisObject的创建),节省内存空间。目前redis中
的共享对象只包括10000个整数(0-9999);可以通过调整REDIS_SHARED_INTEGERS参数提高共享对象的个数;

例如将REDIS_SHARED_INTEGERS调整到20000,则0-19999之间的对象都可以共享

论坛网站在redis中存储了每个帖子的浏览数,而这些浏览数绝大多数分布在0-20000之间,这时
候通过适当增大REDIS_SHARED_INTEGERS参数,便可以利用共享对象节省内存空间。

(4)缩短键值对的存储长度

键值对的长度是和性能成反比的,比如我们来做一组写入数据的性能测试,执行结果如下:
在这里插入图片描述

从以上数据可以看出,在 key 不变的情况下,value 值越大操作效率越慢,因为 Redis 对于同一种数据类型会使用不同的内部编码进行存储,比如字符串的内部编码就有三种:int(整数编码)、raw(优化内存分配的字符串编码)、embstr(动态字符串编码),这是因为 Redis 的作者是想通过不同编码实现效率和空间的平衡,然而数据量越大使用的内部编码就越复杂,而越是复杂的内部编码存储的性能就越低。

这还只是写入时的速度,当键值对内容较大时,还会带来另外几个问题:

  • 内容越大需要的持久化时间就越长,需要挂起的时间越长,Redis 的性能就会越低;
  • 内容越大在网络上传输的内容就越多,需要的时间就越长,整体的运行速度就越低;
  • 内容越大占用的内存就越多,就会更频繁的触发内存淘汰机制,从而给 Redis 带来了更多的运行 负担。

因此在保证完整语义的同时,我们要尽量的缩短键值对的存储长度,必要时要对数据进行序列化和压缩再存储,以 Java 为例,序列化我们可以使用 protostuff 或 kryo,压缩我们可以使用 snappy。

基于Redis内存淘汰机制的优化

设置键值的过期时间(惰性删除)

我们应该根据实际的业务情况,对键值设置合理的过期时间,这样 Redis 会帮你自动清除过期的键
值对,以节约对内存的占用,以避免键值过多的堆积,频繁的触发内存淘汰策略。

Redis 有四个不同的命令可以用于设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除) :

  • EXPlRE 命令用于将键key 的生存时间设置为ttl 秒。
  • PEXPIRE 命令用于将键key 的生存时间设置为ttl 毫秒。
  • EXPIREAT < timestamp> 命令用于将键key 的过期时间设置为timestamp所指定的秒数时间戳。
  • PEXPIREAT < timestamp > 命令用于将键key 的过期时间设置为timestamp所指定的毫秒数时间 戳。
1	127.0.0.1:6379> set key1 'value1'
2	OK
3	127.0.0.1:6379> expire key1 20
4	(integer) 1
5	127.0.0.1:6379> get key1 "value1" 127.0.0.1:6379> get key1 "value1"
	127.0.0.1:6379> get key1 (nil) 127.0.0.1:6379>

使用 lazy free 特性

lazy free 特性是 Redis 4.0 新增的一个非常使用的功能,它可以理解为惰性删除或延迟删除。意思是在删除的时候提供异步延时释放键值的功能,把键值释放操作放在 BIO(Background I/O) 单独的子线程处理中,以减少删除删除对 Redis 主线程的阻塞,可以有效地避免删除 big key 时带来的性能和可用性问题。

lazy free 对应了 4 种场景,默认都是关闭的:

  1. lazyfree-lazy-eviction no
  2. lazyfree-lazy-expire no
  3. lazyfree-lazy-server-del no
  4. slave-lazy-flush no

它们代表的含义如下:

  • lazyfree-lazy-eviction:表示当 Redis 运行内存超过最大内存时,是否开启 lazy free 机制删除;
  • lazyfree-lazy-expire:表示设置了过期时间的键值,当过期之后是否开启 lazy free 机制删除;
  • lazyfree-lazy-server-del:有些指令在处理已存在的键时,会带有一个隐式的 del 键的操作,比如 rename命令,当目标键已存在,Redis 会先删除目标键,如果这些目标键是一个 big key,就会造成阻塞删除的问题,此配置表示在这种场景中是否开启 lazy free 机制删除;
  • slave-lazy-flush:针对 slave(从节点) 进行全量数据同步,slave 在加载 master 的 RDB 文件前,会运行 flushall 来清理自己的数据,它表示此时是否开启 lazy free 机制删除。

建议开启其中的 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del 等配置,这样
就可以有效的提高主线程的执行效率。

限制 Redis 内存大小(重点)

最大缓存

maxmemory 1048576
maxmemory 1048576B
maxmemory 1000KB
maxmemory 100MB
maxmemory 1GB
maxmemory 1000K
maxmemory 100M
maxmemory 1G
有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。

在 64 位操作系统中 Redis 的内存大小是没有限制的,也就是配置项 maxmemory 是被注释掉的,
这样就会导致在物理内存不足时,使用 swap 空间既交换空间,而当操心系统将 Redis 所用的内存 分页移至 swap 空间时,将会阻塞
Redis 进程,导致 Redis 出现延迟,从而影响 Redis 的整体性 能。因此我们需要限制 Redis 的内存大小为一个固定的值,当
Redis 的运行到达此值时会触发内存淘汰策略,内存淘汰策略在 Redis 4.0 之后有 8 种

LRU原理

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心
思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
在这里插入图片描述

  1. 新数据插入到链表头部;
  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  3. 当链表满的时候,将链表尾部的数据丢弃。

案例分析

让我们以用户信息的需求为例,来演示一下LRU算法的基本思路:

1.假设我们使用哈希链表来缓存用户信息,目前缓存了4个用户,这4个用户是按照时间顺序依次从链
表右端插入的。

在这里插入图片描述

2.此时,业务方访问用户5,由于哈希链表中没有用户5的数据,我们从数据库中读取出来,插入到缓
存当中。这时候,链表中最右端是最新访问到的用户5,最左端是最近最少访问的用户1。

在这里插入图片描述

3.接下来,业务方访问用户2,哈希链表中存在用户2的数据,我们怎么做呢?我们把用户2从它的前驱节点和后继节点之间移除,重新插入到链表最右端。这时候,链表中最右端变成了最新访问到的用户2,最左端仍然是最近最少访问的用户1。

在这里插入图片描述

在这里插入图片描述

4.接下来,业务方请求修改用户4的信息。同样道理,我们把用户4从原来的位置移动到链表最右侧,
并把用户信息的值更新。这时候,链表中最右端是最新访问到的用户4,最左端仍然是最近最少访问的用户1。

在这里插入图片描述

在这里插入图片描述

5.后来业务方换口味了,访问用户6,用户6在缓存里没有,需要插入到哈希链表。假设这时候缓存容
量已经达到上限,必须先删除最近最少访问的数据,那么位于哈希链表最左端的用户1就会被删除掉,然后再把用户6插入到最右端。
在这里插入图片描述
在这里插入图片描述

以上,就是LRU算法的基本思路。
https://www.itcodemonkey.com/article/11153.html

LFU原理

LFU,全称是:Least Frequently Used,最不经常使用策略,在一段时间内,数据被使用频次最少的,优先被淘汰。最少使用LFU)是一种用于管理计算机内存的缓存算法。主要是记录和追踪内存块的使用次数,当缓存已满并且需要更多空间时,系统将以最低内存块使用频率清除内存.采用LFU算法的最简单方法是为每个加载到缓存的块分配一个计数器。每次引用该块时,计数器将增加一。当缓存达到容量并有一个新的内存块等待插入时,系统将搜索计数器最低的块并将其从缓存中删除(本段摘自维基百科)

在这里插入图片描述
在这里插入图片描述

LRU和LFU侧重点不同,LRU主要体现在对元素的使用时间上,而LFU主要体现在对元素的使用频次上。

LFU的缺陷是:在短期的时间内,对某些缓存的访问频次很高,这些缓存会立刻晋升为热点数据,而保
证不会淘汰,这样会驻留在系统内存里面。而实际上,这部分数据只是短暂的高频率访问,之后将会长
期不访问,瞬时的高频访问将会造成这部分数据的引用频率加快,而一些新加入的缓存很容易被快速删
除,因为它们的引用频率很低。

Redis缓存淘汰策略

redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。

maxmemory-policy voltile-lru,支持热配置 内存淘汰策略在 Redis 4.0 之后有 8 种:

  1. noeviction:不淘汰任何数据,当内存不足时,新增操作会报错,Redis 默认内存淘汰策略;
  2. allkeys-lru:淘汰整个键值中最久未使用的键值;
  3. allkeys-random:随机淘汰任意键值;
  4. volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值;
  5. volatile-random:随机淘汰设置了过期时间的任意键值;
  6. volatile-ttl:优先淘汰更早过期的键值。

在 Redis 4.0 版本中又新增了 2 种淘汰策略:

  1. volatile-lfu:淘汰所有设置了过期时间的键值中,最少使用的键值;
  2. allkeys-lfu:淘汰整个键值中最少使用的键值。

其中 allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。

我们可以根据实际的业务情况进行设置,默认的淘汰策略不淘汰任何数据,在新增时会报错。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值