FAQ
为什么与其他的键值存储系统相比,Redis是与众不同的?
主要有两个原因:
- 在键值对数据库中,Redis有不同的演化路径,它的值包含了更复杂的数据类型,并且在这些类型上可以执行原子操作。Redis的数据结构和基础的数据结构是紧密相关的,它直接暴露给程序员,没有额外的抽象层。
- Redis是在内存操作但是可以把数据持久化到磁盘的数据库,因此他代表了不同的折中方案:即在不大于内存大小的数据集上实现快速的读写操作。内存数据库的另外一大优势是在内存中来表示复杂的数据结构比在硬盘中操作同样的数据结构要简单得多,因此Redis可以做很多事情而没有内部的复杂性。与此同时,存储在硬盘中的两种格式的文件(RDB和AOF)并不需要可以被随机访问,因此它们是紧凑的,并且可以一直以追加的形式产生(甚至AOF日志的循环也是一个追加操作,因为新的版本是从内存数据中复制过来的)。然而与传统的硬盘存储相比,这种设计理念同样遇到了挑战。作为内存中的主要数据表现形式,Redis的操作必须非常小心以确保在磁盘上总是有该数据集的更新版本。
Redis的内存占用情况如何?
给你展示一些样例(所有这些例子都是从64位的Redis实例中获取的):
- 一个空的实例使用3MB内存
- 100万个小的键->字符串值对使用85M内存
- 100万个键->散列值对,散列拥有5个字段,使用160MB内存
去测试你自己的用例是很简单的,只需要使用redis-benchmark这个功能来产生一些随机数据集,然后使用INFO这个内存命令来查看占用了多少内存空间即可。
存储相同的键值对时,64位的系统会比32位的系统占用的内存大得多,尤其是在键值都比较小的时候。这是因为在64位系统中指针会占用8个字节的空间。但是64位系统的优势当然是你会拥有更多的内存空间,所以为了运行大型的Redis服务,我们或多或少需要一个64位的系统。另外一个选择是使用分片技术。
我喜欢Redis高水平的操作和功能,但是我不喜欢它所有的东西都放在内存中,以至于我的数据集大小不能超过内存大小。有计划来改变这一点吗?
为了能够让数据集大小不受内存大小的限制,Redis的开发者曾经使用虚拟内存和其它系统做过实验,但最后我们发现只要能做好一件事我们就很开心了:数据从内存中提供服务,但是保存在硬盘上。因此到目前为止,我们并没有计划去创建一个基于磁盘的Redis。毕竟大多数Redis都是当前设计的直接结果。
如果你的真正问题不是需要一个总的RAM,而是需要将你的数据分散到多个Redis实例中,请阅读Partitioning page来获取更多信息。
最近,Redis实验室,也就是赞助Redis开发的公司,提供了一种"Redis on flash"的解决方案,它可以使用一种混合了RAM/flash的方式来获得更大的数据集。
将Redis和基于硬盘的数据库一起使用是个好主意吗?
是的,一种常见的设计模式是需要经常写的小数据放在Redis(还有那些你需要使用Redis的数据结构来对问题进行高效建模的数据),而将那些大的数据块放入SQL中或是可以保持最终一致性的硬盘数据库中。与此相似,有时候为了将存储在硬盘数据库中的数据的某个子集在内存中保存一份副本,也会使用Redis。这看起来可能很像缓存,但是实际上它是一种更加高级的模型,因为通常情况下Redis中的数据集是跟硬盘数据库中的数据集同步更新的,而不是依赖于缓存的刷新任务。
有什么办法可以降低Redis的内存占用吗?
如果可以,你可以使用32位的Redis。同时,你可以更好地利用小的散列、列表、有序集合和集合,因为在只有少量元素的特殊情况下,Redis可以以一种更加紧凑的方式来表示这些类型的数据。更多信息请移步Memory Optimization page.
如果Redis内存耗尽了会发生什么?
Redis可能会被Linux内核的OOM进程杀死,或是抛出错误而崩溃,也有可能会导致性能变差。在现代操作系统中,malloc()函数返回NULL是不常见的,通常情况下服务器会启动交换(如果配置了一些交换空间的话),但这会导致Redis的性能开始变差,所以你可能会发现有什么地方不对。
Redis已经建立了一套保护机制,允许用户设置一个最大的内存使用限制,这可以通过在配置文件中设置maxmemory选项来限制Redis可以使用的内存。如果达到了这个限制,再向Redis写命令,Redis就会返回一个错误(但是仍然可以接受只读命令),或者你可以配置一个驱逐键,当达到最大内存使用限制后,你就只是在将Redis当做缓存来使用。
关于这一点,我们有详细的文档介绍,请移步Redis as an LRU cache.
使用INFO命令可以查看Redis当前占用内存的数量,因此你可以写脚本来监控你的Redis服务器,在他们出现极端情况之前检测出来。
在Linux环境下,即使有很多空闲的RAM,通过fork()在后台执行保存还是会失败!
简短的回答:echo 1 > /proc/sys/vm/overcommit_memory:)
较长的回答是这样的:
Redis的后台保存架构依赖于fork的copy-on-write在现代操作系统中的语义:Redis的fork(创建一个子进程)是父级的一个精确拷贝。子进程转储磁盘上的数据库并最终退出。作为一个副本,理论上子进程应该使用与父进程一样多的内存,然而实际上由于copy-on-write的语义在大多数现代操作系统中的实现,父子进程会共享内存页。只有当内存数据在父进程或子进程中发生改变时,内存页才会被复制。因为理论上在子进程进行保存时所有的内存页都会发生变化,Linux不能提前知道到底会消耗多少内存,所以如果overcommit_memory被设置为0,fork命令就会失败,除非有足够的RAM可以用来复制父进程的所有内存页,因此如果你在Redis中有3GB的数据而只有2GB的空闲内存,执行fork时就会失败。
将overcommit_memory设置为1,Linux就将以更加乐观的分配方式来执行fork命令,而这正是你想要的。
一种很好的了解Linux虚拟内存工作原理和overcommit_memory、overcommit_ratio替代方案的来源是这篇来自红帽杂志的"Understanding Virtual Memory"。请注意,这篇文章中对overcommit_memory的两个配置1、2的描述写反了:关于不同取值的正确含义请参考proc(5)。
Redis的on-disk-snapshots是原子性的吗?
是的。当Redis没有执行命令时,后台保存进程总是被fork,因此在RAM里每一个原子性的命令在磁盘快照里也是原子性的。
Redis是单线程的。那么我怎样才能利用多个CPU/核?
CPU成为你的性能瓶颈这种情况并不常见,因为通常情况下Redis并没有与内存或者网络绑定。例如,通过使用流水线技术,在一个普通的Linux系统上运行的Redis每秒也可以执行100万个请求,所以如果你的应用主要使用O(N)或者是O(log(N))的命令,它将很难使用到太多的CPU。
然而,为了最大限度提高CPU的使用率,你可以在同一个服务器启动多个Redis实例,然后把他们作为不同的服务器进行使用。在某些时候,一个服务器可能是不够用的,所以如果你想使用多个CPU,你可以开始考虑使用分片。
你可以在Partitioning page找到更多关于使用多个Redis实例的信息。
然而在Redis4.0中,我们开始让Redis支持多线程。目前该功能仅限于在后台删除对象和通过Redis模块实现的阻塞命令中。在下一次发布中,我们计划让Redis在更多的场景中支持多线程。
单个Redis实例最多可以存储多少个键?在散列、列表、集合和有序集合中,最大的元素数量分别是多少?
理论上Redis可以处理2的32次方个键,在实际测试中,每个Redis实例最少也可以处理2.5亿个键。
每个散列、列表、集合和有序集合都可以存储2的32次方个元素。
换句话说,Redis的存储限制受限于你系统中的可以用内存大小。
从机拥有的键数量与主机不同,这是为什么?
如果你使用了带有存活时间的键(使用了Redis的expire命令),这就是正常的现象。事情真相是这样的:
- 第一次与从机同步的时候,主机生成了一个RDB文件。
- RDB文件不会包含在主机中已过期但是在内存中仍然存在的键。
- 即使从逻辑上讲,键已经过期了,但是这些键依然存在于主机的内存当中。它们被认为是不存在的,但是内存并不会立刻被回收,包括增量和显式访问。然而这些键已经不是数据集的一部分了,但是在INFO命令的输出和DBSIZE命令中还包含有它们的信息。
- 当从机读取主机生成的RDB文件时,这些已经过期的键并不会被加载。
由于上述原因,对于那些使用了很多过期键的用户来说,从机显式的键比主机少这种情况非常常见,但是在实例内容中,两者并没有任何逻辑上的区别。
2017-10-07
原文链接:https://redis.io/topics/faq#are-redis-on-disk-snapshots-atomic