| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> Redis 内存优化神技,小内存保存大数据 -> 正文阅读 |
|
[大数据]Redis 内存优化神技,小内存保存大数据 |
如何用更少的内存保存更多的数据? 我们应该从 Redis 是如何保存数据的原理展开,分析键值对的存储结构和原理。 从而继续延展出每种数据类型底层的数据结构,针对不同场景使用更恰当的数据结构和编码实现更少的内存占用。 为了保存数据, Redis 需要先申请内存,数据过期或者内存淘汰需要回收内存,从而拓展出内存碎片优化。 最后,说下 key、value 使用规范和技巧、 Bitmap 等高阶数据类型,运用这些技巧巧妙解决有限内存去存储更多数据难题…… 这一套组合拳下来直接封神。 主要优化神技如下:
在优化之前,我们先掌握 Redis 是如何存储数据的。 Redis 如何存储键值对Redis 以?
Redis 使用「dict」结构来保存所有的键值对(key-value)数据,这是一个全局哈希表,所以对 key 的查询能以 O(1) 时间得到。 所谓哈希表,我们可以类比 Java 中的? dict 结构如下,源码在?
key 的哈希值最终会映射到 ht_table 的一个位置,如果发生哈希冲突,则拉出一个哈希链表。 大家重点关注? 码哥,Redis 支持那么多的数据类型,哈希桶咋保存? 哈希桶的每个元素的结构由 dictEntry 定义:
哈希桶并没有保存值本身,而是指向具体值的指针,从而实现了哈希桶能存不同数据类型的需求。 而哈希桶中,键值对的值都是由一个叫做?
如下图是由 redisDb、dict、dictEntry、redisObejct 关系图: void?key 和 void?value 指针指向的是?redis对象,因为 Redis 中每个对象都是用? 知道了 Redis 存储原理以及不同数据类型的存储数据结构后,我们继续看如何做性能优化。 1. 键值对优化当我们执行? 第一个优化神技:降低 Redis 内存使用的最粗暴的方式就是缩减键(key)与值(value)的长度。 在《Redis 很强,不懂使用规范就糟蹋了》中我说过关于键值对的使用规范,对于 key 的命名使用「业务模块名:表名:数据唯一id」这样的方式方便定位问题。 比如:users:firends:996 表示用户系统中,id = 996 的朋友信息。我们可以简写为: 对于 key 的优化:使用单词简写方式优化内存占用。 对于 value 的优化那就更多了:
2. 小数据集合编码优化key 对象都是 string 类型,value 对象主要有五种基本数据类型:String、List、Set、Zset、Hash。 数据类型与底层数据结构的关系如下所示: 特别说明下在最新版(非稳定版本,时间 2022-7-3),ziplist 压缩列表由? 另外,同一数据类型会根据键的数量和值的大小也有不同的底层编码类型实现。 在 Redis 2.2 版本之后,存储集合数据(Hash、List、Set、SortedSet)在满足某些情况下会采用内存压缩技术来实现使用更少的内存存储更多的数据。 当这些集合中的数据元素数量小于某个值且元素的值占用的字节大小小于某个值的时候,存储的数据会用非常节省内存的方式进行编码,理论上至少节省 10 倍以上内存(平均节省 5 倍以上)。 比如 Hash 类型里面的数据不是很多,虽然哈希表的时间复杂度是 O(1),ziplist 的时间复杂度是 O(n),但是使用 ziplist 保存数据的话会节省了内存,并且在少量数据情况下效率并不会降低很多。 所以我们需要尽可能地控制集合元素数量和每个元素的内存大小,这样能充分利用紧凑型编码减少内存占用。 并且,这些编码对用户和 api 是无感知的,当集合数据超过配置文件的配置的最大值, Redis 会自动转成正常编码。 数据类型对应的编码规则如下所示 String 字符串
List 列表
?
Set 集合
Hash 哈希表
Sorted Set 有序集合
以下是 Redis redis.conf 配置文件默认编码阈值配置:
?下图是? 为啥对一种数据类型实现多种不同编码方式? 主要原因是想通过不同编码实现效率和空间的平衡。 比如当我们的存储只有100个元素的列表,当使用双向链表数据结构时,需要维护大量的内部字段。 比如每个元素需要:前置指针,后置指针,数据指针等,造成空间浪费。 如果采用连续内存结构的压缩列表(ziplist),将会节省大量内存,而由于数据长度较小,存取操作时间复杂度即使为O(n) 性能也相差不大,因为 n 值小 与 O(1) 并明显差别。 数据编码优化技巧 ziplist 存储 list 时每个元素会作为一个 entry,存储 hash 时 key 和 value 会作为相邻的两个 entry。 存储 zset 时 member 和 score 会作为相邻的两个entry,当不满足上述条件时,ziplist 会升级为 linkedlist, hashtable 或 skiplist 编码。 由于目前大部分Redis运行的版本都是在3.2以上,所以 List 类型的编码都是quicklist。 quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 来紧凑存储,多个 ziplist 之间使用双向指针串接起来。 考虑了综合平衡空间碎片和读写性能两个维度所以使用了个新编码 quicklist。 ziplist 的不足 每次修改都可能触发 realloc 和 memcopy, 可能导致连锁更新(数据可能需要挪动)。 因此修改操作的效率较低,在 ziplist 的元素很多时这个问题更加突出。 优化手段:
3. 对象共享池整数我们经常在工作中使用,Redis 在启动的时候默认后生成一个?0 ~9999 的整数对象共享池用于对象复用,减少内存占用。 比如执行 如果 value 可以使用整数表示的话尽可能使用整数,这样即使大量键值对的 value 大量保存了 0~9999 范围内的整数,在实例中,其实只有一份数据。 靓仔们,有两个大坑遇到注意,它会导致对象共享池失效。 为了直观展示,我们可以理解成 buf 数组的每个字节用一行表示,每一行有 8 个 bit 位,8 个格子分别表示这个字节中的 8 个 bit 位,如下图所示: 4.使用 Bit 比特位或 byte 级别操作比如在一些「二值状态统计」的场景下使用 Bitmap 实现,对于网页 UV 使用 HyperLogLog 来实现,大大减少内存占用。 什么是二值状态统计呀? 也就是集合中的元素的值只有 0 和 1 两种,在签到打卡和用户是否登陆的场景中,只需记录 假如我们在判断用户是否登陆的场景中使用 Redis 的 String 类型实现(key -> userId,value -> 0 表示下线,1 - 登陆),假如存储 100 万个用户的登陆状态,如果以字符串的形式存储,就需要存储 100 万个字符串,内存开销太大。 String 类型除了记录实际数据以外,还需要额外的内存记录数据长度、空间使用等信息。 Bitmap 的底层数据结构用的是 String 类型的 SDS 数据结构来保存位数组,Redis 把每个字节数组的 8 个 bit 位利用起来,每个 bit 位 表示一个元素的二值状态(不是 0 就是 1)。 可以将 Bitmap 看成是一个 bit 为单位的数组,数组的每个单元只能存储 0 或者 1,数组的下标在 Bitmap 中叫做 offset 偏移量。
8 个 bit 组成一个 Byte,所以 Bitmap 会极大地节省存储空间。?这就是 Bitmap 的优势。 5. 妙用 Hash 类型优化尽可能把数据抽象到一个哈希表里。 比如说系统中有一个用户对象,我们不需要为一个用户的昵称、姓名、邮箱、地址等单独设置一个 key,而是将这个信息存放在一个哈希表里。 如下所示:
为啥使用 String 类型,为每个属性设置一个 key 会占用大量内存呢? 因为 Redis 的数据类型有很多,不同数据类型都有些相同的元数据要记录(比如最后一次访问的时间、被引用的次数等)。 所以,Redis 会用一个 RedisObject 结构体来统一记录这些元数据,用 *prt 指针指向实际数据。 当我们为每个属性都创建 key,就会创建大量的? 如下所示 redisObject 内存占用: 用 Hash 类型的话,每个用户只需要设置一个 key。 6. 内存碎片优化Redis 释放的内存空间可能并不是连续的,这些不连续的内存空间很有可能处于一种闲置的状态。 虽然有空闲空间,Redis 却无法用来保存数据,不仅会减少 Redis 能够实际保存的数据量,还会降低 Redis 运行机器的成本回报率。 比如, Redis 存储一个整形数字集合需要一块占用 32 字节的连续内存空间,当前虽然有 64 字节的空闲,但是他们都是不连续的,导致无法保存。 内存碎片是如何形成呢? 两个层面原因导致:
碎片优化可以降低内存使用率,提高访问效率,在4.0以下版本,我们只能使用重启恢复:重启加载 RDB 或者通过高可用主从切换实现数据的重新加载减少碎片。 在4.0以上版本,Redis提供了自动和手动的碎片整理功能,原理大致是把数据拷贝到新的内存空间,然后把老的空间释放掉,这个是有一定的性能损耗的。 因为 Redis 是单线程,在数据拷贝时,Redis 只能等着,这就导致 Redis 无法处理请求,性能就会降低。 手动整理碎片执行? 自动整理内存碎片使用? 这个配置还不够,至于啥时候清理还需要看下面的两个配置:
只有满足这两个条件, Redis 才会执行内存碎片自动清理。 除此之外,Redis 为了防止清理碎片对 Redis 正常处理指令造成影响,有两个参数用于控制清理操作占用 CPU 的时间比例上下限。
7. 使用 32 位的 Redis使用32位的redis,对于每一个key,将使用更少的内存,因为32位程序,指针占用的字节数更少。 但是32的Redis整个实例使用的内存将被限制在4G以下。我们可以通过 cluster 模式将多个小内存节点构成一个集群,从而保存更多的数据。 另外小内存的节点 fork 生成 rdb 的速度也更快。 RDB和AOF文件是不区分32位和64位的(包括字节顺序),所以你可以使用64位的Redis 恢复32位的RDB备份文件,相反亦然。
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/15 23:37:56- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |