热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入理解Redis:底层数据结构

简介redis[1]是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(

简介

redis[1]是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。通常我们并不需要理解其底层数据结构,但如果能了解一下相关知识将会有助于我们更有效地使用Redis,并能够将这些知识应用到我们的工作中。

Redis内部实现如下数据结构[2,3,4,10]:
1 String
2 Hash Table
3 Doubly Linked List
4 Skip List
5 Zip List
6 Int Sets
7 Zip Maps (从2.6版本开始废弃)

Hash table[5]:
在Redis中,所有key-value对都存储在一个hash table中。这个Hash table是一个二维结构。其包括一个一维固定长度的数组,每个槽位上保存一个dictEntry对象。key计算hash值后按照这个定长数组求模,结果相同的key-balue通过链表保存在同一个槽位上,这样便形成了一个二维结构。需要说明的是,hash table中这个固定长度的数组能够根据key-value数量动态调整大小,细节说明请参看引用[5],这里不做更多说明。

这里再看一下dictEntry的定义,主要关注其中的联合体v的val成员:
struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    struct dictEntry *next;
} dictEntry;


val是一个类型为robj的数据结构,其中的type标示了当前的value的数据类型(即string、list、set、zset或者hash),encoding标示了当前value存储方式(即ziplist,String,hash table或者double linked list等)
struct redisObject {
    unsigned type:4;
    unsigned notused:2;     /* Not used */
    unsigned encoding:4;
    unsigned lru:22;        /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
} robj;


encoding目前支持的范围如下所示,具体可参考[1]源代码实现,其中的zipmap由于表示范围的限制已经在2.6版本中废弃,相关说明参见[6]
#define REDIS_ENCODING_RAW 0        /* Raw representation */
#define REDIS_ENCODING_INT 1        /* Encoded as integer */
#define REDIS_ENCODING_HT 2         /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3     /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5    /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6     /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7   /* Encoded as skiplist */


五种数据类型的内部实现

Redis在收到客户端的请求后,为每一个参数创建一个robj对象,type定义为REDIS_STRING,encoding为REDIS_ENCODING_RAW。接下来Redis根据第一个robj对象(也就是命令名)查找对应的函数,并调用查找到的函数,命令执行过程可参考[7]。

String

如果一个String类型的value能够保存为整数,则将对应robj对象的encoding修改为REDIS_ENCODING_INT,将对应robj对象的ptr值改为对应的数值。如果不能转为整数,保持原有encoding为REDIS_ENCODING_RAW。
因此String类型的数据可能使用原始的字符串存储(实际为sds - Simple Dynamic Strings[9],对应encoding为REDIS_ENCODING_RAW)或者整数存储。
具体查看某一个key的encoding,参考Redis命令object[8]

下面是具体的例子:
redis 127.0.0.1:6379> set hello 1
OK
redis 127.0.0.1:6379> OBJECT ENCODING hello
"int"
redis 127.0.0.1:6379> set hello world
OK
redis 127.0.0.1:6379> OBJECT ENCODING hello
"raw"

List

List类型的key创建时使用zip list结构存储,robj对象的encoding字段设置为REDIS_ENCODING_ZIPLIST。zip list实现细节可参考[3]。概况来讲,zip list通过一个连续的内存块实现list结构,其中的每个entry节点头部保存前后节点长度信息,实现双向链表功能。这个头部可根据前后entry长度进行内存压缩,而如果直接使用指针的话则至少需要两个指针,对64位系统来说将占用16个字节,使用zip list时最好情况下只需要两个字节,这在具有大量list类型的key-value对且各个value较小的应用来说,可以节省大量内存。
当list的elem数小于配置值: hash-max-ziplist-entries 或者elem_value字符串的长度小于 hash-max-ziplist-value, 可以编码成 REDIS_ENCODING_ZIPLIST 类型存储,以节约内存;但由于在zip list添加和删除元素会涉及到数据移动,因此当list内容较多时,转而使用双向链表。双向链表的实现可参考数据结构相关教科书。
相关内存优化说明请参考[11]。

Hash

新建的Hash类型也使用ziplist存储value,保存数据过多时,转而使用hast table。

Set

