同样类似的参数还有:
list-max-ziplist-entries 512
说明:list 数据类型多少节点以下会采用去指针的紧凑存储格式。
list-max-ziplist-value 64
说明:list 数据类型节点值大小小于多少字节会采用紧凑存储格式。
set-max-intset-entries 512
说明:set 数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。
最后想说的是 Redis 内部实现没有对内存分配方面做过多的优化,在一定程度上会存在内存碎片,不过大多数情况下这个不会成为 Redis 的性能瓶 颈,不过如果在 Redis 内部存储的大部分数据是数值型的话,Redis 内部采用了一个 shared integer 的方式来省去分配内存的开销,即在系统启动时先分配一个从 1~n 那么多个数值对象放在一个池子中,如果存储的数据恰好是这个数值范围内的数据,则直接从池子里取出该对象,并且通过引用计数的方式来共享,这样在系统存储了大量数值下,也能一定程度上节省内存并且提高性能,这个参数值 n 的设置需要修改源代码中的一行宏定义 REDIS_SHARED_INTEGERS,该值 默认是 10000,可以根据自己的需要进行修改,修改后重新编译就可以了。
Redis 的持久化机制
Redis 由于支持非常丰富的内存数据结构类型,如何把这些复杂的内存组织方式持久化到磁盘上是一个难题,所以 Redis 的持久化方式与传统数据库的方式有比较多的差别,Redis 一共支持四种持久化方式,分别是:
– 定时快照方式(snapshot)
– 基于语句追加文件的方式(aof)
– 虚拟内存(vm)
– Diskstore 方式
在设计思路上,前两种是基于全部数据都在内存中,即小数据量下提供磁盘落地功能,而后两种方式则是作者在尝试存储数据超过物理内存时,即大数据量的数据存储,截止到本文,后两种持久化方式仍然是在实验阶段,并且 vm 方式基本已经被作者放弃,所以实际能在生产环境用的只有前两种,换句话说 Redis 目前还只能作为小数据量存储(全部数据能够加载在内存中),海量数据存储方面并不是 Redis 所擅长的领域。下面分别介绍下这几种持久化方式:
定时快照方式(snapshot):
该持久化方式实际是在 Redis 内部一个定时器事件,每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件,如果满足则通过操作系统 fork 调用来创建出一个子进程,这个子进程默认会与父进程共享相同的地址空间,这时就可以通过子进程来遍历整个内存来进行存储操作,而主进程则仍然可以提供服务,当有写入时由操作系统按照内存页(page)为单位来进行 copy-on-write 保证父子进程之间不会互相影响。
该持久化的主要缺点是定时快照只是代表一段时间内的内存映像,所以系统重启会丢失上次快照与重启之间所有的数据。
基于语句追加方式(aof):
aof 方式实际类似 mysql 基于语句的 binlog 方式,即每条会使 Redis 内存数据发生改变的命令都会追加到一个 log 文件中,也就是说这个 log 文件就是 Redis 的持久化数据。
aof 的方式的主要缺点是追加 log 文件可能导致体积过大,当系统重启恢复数据时如果是 aof 的方式则加载数据会非常慢,几十G的数据可能需要几小时才能加载完,当然这个耗时并不是因为磁盘文件读取速度慢,而是由于读取的所有命令都要在内存中执行一遍。另外由于每条命令都要写 log,所以使用 aof 的方式,Redis 的读写性能也会有所下降。
虚拟内存方式:
虚拟内存方式是 Redis 来进行用户空间的数据换入换出的一个策略,此种方式在实现的效果上比较差,主要问题是代码复杂,重启慢,复制慢等等,目前已经被作者放弃。
diskstore 方式:
diskstore 方式是作者放弃了虚拟内存方式后选择的一种新的实现方式,也就是传统的 B-tree 的方式,目前仍在实验阶段,后续是否可用我们可以拭目以待。
Redis 持久化磁盘 IO 方式及其带来的问题
有 Redis 线上运维经验的人会发现 Redis 在物理内存使用比较多,但还没有超过实际物理内存总容量时就会发生不稳定甚至崩溃的问题,有人认为是基于快照方式持久化的 fork 系统调用造成内存占用加倍而导致的,这种观点是不准确的,因为 fork 调用的 copy-on-write 机制是基于操作系统页这个单位的,也就是只有有写入的脏页会被复制,但是一般你的系统不会在短时间内所有的页都发生了写入而导致复制,那么是什么原因导致 Redis 崩溃的呢?