我试着用一个明确的清单回答两个问题:

Redis使用的底层数据结构是什么? 每种类型的主要优点/缺点/用例是什么?

我读过Redis列表实际上是用链表实现的。但对于其他类型,我无法挖掘出任何信息。此外,如果有人无意中发现了这个问题,并且对修改或访问不同数据结构的利弊没有一个高层次的总结,那么他们也会有一个完整的列表,知道什么时候最好使用特定类型来引用。

具体来说,我希望概述所有类型:字符串,列表,集,zset和散列。

哦,到目前为止,我已经看过这些文章了:

http://redis.io/topics/data-types http://redis.io/topics/data-types-intro http://redis.io/topics/faq


当前回答

I'll try to answer your question, but I'll start with something that may look strange at first: if you are not interested in Redis internals you should not care about how data types are implemented internally. This is for a simple reason: for every Redis operation you'll find the time complexity in the documentation and, if you have the set of operations and the time complexity, the only other thing you need is some clue about memory usage (and because we do many optimizations that may vary depending on data, the best way to get these latter figures are doing a few trivial real world tests).

但既然你问了,这里是每个Redis数据类型的底层实现。

字符串是使用C动态字符串库实现的,因此我们不需要(渐进地说)在追加操作中分配。这样我们就有O(N)个追加,而不是二次行为。 列表是用链表实现的。 集合和哈希是通过哈希表实现的。 排序集是用跳跃表(一种特殊类型的平衡树)实现的。

But when lists, sets, and sorted sets are small in number of items and size of the largest values, a different, much more compact encoding is used. This encoding differs for different types, but has the feature that it is a compact blob of data that often forces an O(N) scan for every operation. Since we use this format only for small objects this is not an issue; scanning a small O(N) blob is cache oblivious so practically speaking it is very fast, and when there are too many elements the encoding is automatically switched to the native encoding (linked list, hash, and so forth).

但你的问题并不仅仅是关于内部的,你的观点是用什么类型来完成什么?

字符串

这是所有类型的基类型。它是四种类型之一,但也是复杂类型的基类型,因为List是字符串的列表,Set是字符串的集合,等等。

在所有想要存储HTML页面的明显场景中,Redis字符串都是一个好主意,但当你想避免转换已经编码的数据时也是如此。例如,如果你有JSON或MessagePack,你可以将对象存储为字符串。在Redis 2.6中,你甚至可以使用Lua脚本操作这种对象服务器端。

字符串的另一个有趣的用法是位图,通常是字节的随机访问数组,因为Redis导出命令来访问字节的随机范围,甚至单个位。例如,查看这篇优秀的博客文章:使用Redis快速轻松实时度量。

列表

当你可能只接触到列表的极端时,列表是好的:接近尾部或接近头部。列表不太适合分页,因为随机访问很慢O(N)。 因此,列表的良好用途是普通队列和堆栈,或者使用具有相同源和目标的RPOPLPUSH在循环中处理项,以“旋转”一圈项。

当我们只想创建一个包含N个元素的上限集合时(通常只访问顶部或底部的元素),或者当N很小时,列表也很有用。

Sets

集合是一个无序的数据集合,所以每当你有一个项目集合时,它们都是很好的,并且以非常快速的方式检查集合的存在性或大小是非常重要的。关于集合的另一件很酷的事情是支持查看或弹出随机元素(SRANDMEMBER和SPOP命令)。

集合也可以很好地表示关系,例如,“用户X的朋友是谁?”等等。但是其他好的数据结构是我们会看到的排序集。

集支持复杂的操作,如交叉,并,等等,所以这是一个很好的数据结构使用Redis在“计算”的方式,当你有数据,你想要对数据执行转换,以获得一些输出。

小集合以一种非常有效的方式编码。

散列

哈希是表示对象的完美数据结构,由字段和值组成。还可以使用HINCRBY对哈希字段进行原子增量。当您拥有用户、博客文章或其他类型的项目等对象时,如果您不想使用自己的编码(如JSON或类似),则哈希可能是一种方法。

然而,请记住,Redis对小散列进行了非常有效的编码,你可以要求Redis以非常快的方式原子地GET, SET或增加单个字段。

哈希还可以使用引用来表示链接的数据结构。例如,检查lamernews.com的注释实现。

排序集

