Redis高级应用--降低内存占用

降低redis内存占用的优点

  1、有助于减少创建快照和加载快照所用的时间
  2、提升载入AOF文件和重写AOF文件时的效率
  3、缩短从服务器同步时间
  4、无需添加额外的硬件

短结构

  Redis为列表、集合、散列、有序集合提供了一组配置选项,这些选项可以让Redis以更节约的方式存储较短的结构(简称为:短结构)。
  在列表、散列和有序集合的长度较短或者体积较小的时候,Redis提供一种压缩列表(ziplist)的紧凑存储方式来存储。压缩列表是一种非结构化表(unstructured)示:Redis通常使用双链表表示列表、使用散列表表示散列、散列表加上跳跃表表示有序集合。压缩列表会以序列化的方式存储数据,每次读取压缩列表都会进行解码,每次写入也要进行编码。
  跟列表、散列和有序集合一样,体积较小的集合也有自己的表示方式:如果整数包含的所有成员都能解释为十进制整数,而这些整数又处于平台有的符号整数范围之内,并且集合成员数量又少,那么Redis就会以有序整数数组的方式存储集合,称为整数集合(intset)

压缩列表

  以简单的列表结构对比压缩列表,了解压缩列表比列表结构更省内存的原因。
  在典型双向列表里面,每个值都都会有一个节点表示。每个节点都会带有指向链表前一个节点和后一个节点的指针,以及一个指向节点包含的字符串值的指针。
  每个节点包含的字符串值都会分为三部分进行存储。包括字符串长度、字符串值中剩余可用字节数量、以空字符结尾的字符串本身。
  在32位的平台下每存储一个字符串至少需要21个字节的额外开销(三个指针+两个int+空字符即:3 x 4 + 2 x 4 +1 = 21)。
图片来源:《Redis实战》
  压缩列表是由节点组成的序列,每个节点包含两个长度和一个字符串。第一个长度记录前一个节点的长度(用于对压缩列表从后向前遍历);第二个长度是记录本当前点的长度;被存储的字符串。

使用压缩列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

############################### ADVANCED CONFIG ###############################

# Hashes are encoded using a memory efficient data structure when they have a
# small number of entries, and the biggest entry does not exceed a given
# threshold. These thresholds can be configured using the following directives.
hash-max-ziplist-entries 512 #表示允许包含的最大元素数量
hash-max-ziplist-value 64 #表示压缩节点允许存储的最大体积

# Similarly to hashes, small lists are also encoded in a special way in order
# to save a lot of space. The special representation is only used when
# you are under the following limits:
list-max-ziplist-entries 512
list-max-ziplist-value 64

# Similarly to hashes and lists, sorted sets are also specially encoded in
# order to save a lot of space. This encoding is only used when the length and
# elements of a sorted set are below the following limits:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

  列表、散列和有序集合的配置都相似,由max-ziplist-entries和max-ziplist-value组成,entries表示在压缩列表的情况下,所允许包含的最大元素数量。value表示压缩列表中每个结点允许存储的最大体积。当超过任一限制后,将不会使用ziplist方式进行存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$redis=new Redis();
$redis->connect('192.168.28.131','6379');
$stime=microtime(true);
for ($i=0; $i<512; $i++)
{
$redis->lpush('test',$i); #往test-list推入512条数据
}

$etime=microtime(true); #获取程序执行结束的时间
$total=$etime-$stime; #计算差值
echo '完成<br/>';
echo "<br />".$total."times";
?>

使用代码测试一下,首先使用flushall清空所有数据。然后执行程序,执行玩之后使用debug object test查看信息,使用的是ziplist。然后将上面的循环中的512改成513,上面配置中list-max-ziplist-entries 512。清空数据,再次运行,可以看到程序使用linkedlist方式进行存储。

压缩列表

集合的整数集合编码

前提条件是集合中包含的所有member都可以被解析为十进制整数。以有序数组的方式存储集合不仅可以降低内存消耗,还可以提升集合操作的执行速度。

1
2
3
4
5
6
# Sets have a special encoding in just one case: when a set is composed
# of just strings that happen to be integers in radix 10 in the range
# of 64 bit signed integers.
# The following configuration setting sets the limit in the size of the
# set in order to use this special memory saving encoding.
set-max-intset-entries 512
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$redis=new Redis();
$redis->connect('192.168.28.131','6379');
$stime=microtime(true);
for ($i=0; $i<512; $i++)
{
$redis->sadd('test-set',$i);
}

$etime=microtime(true); #获取程序执行结束的时间
$total=$etime-$stime; #计算差值
echo '完成<br/>';
echo "<br />".$total."times";
?>

集合的整数集合编码

长压缩列表和大整数集合带来的性能问题

  不管列表、散列、有序集合、集合,当超出限制的条件后,就会转换为更为典型的底层结构类型。因为随着紧凑结构的体积不断变大,操作这些结构的速度将会变得越来越慢。
  在这里我就不实际的测试性能变化了,根据《Redis实战》这本书中的结论,将压缩列表的长度限制在500~2000个元素之内,并将每个元素的体积限制在128字节或以下,压缩表的性能就会处于合理范围内。

分片结构

  分片的本质就是基于简单的规则将数据划分为更小的部分,然后根据数据所属的部分来决定将数据发送到哪个位置上。很多数据库使用这种技术来扩展存储空间,并提高自己所能处理的负载量。
  结合前面讲到的,我们不难发现分片结构对于redis的重要意义。因此我们需要在配置文件中关于ziplist以及intset的相关配置做出适当的调整。

分片式散列

  散列分片主要是根据基础键以及散列包含的键计算出分片键ID,然后再与基础键拼接成一个完整的分片键。在执行hset与hget以及大部分hash命令时,都需要先将key(field)通过shardKey方法处理,得到分片键才能够进行下一步操作。

分片式集合

  如何构造分片式集合才能够让它更节省内存,性能更加强大呢?主要的思路就是,将集合里面的存储的数据尽量在不改变其原有功能的情况下转换成可以被解析为十进制的数据。根据前面所讲到的,当集合中的所有成员都能够被解析为十进制数据时,将会采用intset存储方式,这不仅能够节省内存,而且还可以提高响应的性能。
例子:
  假若要某个大型网站需要存储每一天的唯一用户访问量。那么就可以使用将用户的唯一标识符转化成十进制数字,再存入分片式set中。

学习资料:《redis实战》