4 Redis VM 技术

4.1 前言

  • 重要提示:Redis VM(virtual memory) 现在已经弃用。2.4 是最新支持 VM 的版本(但警告不鼓励使用 VM)。我们发现使用 VM 有一些劣势和问题。在将来的 Redis,我们希望简单提供最好的内存数据库(但像往常一样持久化到磁盘),至少现在不用考虑支持大于 RAM 大小的数据库。我们将来致力于提供脚本化、集群和更好的持久化
  • Redis VM 特性第一次出现在稳定的 Redis 2.0 发布版本。然而,VM 在 git 的不稳定分支上仍然可以获取,且稳定可测试

4.2 简单解释 VM

  • Redis 遵循 key-value 模型。键和一些值关联。通常,Redis 将键和相关的值保存在内存。有时这不是最好的选项,所以在设计上必须把键放在内存(为了保证快速查找),但是可以把较少使用的值交换到磁盘
  • 在实际中,这意味着如果你在内存有 100 000 个键的数据集,但是只有 10% 的键经常使用,支持 VM 的 Redis 会尝试将较少使用的键关联的值转移到磁盘。当客户端的命令请求这些值时,这些值从 swap 文件加载到主存

4.3 何时使用 VM 是个好主意

  • 在使用 VM 之前,你应该问自己你是否真的需要它。Redis 是磁盘备份,内存型数据库。正确使用 Redis 几乎总是有足够的 RAM 保存所有数据到内存。仍然有一些场景是不可能实现的
    • 数据访问非常不均匀。只有小部分的键(比如网站上相关的活跃用户)被大量访问。同时每个键有大量的数据在内存中
    • 不管数据访问模式和大量的值,只是没有足够的内存存放所有的数据。这种配置下,Redis 可当作磁盘型数据库,而键保存在内存,因此键查找很快,但是访问实际的值需要访问磁盘(较慢)
  • 需要记住一个重要的概念Redis 不能交换键,因此如果内存问题的事实是键太多而对应的值很小,VM 不是解决方案
  • 然而,如果因为值很大(比如大量的字符串、列表、集合或者有太多元素的哈希)而占用大量内存,VM 是一个好主意
  • 有时候,可以通过哈希将相关的数据组合在一个键中,从而将“键很多,值很小”的问题转换成“键很少,值很大”的问题。比如,不要为对象的每个属性设置一个键,而是每个对象一个键,用哈希表示不同的属性

4.4 VM 配置

  • 配置 VM 不难,但是需要根据需求仔细设置最好的参数
  • 通过编辑 redis.conf 开启和配置 VM:vm-enabled yes
  • 其他一些配置选项可以改变 VM 行为。规则就是不想使用默认配置运行 Redis,因为每个问题和数据集需要一些微调以达到最好的优势

4.5 设置 vm-max-memory

  • vm-max-memory 指定 Redis 在交换值到磁盘之前可以自由使用的内存大小
  • 基本上,如果没有达到这个内存限制,Redis 不会交换对象,所有对象在内存中。一旦达到这个限制,Redis 会交换足够的对象以使内存降到限制以下
  • 交换的对象主要是“年纪最大”(即未被使用的实际最长),但是一个对象的“可交换性”与它在内存中大小的对数是成比例的。因此,虽然偏向更旧的对象,当“年纪”相同时首先交换更大的对象
  • 警告:因为不能交换键,在键使用空间大于内存时,Redis 不会考虑 vm-max-memory 设置
  • vm-max-memory 最好的值是足够的 RAM 来保持数据工作集。在实际中,只要给 Redis 尽可能多的空间,交换过程更好