排序集是除列表之外唯一用于维护有序元素的数据结构。你可以用排序集做很多很酷的事情。例如,你可以在你的web应用程序中有各种Top Something列表。排名靠前的用户,排名靠前的文章,排名靠前的等等,但是一个Redis实例每秒将支持大量的插入和获取Top -elements操作。

与常规集一样,排序集可用于描述关系,但它们也允许您对项目列表进行分页并记住顺序。例如,如果我用一个排序集记住用户X的朋友,我可以很容易地按照接受的友谊顺序记住他们。

排序集适用于优先级队列。

排序集就像更强大的列表,从列表中间插入、删除或获取范围总是很快。但是它们使用更多的内存,并且是O(log(N))数据结构。

结论

我希望我在这篇文章中提供了一些信息,但最好从http://github.com/antirez/lamernews下载lamernews的源代码,并了解它是如何工作的。Lamer News内部使用了许多来自Redis的数据结构,并且有许多关于使用什么来解决给定任务的线索。

抱歉有语法错误,现在是午夜,太累了,没有时间看这篇文章;)

其他回答

I'll try to answer your question, but I'll start with something that may look strange at first: if you are not interested in Redis internals you should not care about how data types are implemented internally. This is for a simple reason: for every Redis operation you'll find the time complexity in the documentation and, if you have the set of operations and the time complexity, the only other thing you need is some clue about memory usage (and because we do many optimizations that may vary depending on data, the best way to get these latter figures are doing a few trivial real world tests).

但既然你问了,这里是每个Redis数据类型的底层实现。

字符串是使用C动态字符串库实现的,因此我们不需要(渐进地说)在追加操作中分配。这样我们就有O(N)个追加,而不是二次行为。 列表是用链表实现的。 集合和哈希是通过哈希表实现的。 排序集是用跳跃表(一种特殊类型的平衡树)实现的。

But when lists, sets, and sorted sets are small in number of items and size of the largest values, a different, much more compact encoding is used. This encoding differs for different types, but has the feature that it is a compact blob of data that often forces an O(N) scan for every operation. Since we use this format only for small objects this is not an issue; scanning a small O(N) blob is cache oblivious so practically speaking it is very fast, and when there are too many elements the encoding is automatically switched to the native encoding (linked list, hash, and so forth).

但你的问题并不仅仅是关于内部的,你的观点是用什么类型来完成什么?

字符串

这是所有类型的基类型。它是四种类型之一,但也是复杂类型的基类型,因为List是字符串的列表,Set是字符串的集合,等等。

在所有想要存储HTML页面的明显场景中,Redis字符串都是一个好主意,但当你想避免转换已经编码的数据时也是如此。例如,如果你有JSON或MessagePack,你可以将对象存储为字符串。在Redis 2.6中,你甚至可以使用Lua脚本操作这种对象服务器端。

字符串的另一个有趣的用法是位图,通常是字节的随机访问数组,因为Redis导出命令来访问字节的随机范围,甚至单个位。例如,查看这篇优秀的博客文章:使用Redis快速轻松实时度量。

列表

当你可能只接触到列表的极端时,列表是好的:接近尾部或接近头部。列表不太适合分页,因为随机访问很慢O(N)。 因此,列表的良好用途是普通队列和堆栈,或者使用具有相同源和目标的RPOPLPUSH在循环中处理项,以“旋转”一圈项。

当我们只想创建一个包含N个元素的上限集合时(通常只访问顶部或底部的元素),或者当N很小时,列表也很有用。

Sets

集合是一个无序的数据集合,所以每当你有一个项目集合时,它们都是很好的,并且以非常快速的方式检查集合的存在性或大小是非常重要的。关于集合的另一件很酷的事情是支持查看或弹出随机元素(SRANDMEMBER和SPOP命令)。

集合也可以很好地表示关系,例如,“用户X的朋友是谁?”等等。但是其他好的数据结构是我们会看到的排序集。

集支持复杂的操作,如交叉,并,等等,所以这是一个很好的数据结构使用Redis在“计算”的方式,当你有数据,你想要对数据执行转换,以获得一些输出。

小集合以一种非常有效的方式编码。

散列

哈希是表示对象的完美数据结构,由字段和值组成。还可以使用HINCRBY对哈希字段进行原子增量。当您拥有用户、博客文章或其他类型的项目等对象时,如果您不想使用自己的编码(如JSON或类似),则哈希可能是一种方法。

