Redis 实战 —— 12. 降低内存占用
简介
降低 Redis 的内存占用有助于减少创建快照和加载快照所需的时间、提升载入 AOF 文件和重写 AOF 文件时的效率、缩短从服务器进行同步所需的时间(快照、 AOF 文件重写在 持久化选项 中进行了介绍,从服务器同步在 复制、处理故障、事务及性能优化 中进行了介绍),并且能让 Redis 存储更多的数据而无需添加额外的硬件。 P208
短结构 (short structure) P208
Redis 为列表、集合、散列和有序集合提供了一组配置选项,这些选项可以让 Redis 以更节约空间的方式存储长度较短的结构(后面简称“短结构”)。 P208
在列表、散列和有序集合的长度较短或者体积较小的时候, Redis 可以选择使用一种名为压缩列表 (ziplist) 的紧凑存储方式来存储这些机构。压缩列表是列表、散列和有序集合这 3 种不同类型的对象的一种非结构化 (unstructured) 表示:与 Redis 在通常情况下使用双向链表表示列表、使用散列表表示散列、使用散列表加上跳表 (skiplist) 表示有序集合的做法不同,压缩列表会以序列化的方式存储数据,这些序列化数据每次被读取的时候都要进行解码,每次被写入的时候也要进行局部的重新编码,并且可能需要对内存里面的数据进行移动。 P209
压缩列表表示 P209
本节以最简单的列表进行观察对比。
双向链表 P209
列表不进行压缩时使用双向链表 (doubly linked list) 进行存储,链表的每个结点都有三个指针: P209
- 指向前一个结点的指针
- 指向后一个结点的指针
- 指向结点包含的字符串值的指针
其中字符串值又分为三个部分: P209
- 字符串的长度
- 字符串剩余可用的字节数
- 以空字符结尾的字符串本身
可以发现未压缩前,每存储一个字符串,最少需要 21 字节的额外开销 (overhead) 。(三个指针每个占 4 个字节,两个整数每个占 4 个字节,字符串结尾的空字符占 1 个字节) P209
压缩列表 P209
压缩列表是由结点(非真实结点)组成的序列 (sequence) ,每个结点都由两个长度值和一个字符串组成。 P209
- 第一个长度值:前一个结点的长度,用于从后向前的遍历(一般以一个字节存储)
- 第二个长度值:当前结点的长度(一般以一个字节存储)
- 字符串:长度等于字节数,没有空字符
可以发现压缩后,每存储一个字符串,最少需要 2 字节的额外开销。 P210
使用压缩列表编码 P210
不同结构关于使用压缩列表的配置选项 P210
# 列表使用压缩列表表示的限制条件
list-max-ziplist-entries 512
list-max-ziplist-value 64
# 散列使用压缩列表表示的限制条件
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
# 有序集合使用压缩列表表示的限制条件
zset-max-ziplist-entries 512
zset-max-ziplist-value 64
其中, ...-entries
选项说明列表、散列和有序集合在被编码为压缩列表的情况下,允许包含的最大元素数量; ...-value
选项说明了压缩列表每个结点的最大体积是多少字节。当这些选项设置的限制条件中的任意一个被突破时, Redis 就会将对应的列表、散列和有序集合从压缩列表编码转换为其他结构,而内存占用也会因此增加,并且即使其将来重新满足限制条件,也不会再转换回压缩列表。 P210
调试 P210
OBJECT
命令允许从内部查看给定 key 的 Redis 对象, 它通常用在调试(debugging) 或者了解为了节省空间而对 key 使用特殊编码的情况。当将Redis 用于进行缓存时,也可以通过 OBJECT
命令中的信息,决定 key 的驱逐策略 (eviction policies) 。
OBJECT REFCOUNT <key>
: 返回给定 key 引用所储存的值的次数。主要用于调试OBJECT ENCODING <key>
: 返回给定 key 所储存的值所使用的内部表示(representation)OBJECT IDLETIME <key>
: 返回给定 key 自储存以来的空闲时间(idle, 没有被读取也没有被写入),以秒为单位
集合的整数集合编码 P211
如果集合的所有成员都可以被解释为十进制整数(在平台的有符号整数范围内),并且集合成员的数量足够少,那么 Redis 就会以有序整数数组的方式存储集合,这种存储方式又被称为整数集合 (intset) 。整数集合不仅可以降低内存消耗,还可以提升所有标准集合操作的执行速度。 P211
整数集合的配置选项 P211
# 集合使用整数集合表示的限制条件
set-max-intset-entries 512
当整数集合包含当元素数量超过配置选项设定的限制时,整数集合将被转换为散列表表示。 P212
长压缩列表和大整数集合带来的性能问题 P212
压缩列表结点数 | 性能 |
---|---|
< 1000 | 差别不大 |
5000 ~ 10000 | 开始下降 |
50000 | 下降明显 |
> 100000 | 低到无法使用 |
推荐将压缩列表的长度限制在 1024 个元素内,并且每个元素的体积不能超过 64 字节,对于大多数散列应用来说,这种配置可以同时兼顾低内存占用和高性能这两方面优点。 P214
注
Redis 在 3.2 版本后的列表底层默认使用 quicklist ,这种数据结构兼顾了双向链表和压缩列表的优点,因此列表目前来说已使用最优配置。
我们在设计 Redis 时同时也要保持键名简短(包括数据键、散列的域、集合和有序集合的成员以及所有列表的结点),当存储结点的数据量达到上百万个或者数十亿个时,将能节省 MB 升至 GB 级的空间。 P214
分片结构 P214
分片 (sharding) 本质上就是基于某些简单的规则将数据划分为更小的部分,然后根据数据所属的部分来决定将数据发送到哪个位置上面。这种技术可以扩展存储空间并提高所能处理的负载量。 P214
接下来将把分片的概念应用到散列、集合和有序集合上,并在讲解实现这些数据结构的其中一部分标准功能的方法。这种情况下,程序不再是将值 X
存储到键 Y
里面,而是将值 X
存储到键 Y:<shardid>
里面。 P214
对列表进行分片 P214
在不使用 Lua 脚本的情况下对列表进行分配非常困难,因此将在后面介绍使用 Lua 脚本构建一个分片式的列表,并支持以阻塞和非阻塞两种方式从列表的两端进行推入和弹出操作。
对有序集合进行分片 P215
因为 ZRANGE
, ZRANGEBYSCORE
, ZRANK
, ZCOUNT
, ZREMRANGE
, ZREMRANGEBYSCORE
这类命令的分片版本需要对有序集合的所有分片进行操作才能计算出命令的最终结果,所以这些操作无法运行得像普通的有序集合操作那么快,因此对有序集合进行分片的作用不大。
如果需要将完整的信息存储到一个体积较大的有序集合中,但只会对分值排名前 n 位和后 n 位对元素进行操作,那么可以使用下面介绍对散列分片对方法对有序集合进行分片,并维护额外对最高分值对有序集合和最低分值对有序集合,然后通过 ZADD
命令为这两个有序集合添加新元素,并通过 ZREMRANGEBYRANK
命令确保元素对数量不会超过限制。 P215
分片式散列 P215
对散列的键进行划分时,可以把散列存储的键作为一个信息源,并使用散列函数为键计算出一个数字散列值,然后根据需要存储的键的总数量以及每个分片需要存储的键数量,计算出所需的分片数,最后使用分片数和散列只决定应把键存储到哪个分片里面。 P215
所思
其实我们平时在考虑分片这种形式的时候是不太会考虑到键的总数量的这种条件,基本上是根据现有的数据进行分析后设定一个分片数量 shard_num
,这样当有一个键 key
需要计算对应的分片时,只需要 cal_hash(key) % shard_num
即可得到对应的 shard_id
。但类似 CRC32
和 MD5
这种方式进行散列值时有一个问题,就是书中提到的当分片数量改变时,会有大量键的新旧散列值不同,就需要将数据迁移至新散列值对应的 shard_id
。为了避免这样的情况,就需要一致性哈希算法,使得分片数量改变时需要迁移的数据尽量小一点,并保证迁移后的数据仍能够较为均匀的在每个分片上。
将字符串存储到散列里面 P217
如果发现将很多相关联的短字符串或者数字存储到了字符串键里面,并且持续地将这些键命名为 namespace:id
这样的形式,那么可以考虑将这些值存储到分片散列里面,在某些情况下,这种做法可以明显减少内存占用。 P217
分片集合 P218
集合一样可以通过类似散列的方式处理键获得分片 id ,进而改造相应的命令支持分片式操作。
如果键是整数且最大值相对较小,那么除了直接使用键获取分片 id ,还可以使用位图 (bitmap) 记录每个键是否在“集合”中。 P221
如果键是整数,数量非常多,无法全部存下,但又能容忍一定的误差,那么可以使用布隆过滤器记录每个键是否在“集合”中(判断为不存在时,则必定不存在;判断为存在时,有极低概率不存在)。
打包存储二进制位和字节 P221
前面提到当使用类似 namespace:id
这样当字符串键去存储短字符串或者计数器时,使用分片散列可以有效降低存储这些数据所需当内存。但是,如果被存储的是一些简短并且长度固定当连续 id ,那么我们还有使用比分片散列更为节约内存当数据存储方法可用。 P221
Redis 数据结构常用命令简介 中介绍过可用于高效打包和更新 Redis 字符串的四个命令: P221
GETRANGE
: 用于读取被存储字符串的其中一部分内容SETRANGE
: 用于对存储在字符串里面的其中一部分内容进行设置GETBIT
: 用于获取字符串里面某个二进制位的值SETBIT
: 用于对字符串里面某个二进制位进行设置
通过这四个命令,我们可以在不对数据进行压缩的情况下,使用 Redis 字符串以尽可能紧凑的格式去存储计数器、定长字符串、布尔值等数据。 P221
决定被存储位置信息的格式 P221
我们以存储的信息是用户的位置信息为例,不同内存的使用量决定了不同的位置精度: P221
- 1 字节:精确到国家
- 2 字节:精确到国家及所在州/省
- 3 字节:精确到邮政编码
- 4 字节:精确到经纬度(2 米)
这里我们用 2 字节存储位置信息,首先我们可以使用一个数组存储所有国家(或地区)的 ISO3 国家(或地区)编码,然后用第一个字节存储所在国家(或地区)在数组中的下标。然后我们可以使用一个 map ,同样使用数组存储每个国家(或地区)的州/省信息,用第二个字节存储所在州/省在对应数组中的下标。 P222
存储打包后的数据 P223
获取到位置信息对应到两个字节到数据后,就可以使用 SETRANGE
命令将其存储到字符串键里面去了。但是还需要考虑用户的总量,假如用户数量达到 7.5 亿,那么需要 1.5 GB 内存存储所有用户的数据,但 Redis 的字符串键最大只能存储 512 MB 数据,并且 Redis 在对现有的字符串进行设置的时候,如果被设置的部分超过了现有字符串的末尾,那么 Redis 可能需要分配更多的内存以存储新数据,因此对一个长字符串的末尾进行设置,耗时要比执行一个简单的 SETBIT
调用多得多。为了解决上述问题,我们需要将数据分片到多个字符串键里面。 P223
我们可以在每个字符串里面存储 2^20 个用户的位置信息,这相当于在字符串里面构建 100 多万个节点,而这样的字符串需要占 2 MB 的内存。 P223
对分片字符串进行聚合计算 P224
对所有用户的位置信息进行聚合计算 P224
找到提前存储的最大的用户 id ,然后计算最大分片 id ,遍历每个字符串分片中的每个用户的数据(使用 GETRANGE
分块获取数据),根据两个字节对应的下标找到对应的国家(或地区)及州/省信息,然后统计即可。
对指定用户的位置信息进行聚合计算 P226
遍历每个指定的用户 id ,计算其对应的分片 id 和分片中的偏移量,使用 GETRANGE
获取对应的两个字节,根据两个字节对应的下标找到对应的国家(或地区)及州/省信息,然后统计即可。
本文首发于公众号:满赋诸机(点击查看原文) 开源在 GitHub :reading-notes/redis-in-action
Redis 实战 —— 12. 降低内存占用的更多相关文章
- redis实战笔记(9)-第9章 降低内存占用
本章主要内容 1.短结构( short structure) 2.分片结构( shared structure) 3.打包存储二进制位和字节 本章将介绍3种非常有价值的降低Redis内存占用的 ...
- Windows 10 彻底关闭 Antimalware Service Executable 降低内存占用
概述 最近给内网的一台电脑安装 Windows 10 专业版系统,由于此电脑不会涉及到不安全因素,所以杀毒软件非必须. 以最大限度节省系统资源考虑,默认安装的 Micoroft Defender 占用 ...
- eclipse关闭无用启动项,降低内存占用
1,我使用的eclipse版本 2.打开windows-->preference 3,勾选掉无用的启动项,我的已经去掉过了, 4,重启eclipse,如果操作后导致一些必须的功能不能用了,可以点 ...
- 选择合适Redis数据结构,减少80%的内存占用
redis作为目前最流行的nosql缓存数据库,凭借其优异的性能.丰富的数据结构已成为大部分场景下首选的缓存工具. 由于redis是一个纯内存的数据库,在存放大量数据时,内存的占用将会非常可观.那么在 ...
- redis实战笔记(3)-第3章 Redis命令
第3章 Redis命令 本章主要内容 字符串命令. 列表命令和集合命令 散列命令和有序集合命令 发布命令与订阅命令 其他命令 在每个不同的数据类型的章节里, 展示的都是该数据类型所独有的. 最 ...
- Redis实战:如何构建类微博的亿级社交平台
微博及 Twitter 这两大社交平台都重度依赖 Redis 来承载海量用户访问.本文介绍如何使用 Redis 来设计一个社交系统,以及如何扩展 Redis 让其能够承载上亿用户的访问规模. 虽然单台 ...
- Redis 实战 —— 04. Redis 数据结构常用命令简介
字符串 P39 Redis 的字符串是一个有字节组成的序列,可以存储以下 3 种类型的值:字节串(byte string).整数.浮点数. 在需要的时候, Redis 会将整数转换成浮点数.整数的取值 ...
- [2017-08-09]一则使用WinDbg工具调试iis进程调查内存占用过高的案例
最近遇到一个奇葩内存问题,跟了三四天,把Windbg玩熟了,所以打算分享下. 症状简介 我们团队的DEV开发环境只有一台4核16G的win2012r2. 这台服务器上装了SqlServer.TFS(项 ...
- 【转】一则使用WinDbg工具调试iis进程调查内存占用过高的案例
最近遇到一个奇葩内存问题,跟了三四天,把Windbg玩熟了,所以打算分享下. 症状简介 我们团队的DEV开发环境只有一台4核16G的win2012r2.这台服务器上装了SqlServer.TFS(项目 ...
随机推荐
- JAVA Executor(线程池)框架
一.Executor概述 为更好控制线程,jdk提供一套线程管理框架Executor,帮助开发人员有效地进行线程控制.它们都位于java.util.concurrent包中,是jdk并发包的核心.其中 ...
- TurtleBot3使用课程-第二节a(北京智能佳)
目录 1.[第3类]LRF(LDS)传感器 2 1.1 传感器包安装 2 1.1.1 传感器端口访问设置 2 1.1.2 运行hlds_laser_publisher节点 2 1.1.3 在RViz中 ...
- 翻译 | 30个 Python3 的最佳实践,技巧和窍门
1.使用 Python3 如果你关注 Python 的话,应该会知道 Python 2 已经于今年(2020 年)1 月 1 日正式弃用了.这份教程的很多例子都是只支持 Python 3 的,如果你还 ...
- netty心跳检测机制
既然是网络通信那么心跳检测肯定是离不开的,netty心跳检测分为读.写.全局 bootstrap.childHandler(new ChannelInitializer<SocketChanne ...
- docker基础总结
搜索镜像docker search ubuntu 搜索ubuntu的Docker镜像 搜索结果单个单词ubuntu这样的镜像,被称为基础镜像或根镜像,这些基础镜像由 Docker 公司创建搜索结果ti ...
- python3实现计算器
实验内容 1.简单计算器的设计 请设计简单的"加减乘除"计算器并从键盘上输入数据进行计算 数字的加减乘除,input返回的结果是str类型的,通过截取字符串中的运算符,来提取数字, ...
- 在 Azure 上执行一些简单的 python 工作
1. 公司禁用了 python 我的主业是桌面开发,偶尔也需要搞搞数据和算法.最近在用 python 处理一些工作,正搞得热火朝天,突然 python 就不能用了,一查记录原来是 IT 管理员禁止我使 ...
- 【ORACLE错误】SP2-0618: Cannot find the Session Identifier. Check PLUSTRACE role is enabled
执行set autotrace traceonly的时候,报错 SQL> set autotrace traceonly SP2-0618: Cannot find the Session Id ...
- C#数组的 Length 和 Count()
C#数组的 Length 和 Count() C# 数组中 Length 表示数组项的个数,是个属性.而 Count() 也是表示项的个数,是个方法,它的值和 Length 一样.但实际上严格地说, ...
- XSS类型,防御及常见payload构造总结
什么是XSS? XSS全称是Cross Site Scripting即跨站脚本,当目标网站目标用户浏览器渲染HTML文档的过程中,出现了不被预期的脚本指令并执行时,XSS就发生了. 最直接的例子:&l ...