4.6 配置 swap 文件

  • Redis 使用交换文件将数据从内存转移到磁盘。交换文件和数据持久性无关,而且当一个 Redis 示例终止时可以被删除。但是,Redis 运行时,不应移动、删除或改变交换文件
  • 因为在随机访问方式中经常使用 Redis 交换文件,把交换文件放在 SSD(solid state disk)会达到更好的性能
  • 交换文件被分成页。一个值可以被交换到一个或多个页,但是一个页不能保存超过一个值
  • 没有直接方式告诉 Redis 应该使用多大的交换文件。而是配置两个不同的值,二者相乘得到使用的字节数。可通过 redis.conf 配置两个参数
    • vm-pages:交换文件的页数
    • vm-page-size:页的大小,以字节为单位
  • 比如页大小是 32 bytes,总页数是 10 000 000,交换文件可以保存 320 MB 的数据
  • 因为一个页不能保存超过一个值(但是一个值可以保存在多个页),必须仔细设置这些参数。通常,最好的注意是设置页大小以便大部分值可以使用较少的页交换

4.7 线程式 VM vs 阻塞式 VM

  • 另一个很重要的配置参数是 vm-max-threads。默认值是 4
  • 表示为了执行交换文件的 I/O 操作所用的线程数量最大值。一个好的值只要和系统的核数匹配即可
  • 0 会开启阻塞式 VM。当配置成阻塞式 VM 时,Redis 会以同步阻塞方式执行 I/O
    • 客户端访问交换出去的值,从磁盘读时会阻塞其他客户端,因此客户端经历的延迟会变大,尤其当磁盘慢或者忙,或者磁盘有大的交换的值
    • 阻塞式 VM 的性能总的来说较好,因为没有同步、线程创建、恢复等待值的阻塞客户端的时间损失。因此,如果愿意接受较高的延迟,阻塞式 VM 是个好的选择。尤其是交换很少发生,且大部分访问的数据都在内存时
  • 相反,如果有大量的交换操作,且有许多核想要利用,而且通常不希望处理交换值的客户端阻塞其他客户端几毫秒(交换值很大的时候时间更长),最好使用线程式 VM
  • 鼓励使用不同的配置对数据集做实验。。。

4.8 需要知道的一些事

4.8.1 swap 文件的好位置

  • 在很多配置中,交换文件可以很大,达到 40GB 甚至更大。不是所有类型的文件系统可以较好的处理大文件,尤其是 Mac OS X 文件系统在处理大文件方面比较差
  • 建议使用 Linux ext3 文件系统,或者其他较好支持稀疏文件(sparse files)的文件系统。什么是稀疏文件呢?
    • 稀疏文件大部分内容是空白的。高级的文件系统如 ext2,ext3,ext4,ReiserFS,Reiser4 等可以更有效地编码这些文件,并且在需要的时候为文件分配更多的空间,即文件更多的实际块被使用
  • 交换文件显然是非常稀疏的,尤其是当服务运行时间较短,或者相比交换出去的数据更大时。一个不支持稀疏文件的文件系统创建一个大文件时,有时会阻塞 Redis 流程

4.8.2 监视 VM

  • 当有一个开启 VM 的 Redis 系统允许时,可能对它如何工作感兴趣:总共交换了多少对象,每秒交换和加载的对象数目等
  • 有一个工具方便检查 VM 如何允许,是 Redis 工具的一部分。这个工具叫做 redis-stat,使用方式很直接:./redis-stat vmstat

    ./redis-stat vmstat
    --------------- objects --------------- ------ pages ------ ----- memory -----
    load-in  swap-out  swapped   delta      used     delta      used     delta
    138837   1078936   800402    +800402    807620   +807620    209.50M  +209.50M
    4277     38011     829802    +29400     837441   +29821     206.47M  -3.03M
    3347     39508     862619    +32817     870340   +32899     202.96M  -3.51M
    4445     36943     890646    +28027     897925   +27585     199.92M  -3.04M
    10391    16902     886783    -3863      894104   -3821      200.22M  +309.56K
    8888     19507     888371    +1588      895678   +1574      200.05M  -171.81K
    8377     20082     891664    +3293      899850   +4172      200.10M  +53.55K
    9671     20210     892586    +922       899917   +67        199.82M  -285.30K
    10861    16723     887638    -4948      895003   -4914      200.13M  +312.35K
    9541     21945     890618    +2980      898004   +3001      199.94M  -197.11K
    9689     17257     888345    -2273      896405   -1599      200.27M  +337.77K
    10087    18784     886771    -1574      894577   -1828      200.36M  +91.60K
    9330     19350     887411    +640       894817   +240       200.17M  -189.72K
    
  • 上述输出的 redis 服务开启了 VM,大约有 1 百万键,且有大量的同步加载使用 redis-load 工具

  • 可以从输出中看到,每秒都发生一些 load-in 和 swap-out 操作。注意第一行表示服务启动后实际的值,后续的行和前面的不一样

  • 如果分配足够的内存来保存数据工作集,可能应该看到更少的交换发送,因为 redis-stat 是一个很有价值的工具来理解是否需要去商店购买 RAM