然而,请记住,Redis对小散列进行了非常有效的编码,你可以要求Redis以非常快的方式原子地GET, SET或增加单个字段。

哈希还可以使用引用来表示链接的数据结构。例如,检查lamernews.com的注释实现。

排序集

排序集是除列表之外唯一用于维护有序元素的数据结构。你可以用排序集做很多很酷的事情。例如,你可以在你的web应用程序中有各种Top Something列表。排名靠前的用户,排名靠前的文章,排名靠前的等等,但是一个Redis实例每秒将支持大量的插入和获取Top -elements操作。

与常规集一样,排序集可用于描述关系,但它们也允许您对项目列表进行分页并记住顺序。例如,如果我用一个排序集记住用户X的朋友,我可以很容易地按照接受的友谊顺序记住他们。

排序集适用于优先级队列。

排序集就像更强大的列表,从列表中间插入、删除或获取范围总是很快。但是它们使用更多的内存,并且是O(log(N))数据结构。

结论

我希望我在这篇文章中提供了一些信息,但最好从http://github.com/antirez/lamernews下载lamernews的源代码,并了解它是如何工作的。Lamer News内部使用了许多来自Redis的数据结构,并且有许多关于使用什么来解决给定任务的线索。

抱歉有语法错误,现在是午夜,太累了,没有时间看这篇文章;)

Redis存储指向值的键。键可以是任意大小合理的二进制值(为了可读性和调试目的,建议使用短ASCII字符串)。值是Redis的五种原生数据类型之一。

1.strings -一个最大512 MB的二进制安全字节序列 2.哈希-键值对的集合 3.列表——字符串的插入顺序集合 4.集合——没有顺序的唯一字符串的集合 5.排序集-由用户定义评分排序的唯一字符串的集合

字符串

Redis字符串是一个字节序列。

Redis中的字符串是二进制安全的(意味着它们有一个已知的长度,而不是由任何特殊的终止字符决定的),所以你可以在一个字符串中存储最多512兆字节的任何东西。

字符串是典型的“键值存储”概念。你有一个指向值的键,其中键和值都是文本或二进制字符串。

有关字符串的所有可能操作,请参见 http://redis.io/commands/#string

散列

Redis哈希是键值对的集合。

Redis哈希包含许多键值对,其中每个键和值都是一个字符串。Redis哈希不直接支持复杂值(这意味着,你不能让一个哈希字段有一个列表或集合或其他哈希值),但你可以使用哈希字段指向其他顶级复杂值。您可以对哈希字段值执行的唯一特殊操作是数字内容的原子增量/减量。

你可以用两种方式来考虑Redis哈希:一种直接的对象表示,一种紧凑地存储许多小值的方式。

直接对象表示很容易理解。对象有一个名称(散列的键)和一组带值的内部键。请看下面的例子。

Storing many small values using a hash is a clever Redis massive data storage technique. When a hash has a small number of fields (~100), Redis optimizes the storage and access efficency of the entire hash. Redis's small hash storage optimization raises an interesting behavior: it's more efficient to have 100 hashes each with 100 internal keys and values rather than having 10,000 top level keys pointing to string values. Using Redis hashes to optimize your data storage this way does require additional programming overhead for tracking where data ends up, but if your data storage is primarly string based, you can save a lot of memory overhead using this one weird trick.

关于哈希的所有可能操作,请参阅哈希文档

列表

Redis列表就像链表一样。

可以从列表的头部或尾部向列表插入、删除和遍历列表。

当您需要按照插入值的顺序维护值时,请使用列表。(如果你需要的话,Redis确实给了你插入到任意列表位置的选项,但是如果你插入的位置远离你的起始位置,你的插入性能会降低。)

Redis列表通常用作生产者/消费者队列。向列表中插入项,然后从列表中弹出项。如果用户试图从一个没有元素的列表中弹出,会发生什么?你可以让Redis等待一个元素出现,并在它被添加时立即返回给你。这将Redis变成一个实时消息队列/事件/作业/任务/通知系统。

您可以从列表的任意一端原子地删除元素,从而使任何列表都可以被视为堆栈或队列。

您还可以通过在每次插入后将列表修剪为特定大小来维护固定长度的列表(有上限的集合)。

关于列表上所有可能的操作,请参阅列表文档

Sets

Redis集合是集合。

