我试图在一个明确的列表中回答两个问题:
- Redis的底层数据结构是什么?
- 每种类型的主要优点/缺点/用例是什么?
因此,我读过Redis列表实际上是用链接列表实现的。但是对于其他类型,我无法提取任何信息。同样,如果有人偶然发现了这个问题,而又对修改或访问不同数据结构的优缺点没有一个高层次的总结,那么他们将有完整的清单,列出何时可以最佳地使用特定类型进行引用。
具体来说,我希望概述所有类型:字符串,列表,集合,zset和哈希。
到目前为止,我已经看过这些文章,其中包括:
我试图在一个明确的列表中回答两个问题:
因此,我读过Redis列表实际上是用链接列表实现的。但是对于其他类型,我无法提取任何信息。同样,如果有人偶然发现了这个问题,而又对修改或访问不同数据结构的优缺点没有一个高层次的总结,那么他们将有完整的清单,列出何时可以最佳地使用特定类型进行引用。
具体来说,我希望概述所有类型:字符串,列表,集合,zset和哈希。
到目前为止,我已经看过这些文章,其中包括:
Answers:
我将尝试回答您的问题,但首先我会从一开始可能看起来很奇怪的事情开始:如果您对Redis内部不感兴趣,则不必在意内部如何实现数据类型。这是有一个简单的原因:对于每个Redis操作,您都会在文档中找到时间复杂度,并且,如果您拥有一组操作和时间复杂度,则唯一需要做的就是了解内存使用情况的一些线索(并且因为我们会根据数据进行很多优化,获取这些数据的最佳方法是进行一些琐碎的实际测试。
但是,正如您所问的那样,这是每种Redis数据类型的基础实现。
但是,如果列表,集合和排序集合的项目数少且最大值大,则使用不同的,更紧凑的编码。对于不同的类型,此编码有所不同,但其特点是它是一个紧凑的数据块,通常会为每个操作强制进行O(N)扫描。由于我们仅将这种格式用于小物体,因此这不是问题。扫描一个很小的O(N)Blob可以忽略高速缓存,因此从实践上讲它是非常快的,并且当元素过多时,编码会自动切换到本机编码(链接列表,哈希等)。
但是您的问题不仅仅在于内部,而是您要使用哪种类型来完成任务?。
这是所有类型的基本类型。它是四种类型之一,也是复杂类型的基本类型,因为List是字符串列表,Set是一组字符串,依此类推。
在要存储HTML页面的所有显而易见的情况下,以及在避免转换已编码数据的情况下,Redis字符串都是一个好主意。因此,例如,如果您具有JSON或MessagePack,则可以将对象存储为字符串。在Redis 2.6中,您甚至可以使用Lua脚本来操纵这种对象服务器端。
字符串的另一种有趣用法是位图,通常是字节的随机访问数组,因为Redis导出命令以访问字节的随机范围甚至单个位。例如,查看以下优秀博客文章:使用Redis的Fast Easy实时指标。
当您可能只触摸列表的极端时(靠近尾巴或靠近头部),列表是不错的选择。列表不是很好的分页内容,因为随机访问速度很慢,O(N)。因此,列表的良好用法是普通队列和堆栈,或者使用具有相同源和目标的RPOPLPUSH在循环中处理项目以“旋转”项目环。
当我们只想创建N个项目的封顶集合时,通常我们只访问顶部或底部项目,或者当N小时,列表也很好。
集合是无序的数据集合,因此每次您拥有一个项目集合时它们都很好,并且以非常快速的方式检查集合的存在或大小非常重要。关于集的另一件很酷的事情是支持偷看或弹出随机元素(SRANDMEMBER和SPOP命令)。
集合也可以很好地表示关系,例如“用户X的朋友是什么?” 等等。但是,正如我们将看到的,用于这种东西的其他好的数据结构是排序集。
集合支持复杂的操作,例如交集,并集等,因此当您有数据并且想要对该数据执行转换以获得一些输出时,这是一种以“计算”方式使用Redis的良好数据结构。
小集合以非常有效的方式编码。
散列是代表由字段和值组成的对象的理想数据结构。散列字段也可以使用HINCRBY自动增加。当您有对象(例如用户,博客文章或其他类型的项目)时,如果您不想使用自己的编码(例如JSON或类似格式),则很可能要使用哈希。
但是,请记住,Redis非常有效地编码了小哈希,并且您可以要求Redis以非常快速的方式原子地获取,设置或递增各个字段。
哈希还可以用于使用引用来表示链接的数据结构。例如,查看lamernews.com评论的实现。
除列表外,排序集是唯一其他可维护有序元素的数据结构。您可以使用排序集来做很多很酷的事情。例如,您可以在Web应用程序中拥有各种Top Something列表。按分数排名最高的用户,按浏览量排名的最高帖子,排名最高的东西,但是单个Redis实例将支持每秒大量的插入和get-top-elements操作。
像常规集一样,已排序的集可用于描述关系,但是它们也允许您分页项目列表并记住排序。例如,如果我记得带有排序集的用户X的朋友,我可以轻松地按照接受的朋友的顺序记住他们。
排序集适合于优先级队列。
排序集就像更强大的列表一样,从列表中间插入,删除或获取范围总是非常快。但是它们使用更多的内存,并且是O(log(N))数据结构。
我希望我在这篇文章中提供了一些信息,但是最好从http://github.com/antirez/lamernews下载lamernews的源代码并了解其工作原理。Lamer News内部使用了Redis的许多数据结构,并且有很多线索可以用来解决给定的任务。
抱歉语法错误,在这里是午夜,又太累了,无法阅读这篇文章;)
大多数时候,您不需要了解Redis使用的基础数据结构。但是,有些知识可以帮助您权衡CPU v / s内存。它还可以帮助您以有效的方式对数据建模。
在内部,Redis使用以下数据结构:
要查找特定键使用的编码,请使用命令object encoding <key>
。
在Redis中,字符串称为简单动态字符串或SDS。这是一个较小的包装器char *
,可让您存储字符串的长度和可用字节数作为前缀。
因为存储了字符串的长度,所以strlen是O(1)运算。另外,由于长度是已知的,因此Redis字符串是二进制安全的。字符串包含空字符是完全合法的。
字符串是Redis中最通用的数据结构。字符串是以下所有内容:
long
可以存储数字。请参阅INCR,DECR,INCRBY和DECRBY命令。chars
,ints
,longs
其能够允许高效的随机存取或任何其他数据类型)。请参阅SETRANGE和GETRANGE命令。Redis使用以下字典:
Redis词典使用哈希表实现。除了说明实现之外,我仅介绍Redis的具体内容:
dictType
来扩展哈希表的行为。此结构具有函数指针,因此以下操作是可扩展的:a)哈希函数,b)键比较,c)键析构函数,和d)值析构函数。该Set
数据结构使用字典,以保证没有重复。在Sorted Set
使用字典的元素映射到它的分数,这是为什么ZSCORE是O(1)的操作。
的list
数据类型是使用实现的双向链表。Redis的实现是直接从算法教科书开始的。唯一的变化是Redis将长度存储在列表数据结构中。这样可以确保LLEN具有O(1)复杂度。
Redis使用“ 跳过列表”作为“排序集”的基础数据结构。维基百科有很好的介绍。威廉·普格(William Pugh)的论文“ 跳过列表:平衡树的概率替代方法”有更多详细信息。
排序集同时使用“跳过列表”和“字典”。字典存储每个元素的分数。
Redis的“跳过列表”实现在以下方面与标准实现不同:
压缩列表类似于双向链接列表,不同之处在于它不使用指针并且以内联方式存储数据。
双向链表中的每个节点都有3个指针-一个前向指针,一个后向指针和一个指针来引用存储在该节点上的数据。指针需要内存(在64位系统上为8字节),因此对于小列表,双向链接列表效率很低。
压缩列表按顺序在Redis字符串中存储元素。每个元素都有一个小标题,用于存储元素的长度和数据类型,下一个元素的偏移量以及上一个元素的偏移量。这些偏移量替换了前进和后退指针。由于数据是内联存储的,因此我们不需要数据指针。
Zip列表用于存储小列表,排序集和哈希。排序后的数据集被平整为一个列表,[element1, score1, element2, score2, element3, score3]
并存储在“压缩列表”中。散列成扁平状的清单[key1, value1, key2, value2]
等。
使用压缩列表,您可以在CPU和内存之间进行权衡。Zip列表可以节省内存,但是它们比链接列表(或哈希表/ Skip列表)使用更多的CPU。在zip列表中找到一个元素是O(n)。插入新元素需要重新分配内存。因此,Redis仅将此编码用于小型列表,哈希和排序集。您可以通过更改redis.conf 中<datatype>-max-ziplist-entries
and 的值来调整此行为<datatype>-max-ziplist-value>
。有关更多信息,请参见Redis内存优化,“小聚合数据类型的特殊编码”部分。
在上ziplist.c意见都非常出色,并且可以完全理解这种数据结构,而不必阅读代码。
整数集是“排序整数数组”的奇特名称。
在Redis中,通常使用哈希表来实现集合。对于小集合,哈希表在内存方面是低效的。当集合仅由整数组成时,数组通常更有效。
整数集是整数的排序数组。为了找到元素,使用了二进制搜索算法。这具有O(log N)的复杂度。向此数组添加新的整数可能需要重新分配内存,这对于大型整数数组可能会变得昂贵。
作为进一步的内存优化,整数集有3种变体,具有不同的整数大小:16位,32位和64位。Redis足够聪明,可以根据元素的大小使用正确的变体。添加新元素且其大小超过当前大小时,Redis会自动将其迁移到下一个大小。如果添加了字符串,Redis会自动将Int集转换为基于常规哈希表的集。
整数集是CPU和内存之间的权衡。整数集具有极高的内存效率,对于小型集合,它们比哈希表要快。但是在经过一定数量的元素后,O(log N)的检索时间和重新分配内存的成本变得太大。根据实验,发现切换到常规哈希表的最佳阈值为512。但是,您可以根据应用程序的需要增加此阈值(减小此阈值没有意义)。参见set-max-intset-entries
redis.conf。
邮编地图是扁平化的字典,并存储在列表中。它们与邮编列表非常相似。
自Redis 2.6起已弃用Zip Maps,并且将小哈希存储在Zip List中。要了解有关此编码的更多信息,请参阅zipmap.c中的注释。
Redis存储指向值的键。键可以是任意大小不超过合理大小的二进制值(建议使用短ASCII字符串以提高可读性和调试性)。值是五种本地Redis数据类型之一。
1.strings —二进制安全字节序列,最大为512 MB
2.哈希-键值对的集合
3.lists —插入顺序的字符串集合
4.sets —唯一字符串的集合,无顺序
5.sorted sets —一组按用户定义的评分排序的唯一字符串
弦乐
Redis字符串是字节序列。
Redis中的字符串是二进制安全的(这意味着它们的已知长度不受任何特殊的终止字符确定),因此,您可以在一个字符串中存储高达512 MB的任何内容。
字符串是规范的“键值存储”概念。您有一个指向值的键,其中键和值都是文本或二进制字符串。
有关字符串的所有可能操作,请参见 http://redis.io/commands/#string
散列
Redis哈希是键值对的集合。
Redis哈希表包含许多键值对,其中每个键和值都是一个字符串。Redis哈希不直接支持复杂值(意味着,您不能让哈希字段具有列表或集合或另一个哈希值),但是可以使用哈希字段指向其他顶级复杂值。您可以对哈希字段值执行的唯一特殊操作是数字内容的原子递增/递减。
您可以通过两种方式想到Redis哈希:作为直接的对象表示形式以及作为紧凑存储许多小值的方式。
直接对象表示很容易理解。对象具有名称(哈希键)和具有值的内部键的集合。参见下面的示例。
使用哈希存储许多小的值是聪明的Redis大规模数据存储技术。当散列具有少量字段(约100个)时,Redis会优化整个散列的存储和访问效率。Redis的小型哈希存储优化引发了一个有趣的行为:拥有100个带有100个内部键和值的散列比拥有10,000个指向字符串值的顶级键更有效。使用Redis哈希以这种方式优化数据存储确实需要额外的编程开销来跟踪数据的最终存储位置,但是,如果您的数据存储主要是基于字符串的,则可以使用这一怪异的技巧节省大量的内存开销。
有关散列的所有可能操作,请参见哈希文档
清单
Redis列表的行为类似于链接列表。
您可以从列表的开头或结尾插入列表,从列表删除和遍历列表。
当您需要按插入顺序维护值时,请使用列表。(Redis确实为您提供了插入任意列表位置的选项,但是如果您插入的位置离起始位置较远,插入性能将会降低。)
Redis列表通常用作生产者/消费者队列。将项目插入列表,然后从列表中弹出项目。如果您的消费者尝试从没有元素的列表中弹出,会发生什么情况?您可以要求Redis等待某个元素出现,并在添加元素后立即将其返回给您。这将Redis变成实时消息队列/事件/作业/任务/通知系统。
您可以从列表的任一端原子删除元素,从而将任何列表视为堆栈或队列。
您还可以通过在每次插入后将列表修整为特定大小来维护定长列表(加盖的集合)。
有关列表上所有可能的操作,请参阅列表文档
套装
Redis集是集合。
Redis集包含唯一的无序Redis字符串,其中每个字符串每个集仅存在一次。如果将相同的元素添加到集合十次,则只会显示一次。集合非常适合懒惰地确保某物至少存在一次,而不必担心重复元素的积累和浪费。您可以根据需要多次添加相同的字符串,而无需检查它是否已经存在。
集合可以快速进行成员资格检查,插入和删除集合中的成员。
正如您所期望的,集合具有高效的集合操作。您可以一次获取多个集合的并集,相交和差。结果可以返回给调用者,也可以将结果存储在新集中以供以后使用。
集合具有对成员资格检查(与列表不同)的恒定时间访问权限,并且Redis甚至具有方便的随机成员删除和返回(“从集合中弹出一个随机元素”)或随机成员返回而无需替换(“给我30个具有随机性的唯一用户) ”或更换(“给我7张卡,但每次选择后,将卡放回去,以便有可能再次采样”)。
有关集合的所有可能操作,请参阅集合文档。
排序集
Redis排序集是具有用户定义顺序的集。
为简单起见,您可以将排序后的集合视为具有唯一元素的二叉树。(Redis排序的集合实际上是跳过列表。)元素的排序顺序由每个元素的得分定义。
排序的集合仍然是集合。元素只能在集合中出现一次。出于唯一性目的,元素由其字符串内容定义。插入排序分数为3的元素“苹果”,然后插入排序分数为500的元素“苹果”会在您的排序集中产生一个排序分数为500的元素“苹果”。集仅基于数据是唯一的,而不基于(分数,数据)对。
确保您的数据模型依赖于字符串内容,而不是元素的唯一性分数。分数可以重复(甚至为零),但是最后一次,每个排序的set元素只能存在一次。例如,如果您尝试通过将得分的分数作为登录纪元和用户ID的值作为排序集来存储每个用户登录的历史记录,那么最终将只为所有用户存储最后一个登录纪元。您的设置将增长到用户群的大小,而不是您所需的用户群*登录名的大小。
元素会随分数添加到您的集合中。您可以随时更新任何元素的分数,只需使用新分数再次添加该元素。分数由浮点双精度表示,因此您可以根据需要指定高精度时间戳的粒度。多个元素可能具有相同的分数。
您可以通过几种不同的方式检索元素。由于所有内容均已排序,因此您可以要求分数最低的元素。您可以要求分数最高的元素(“反向”)。您可以按其排序分数以自然或逆序要求元素。
有关排序集的所有可能操作,请参阅排序集文档。