创建Set类型的key-value时,如果value能够表示为整数,则使用intset类型保存value。intset使用和ziplist相似的实现方式保存整数[4]。数据量大时,切换为使用hash table保存各个value。

Zset

zset指排序的set,如果新建的zset包含value数大于配置或者value长度大于配置值[11],则直接使用hash table和skip list[12]存储value,skip list实现对value的排序;否则直接使用skip list存储value。Redis可以保存相同score的value值,其实现可参考源代码[1]以及文献[12],Redis是参考[12]中伪代码实现的。

本文只对Redis底层数据结构实现进行了简单归并汇总,各部分实现细节请参考引用链接即Redis源代码。本文内容基于Redis 2.6版本。

引用

[1] http://redis.io/

[2] http://stackoverflow.com/questions/9625246/what-are-the-underlying-data-structures-used-for-redis
[3] 《Redis ziplist内部结构分析》, http://www.searchdatabase.com.cn/showcontent_60781.htm
[4] 《解读Redis中ziplist、zipmap、intset实现细节》, http://www.wzxue.com/%E8%A7%A3%E8%AF%BBredis%E4%B8%ADziplist%E5%AE%9E%E7%8E%B0%E7%BB%86%E8%8A%82/
[5] 《redis源代码分析 – hash table》,http://www.kuqin.com/database/20110904/264306.html
[6]  zipmap zmlen is too short, https://github.com/antirez/redis/issues/188
[7] 《深入理解Redis:命令处理流程 》, http://blog.csdn.net/hanhuili/article/details/17339005
[8] http://redis.io/commands/object
[9] 《Redis sds数据结构实现分析》,http://www.searchdatabase.com.cn/showcontent_64553.htm
[10] 《Redis内存存储结构分析》,http://www.searchtb.com/2011/05/redis-storage.html
[11] http://redis.io/topics/memory-optimization
[12] http://homepage.divms.uiowa.edu/~ghosh/skip.pdf

推荐阅读
  • 本文介绍了Linux Shell中括号和整数扩展的使用方法,包括命令组、命令替换、初始化数组以及算术表达式和逻辑判断的相关内容。括号中的命令将会在新开的子shell中顺序执行,括号中的变量不能被脚本余下的部分使用。命令替换可以用于将命令的标准输出作为另一个命令的输入。括号中的运算符和表达式符合C语言运算规则,可以用在整数扩展中进行算术计算和逻辑判断。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
  • Skywalking系列博客1安装单机版 Skywalking的快速安装方法
    本文介绍了如何快速安装单机版的Skywalking,包括下载、环境需求和端口检查等步骤。同时提供了百度盘下载地址和查询端口是否被占用的命令。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文介绍了一些好用的搜索引擎的替代品,包括网盘搜索工具、百度网盘搜索引擎等。同时还介绍了一些笑话大全、GIF笑话图片、动态图等资源的搜索引擎。此外,还推荐了一些迅雷快传搜索和360云盘资源搜索的网盘搜索引擎。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • REVERT权限切换的操作步骤和注意事项
    本文介绍了在SQL Server中进行REVERT权限切换的操作步骤和注意事项。首先登录到SQL Server,其中包括一个具有很小权限的普通用户和一个系统管理员角色中的成员。然后通过添加Windows登录到SQL Server,并将其添加到AdventureWorks数据库中的用户列表中。最后通过REVERT命令切换权限。在操作过程中需要注意的是,确保登录名和数据库名的正确性,并遵循安全措施,以防止权限泄露和数据损坏。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • python3 nmap函数简介及使用方法
    本文介绍了python3 nmap函数的简介及使用方法,python-nmap是一个使用nmap进行端口扫描的python库,它可以生成nmap扫描报告,并帮助系统管理员进行自动化扫描任务和生成报告。同时,它也支持nmap脚本输出。文章详细介绍了python-nmap的几个py文件的功能和用途,包括__init__.py、nmap.py和test.py。__init__.py主要导入基本信息,nmap.py用于调用nmap的功能进行扫描,test.py用于测试是否可以利用nmap的扫描功能。 ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
  • node.jsurlsearchparamsAPI哎哎哎 ... [详细]
author-avatar
大大炮
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有