Redis集合包含唯一的无序Redis字符串,其中每个字符串在一个集合中只存在一次。如果你将相同的元素添加到一个集合十次,它只会显示一次。集合非常适合惰性地确保某些东西至少存在一次,而不用担心重复元素的积累和浪费空间。您可以随心所欲地多次添加相同的字符串,而不需要检查它是否已经存在。

集合可以快速地检查、插入和删除集合中的成员。

集合具有高效的集合操作,正如您所期望的那样。你可以同时取多个集合的并集、交集和差集。结果可以返回给调用者,也可以存储在一个新的集合中以供以后使用。

集合有恒定的时间访问成员检查(不像列表),Redis甚至有方便的随机成员删除和返回(“从集合中弹出一个随机元素”)或随机成员返回而不替换(“给我30个随机的唯一用户”)或替换(“给我7张卡片,但每次选择后,把卡片放回去,这样它就可以再次采样”)。

关于集合的所有可能操作,请参阅集合文档。

排序集

Redis排序集是用户定义的排序集。

为简单起见,您可以将已排序的集合看作具有唯一元素的二叉树。(Redis排序集实际上是跳跃列表。)元素的排序顺序由每个元素的得分定义。

排序的集合仍然是集合。元素在一个集合中只能出现一次。为了唯一性,元素是由它的字符串内容定义的。插入排序分数为3的元素“apple”,然后插入排序分数为500的元素“apple”,结果在排序集中生成一个排序分数为500的元素“apple”。集合仅基于数据而不是基于(Score, Data)对是唯一的。

Make sure your data model relies on the string contents and not the element's score for uniqueness. Scores are allowed to be repeated (or even zero), but, one last time, set elements can only exist once per sorted set. For example, if you try to store the history of every user login as a sorted set by making the score the epoch of the login and the value the user id, you will end up storing only the last login epoch for all your users. Your set would grow to size of your userbase and not your desired size of userbase * logins.

元素与分数一起添加到您的集合中。您可以随时更新任何元素的分数,只需再次添加一个新分数的元素。分数由浮点双精度精度表示,因此可以在需要时指定高精度时间戳的粒度。多个元素可能具有相同的分数。

可以用几种不同的方式检索元素。由于所有内容都已排序,因此可以从最低值开始请求元素。您可以请求从最高分开始的元素(“反向”)。您可以根据元素的排序分数按自然顺序或倒序请求它们。

关于排序集的所有可能操作,请参阅排序集文档。

大多数时候,你不需要理解Redis使用的底层数据结构。但是一些知识可以帮助你做出CPU v/s内存的权衡。它还可以帮助您以有效的方式对数据建模。

在内部,Redis使用以下数据结构:

字符串 字典 双链表 跳跃表 邮政编码列表 Int集 Zip地图(自Redis 2.6起已弃用,支持Zip列表)

要查找特定键使用的编码,请使用命令object encoding <key>。

1. 字符串

在Redis中,字符串被称为简单动态字符串,或SDS。它是一个较小的char *包装器,允许您将字符串的长度和空闲字节数作为前缀存储。

因为字符串的长度是存储的,所以strlen是一个O(1)操作。此外,因为长度是已知的,Redis字符串是二进制安全的。字符串中包含空字符是完全合法的。

字符串是Redis中最通用的数据结构。String是以下所有类型:

A string of characters that can store text. See SET and GET commands. A byte array that can store binary data. A long that can store numbers. See INCR, DECR, INCRBY and DECRBY commands. An Array (of chars, ints, longs or any other data type) that can allow efficient random access. See SETRANGE and GETRANGE commands. A bit array that allows you to set or get individual bits. See SETBIT and GETBIT commands. A block of memory that you can use to build other data structures. This is used internally to build ziplists and intsets, which are compact, memory-efficient data structures for small number of elements. More on this below.

2. 字典

Redis使用字典来实现以下功能:

将键映射到其关联值,其中值可以是字符串、散列、集合、排序集合或列表。 将密钥映射到其到期时间戳。 实现Hash、Set和Sorted Set数据类型。 将Redis命令映射到处理这些命令的函数。 将Redis键映射到被该键阻塞的客户端列表。看到BLPOP。

Redis字典是使用哈希表实现的。我将不解释实现,只解释Redis的具体内容:

