redis实战笔记(9)-第9章 降低内存占用

本章主要内容
 
1.短结构( short structure)
2.分片结构( shared structure)
3.打包存储二进制位和字节
 
本章将介绍3种非常有价值的降低Redis内存占用的方法。 降低Redis的内存占用有助于减少创建快照和加载快照所需的时间、 提升载入AOF文件和重写AOF文件时的效率、 缩短从服务器进行同步所需的时间①,并且能让Redis存储更多的数据而无需添加额外的硬件。
 
本章首先会介绍如何
1.使用Redis的短数据结构来更高效地表示数据。
2.接着会介绍如何使用分片技术, 将一些体积较大的结构分割为多个体积较小的结构。 ②
3.最后介绍如何将固定长度的数据打包存储到字符串键里面, 从而进一步地降低内存占用。
 
笔者曾经通过同时使用本章介绍的这几种技术, 成功地将分布在3台服务器上的70多GB数据缩小至3GB, 并且只使用了 1台服务器进行存储。

9.1 短结构
Redis为列表、 集合、 散列和有序集合提供了一组配置选项, 这些选项可以让Redis以更节约空间的方式存储长度较短的结构(后面简称“短结构”) 。
 
列表、 散列和有序集合的长度较短或者体积较小的时候, Redis可以选择使用一种名为压缩列表( ziplist) 的紧凑存储方式来存储这些结构。
压缩列表是列表、 散列和有序集合这3种不同类型的对象的一种非结构化( unstructured) 表示: 与Redis在通常情况下使用双链表表示列表、 使用散列表表示散列、 使用散列表加上跳跃表( skiplist) 表示有序集合的做法不同, 压缩列表会以序列化的方式存储数据, 这些序列化数据每次被读取的时候都要进行解码, 每次被写入的时候也要进行局部的重新编码, 并且可能需要对内存里面的数据进行移动。
 
 
9.1.1 压缩列表表示
为了确保压缩列表只会在有需要降低内存占用的情况下使用, Redis引 入了代码清单9-1展示的配置选项, 这些选项决定了列表、 散列和有序集合会在什么情况下使用压缩列表表示。

 

列表、 散列和有序集合的基本配置选项都很相似, 它们都由-maxziplist-entries选项和-max-ziplist-value选项组成, 并且这3组
选项的语义也基本相同
 
entries选项说明列表、 散列和有序集合在被编码为压缩列表的情况下, 允许包含的最大元素数量;
value选项则说明了压缩列表每个节点的最大体积是多少个字节。
当这些选项设置的限制条件中的任意一个被突破的时候, Redis就会将相应的列表、 散列或是有序集合从压缩列表编码转换为其他结构, 而内存占用也会因此而增加。
 
如果用户是以默认配置方式安装Redis 2.6的话, 那么 Redis提供的默认配置将与代码清单9-1中展示的配置相同
 
跟列表、 散列和有序集合不同, 集合并没有使用压缩列表表示, 而是使用了另外一种具有不同语义和限制的紧凑表示, 接下来的一节就会对这种表示进行介绍。
 
 
 
9.1.2 集合的整数集合编码
跟列表、 散列和有序集合一样, 体积较小的集合也有自 己的紧凑表示: 如果整数包含的所有成员都可以被解释为十进制整数, 而这些整数又处于平台的有符号整数范围之内, 并且集合成员的数量又足够少的话(具体的限制大小稍后就会说明) , 那么 Redis就会以有序整数数组的方式存储集合, 这种存储方式又被称为整数集合( intset) 。
 
代码清单9-3 配置集合在使用整数集合编码时能够包含的最大元素数量

只要集合存储的整数数量没有超过配置设定的大小, Redis就会使用整数集合表示以减少数据的体积。
 
 
9.1.3 长压缩列表和大整数集合带来的性能问题
 
当一个结构突破了用户为压缩列表或者整数集合设置的限制条件时, Redis就会自 动将它转换为更为典型的底层结构类型。 这样做的主要原因在于, 随着紧凑结构的体积变得越来越大, 操作这些结构的速度也会变得越来越慢。
 