4.8.3 开启 VM 的 Redis:.rdb 文件 和 只能追加的文件哪个好

  • 当开启 VM 时,保存和加载数据库是相当慢的操作。如果服务配置成使用最小的内存(即 vm-max-memory 设置成 0),在开启 VM 后,在 2 秒内加载的数据库通常需要 13 秒时间加载
  • 因此,你可能想要切换配置使用只能追加的文件(Append Only File)来持久化,以便于你可以一直执行 BGREWRITEAOF
  • 需要注意当一个 BGSAVEBGREWRITEAOF 在处理时,Redis 不会在磁盘上交换新的值。当有一个子进程访问 VM 时 VM 是只读的。因此如果一个工作的子进程有大量的写操作时,内存使用会增长
    • 子进程在读 VM 时,主进程不能进行值交换操作。因为通常读完一次值,“年龄”变小,可能从 VM 取出,而交换新的值

4.8.4 尽可能少的使用内存

  • 将 Redis 设置成磁盘型数据库,而只保存键在内存的一个有趣的设置是设置 vm-max-memory 为 0。如果不介意更多延迟和较差的性能,但是想要非常大的值使用更少的内存,这个是好的设置
  • 这种设置情况,应该首先尝试设置 VM 是阻塞式的(vm-max-threads 为 0),因为大业务量会导致很多交换操作,且和简单的阻塞式实现相比,线程会消耗大量的资源

4.9 VM 稳定性

  • VM 仍然是实验性代码,但是在过去的几周,在开发环境下用各种方式测试了 VM,甚至在一些生产环境。在测试阶段没有注意到 bug。但是在一些未控制的环境,且出于某些原因无法复现这些设置,会出现更加模糊的 bug
  • 在这个阶段,鼓励在开发环境尝试 VM,甚至是生产环境下,当数据库不是关键型任务,比如大量持久化数据可以小时而不会有任何问题

4.10 参考

4.11 其他内容

4.11.1 Redis 对象和 VM pointer

  • 键值都是 Redis 的对象,但是当值被移到 VM 时,会变成 VM pointer。主要记录值在磁盘的信息,如记录对象在交换文件第几页、共使用几页等内容
  • Redis 对象和 VM pointer 都有一个字段 storage,用于判断值的位置
    • redis_vm_memory:在内存
    • redis_vm_swapped:在磁盘
    • redis_vm_loading:在磁盘,但目前正有进程将其加载到内存
    • redis_vm_swapping:在内存,但目前正有进程将其写入磁盘

4.11.2 交换过程

  • 将对象交换到交换文件
    • 计算保存此对象需要占用多少页
    • 在交换文件中寻找一段连续空间保存此对象
    • 把对象写入交换文件
  • 将对象从交换文件取出到内存
    • VM pointer 记录了对象在文件的起始页和占用页数,直接加载到内存即可
  • 阻塞式 VM 在内存使用超过设定的 vm-max-memory 时,会循环找到候选对象进行交换直到内存使用下降到设定值以下

相关