Dictionaries use a structure called dictType to extend the behaviour of a hash table. This structure has function pointers, and so the following operations are extendable: a) hash function, b) key comparison, c) key destructor, and d) value destructor. Dictionaries use the murmurhash2. (Previously they used the djb2 hash function, with seed=5381, but then the hash function was switched to murmur2. See this question for an explanation of the djb2 hash algorithm.) Redis uses Incremental Hashing, also known as Incremental Resizing. The dictionary has two hash tables. Every time the dictionary is touched, one bucket is migrated from the first (smaller) hash table to the second. This way, Redis prevents an expensive resize operation.

Set数据结构使用Dictionary来保证没有重复。排序集使用字典将一个元素映射到它的分数,这就是为什么ZSCORE是一个O(1)操作。

3.双链表

列表数据类型是使用双链表实现的。Redis的实现直接来自于算法教科书。唯一的变化是Redis将长度存储在列表数据结构中。这确保了LLEN具有O(1)复杂度。

4. 跳跃表

Redis使用跳过列表作为排序集的底层数据结构。维基百科有一个很好的介绍。William Pugh的论文跳跃列表:平衡树的概率替代方案有更多细节。

排序集同时使用跳过表和字典。字典存储每个元素的分数。

Redis的跳跃列表实现在以下方面与标准实现不同:

Redis允许重复得分。如果两个节点有相同的分数,它们将按字典顺序排序。 每个节点在0级有一个后向指针。这允许您以分数的相反顺序遍历元素。

5. 邮政编码列表

Zip List类似于双链表,只是它不使用指针并内联存储数据。

双链表中的每个节点都有3个指针——一个前向指针、一个后向指针和一个用于引用存储在该节点上的数据的指针。指针需要内存(64位系统需要8个字节),因此对于小列表,双链表的效率非常低。

Zip列表在Redis字符串中按顺序存储元素。每个元素都有一个小的头部,存储元素的长度和数据类型,下一个元素的偏移量和上一个元素的偏移量。这些偏移量替换向前指针和向后指针。由于数据是内联存储的,因此不需要数据指针。

Zip列表用于存储小列表、排序集和散列。排序集被平铺成一个列表,如[element1, score1, element2, score2, element3, score3],并存储在Zip list中。哈希被平铺成一个列表,如[key1, value1, key2, value2]等。

With Zip Lists you have the power to make a tradeoff between CPU and Memory. Zip Lists are memory-efficient, but they use more CPU than a linked list (or Hash table/Skip List). Finding an element in the zip list is O(n). Inserting a new element requires reallocating memory. Because of this, Redis uses this encoding only for small lists, hashes and sorted sets. You can tweak this behaviour by altering the values of <datatype>-max-ziplist-entries and <datatype>-max-ziplist-value> in redis.conf. See Redis Memory Optimization, section "Special encoding of small aggregate data types" for more information.

c上的注释非常棒,无需阅读代码就可以完全理解这个数据结构。

6. Int集

整型集是“排序整型数组”的花哨名称。

在Redis中,集合通常使用哈希表实现。对于小的集合,哈希表在内存方面是低效的。当集合仅由整数组成时,数组通常更有效。

Int Set是一个已排序的整数数组。为了找到一个元素,使用了二分搜索算法。这有一个O(log N)的复杂度。向这个数组中添加新的整数可能需要重新分配内存,这对于大的整数数组来说是非常昂贵的。

作为进一步的内存优化,Int集有3种不同整数大小的变体:16位、32位和64位。Redis非常聪明,可以根据元素的大小使用正确的变体。当添加一个新元素并且超出当前大小时,Redis会自动将其迁移到下一个大小。如果添加了一个字符串,Redis会自动将Int集转换为基于常规哈希表的集合。

Int Sets are a tradeoff between CPU and Memory. Int Sets are extremely memory efficient, and for small sets they are faster than a hash table. But after a certain number of elements, the O(log N) retrieval time and the cost of reallocating memory become too much. Based on experiments, the optimal threshold to switch over to a regular hash table was found to be 512. However, you can increase this threshold (decreasing it doesn't make sense) based on your application's needs. See set-max-intset-entries in redis.conf.

7. Zip地图

Zip map是扁平的字典,存储在列表中。它们非常类似于Zip列表。

自Redis 2.6以来,Zip map已弃用,小散列存储在Zip列表中。要了解关于这种编码的更多信息,请参阅zipmap.c中的注释。