为了直接观察这个问题是如何发生的, 我们首先需要把list-maxziplist-entries选项的值设置为110000。 这个值比实际中应用的值
要大很多, 但这有助于凸显我们想要发现的问题。 在修改配置选项并重新启动Redis之后, 我们将对Redis进行性能测试, 以此来考察列表在使用长度较大的压缩列表编码时, 性能问题是如何出现的。
 
为了测试列表在使用长度较大的压缩列表作为编码时的性能表现,我们需要用到代码清单9-5展示的测试函数。 这个函数首先会创建一个列表, 并将指定数量的节点添加到列表里面, 然后反复地调用RPOPLPUSH命令, 将元素从列表的右端移动到左端, 以此来计算列表在使用长度较大的压缩列表作为编码时, 执行复杂命令时的性能下界。

初看上去, 即使压缩列表的元素数量上升至好几千, 测试得出的性能似乎也并不是太坏。 但是别忘了这只是执行单个操作时的成绩, 而这个操作所做的只不过是取出列表右端的元素然后将它推入列表的左端。尽管压缩列表在执行插入操作时需要移动所有元素的做法导致了性能下降, 但压缩列表查找左端和右端的速度并不慢, 更别说这个测试还充分地利用了 CPU缓存。 但是当Redis需要像6.1节中介绍的自 动补全例子一样, 扫描整个列表以查找某个特定值的时候, 又或者需要获取和更新散列的不同域( field) 的时候, Redis就会需要解码很多单独的节点, 而CPU缓存的作用也会因此而受到影响。
 
当列表的元素数量超过5000个时, 函数的性能将只有之前调用RPOPLPUSH命令时的一半, 有兴趣的读者可以自 己亲手去验证这一
点。
 
只要将压缩列表的长度限制在500~2000个元素之内, 并将每个元素的体积限制在128字节或以下, 那么压缩列表的性能就会处于合理范围之内。 笔者的做法是将压缩列表的长度限制在1024个元素之内, 并且每个元素的体积不能超过64字节, 对于大多数散列应用来说, 这种配置可以同时兼顾低内存占用和高性能这两方面优点。
 

9.2 分片结构
分片( sharding) 是一种广为人知的技术, 很多数据库都使用这种技术来扩展存储空间并提高自 己所能处理的负载量。 分片本质上就是基于某些简单的规则将数据划分为更小的部分, 然后根据数据所属的部分来决定将数据发送到哪个位置上面。
 
我们将把分片的概念应用到散列、 集合和有序集合上面, 并在实现这些数据结构的其中一部分标准功能的同时, 使用9.1节
中介绍的短结构以降低内存占用。 在这种情况下, 程序不再是将值X存储到键Y里面, 而是将值X存储到键Y: <shardid>里面。
 
9.2.1 分片式散列
 
 
9.2.2 分片集合

9.3 打包存储二进制位和字节
9.1节中在讨论如何对散列进行分片的时候, 文章曾经简单地提到过, 当用户使用诸如namespace: id这样的字符串键去存储短字符串或者计数器时, 使用分片散列可以有效地降低存储这些数据所需的内存。但是, 如果被存储的是一些简短并且长度固定的连续ID, 那么我们还有比使用分片散列更为节约内存的数据存储方法可用。
 
9.3.1 决定被存储位置信息的格式
 
9.3.2 存储打包后的数据
 
9.3.3 对分片字符串进行聚合计算

9.4 小结
在这一章, 我们学习了几种用于降低Redis内存占用的方法, 包括使用短结构、 通过分片将体积较大的结构重新划分为多个体积较小的结构, 以及将数据打包存储在字符串键里面。
本章希望向读者传达这样一个概念: 谨慎地选择数据的存储方式,可以有效地降低程序在使用Redis时的内存占用。
 
在接下来的一章中, 我们将对只读从服务器、 将数据分片到多个主服务器、 优化各种不同类型的查询语句等一系列主题进行回顾, 学习如何将Redis扩展到更大的机器群组上面。

① 快照、 AOF文件重写以及从服务器同步在第4章都有介绍。
本章介绍的分片技术主要用于降低单台Redis服务器的内存占用, 第10章将会介绍如何使用类似的技术提升多台Redis服务器的读写
吞吐量, 并对它们进行内存分区。
 
 
 
posted @ 2017-08-28 23:38  crazyYong  阅读(1137)  评论(0编辑  收藏  举报