在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
Redis作为Key-Value存储系统,数据结构如下: Redis没有表的概念,Redis实例所对应的db以编号区分,db本身就是key的命名空间。 比如:user:1000作为key值,表示在user这个命名空间下id为1000的元素,类似于user表的id=1000的行。 RedisDB结构Redis中存在“数据库”的概念,该结构由redis.h中的redisDb定义。 当redis 服务器初始化时,会预先分配 16 个数据库 所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中 redisClient中存在一个名叫db的指针指向当前使用的数据库 RedisDB结构体源码: /* Redis database representation. There are multiple databases identified * by integers from 0 (the default database) up to the max configured * database. The database number is the 'id' field in the structure. */ typedef struct redisDb { dict *dict; /* 存储数据库所有的key-value */ dict *expires; /* 存储key的过期时间 */ dict *blocking_keys; /* blpop 存储阻塞key和客户端对象*/ dict *ready_keys; /* 阻塞后push 响应阻塞客户端 存储阻塞后push的key和客户端对象 */ dict *watched_keys; /* 存储watch监控的的key和客户端对象 */ int id; /* Database ID */ long long avg_ttl; /* 存储的数据库对象的平均ttl(time to live),用于统计 */ unsigned long expires_cursor; /* 循环过期检查的光标. */ list *defrag_later; /* 需要尝试去清理磁盘碎片的链表,会慢慢的清理 */ } redisDb; id dict expires RedisObject结构Value是一个对象 结构信息概览 typedef struct redisObject { unsigned type:4; //类型 对象类型 unsigned encoding:4;//编码 // LRU_BITS为24bit 记录最后一次被命令程序访问的时间 unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ int refcount; //引用计数 void *ptr;//指向底层实现数据结构的指针 } robj; 4位type type 字段表示对象的类型,占 4 位; REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。 当我们执行 type 命令时,便是通过读取 RedisObject 的 type 字段获得对象的类型 127.0.0.1:6379> set a1 111 OK 127.0.0.1:6379> type a1 string 4位encoding encoding 表示对象的内部编码,占 4 位 每个对象有不同的实现编码 Redis 可以根据不同的使用场景来为对象设置不同的编码,大大提高了 Redis 的灵活性和效率。 通过 object encoding 命令,可以查看对象采用的编码方式 127.0.0.1:6379> OBJECT encoding a1 "int" 24位LRU 高16位存储一个分钟数级别的时间戳,低8位存储访问计数(lfu : 最近访问次数) lru----> 高16位: 最后被访问的时间 lfu----->低8位:最近访问次数 refcount refcount 的作用,主要在于对象的引用计数和内存回收。 当对象的refcount>1时,称为共享对象 Redis 为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。 ptr 7种type 字符串对象 C语言: 字符数组 “\0” Redis 使用了 SDS(Simple Dynamic String)。用于存储字符串和整型数据。 /* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; buf[] 的长度=len+free+1 SDS的优势: 1.SDS 在 C 字符串的基础上加入了 free 和 len 字段,获取字符串长度:SDS 是 O(1),C 字符串是 2.SDS 由于记录了长度,在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。 3.可以存取二进制数据,以字符串长度len来作为结束标识
\0 空字符串 二进制数据包括空字符串,所以没有办法存取二进制数据
非二进制 \0 使用场景: 跳跃表(重要) 跳跃表是有序集合(sorted-set)的底层实现,效率高,实现简单。 跳跃表的基本思想: 将有序链表中的部分节点分层,每一层都是一个有序链表。 查找 在查找时优先从最高层开始向后查找,当到达某个节点时,如果next节点值大于要查找的值或next指针指向null,则从当前节点下降一层继续向后查找。 举例: 第一次分层: 遍历5次找到元素9(红色的线为查找路径) 遍历4次找到元素9
遍历4次找到元素9 插入与删除上面例子中,9个结点,一共4层,是理想的跳跃表。 通过抛硬币(概率1/2)的方式来决定新插入结点跨越的层数,每层都需要判断: 正面:插入上层 背面:不插入 达到1/2概率(计算次数) 删除 找到指定元素并删除每层的该元素即可 跳跃表特点: 每层都是一个有序链表 查找次数近似于层数(1/2) 底层包含所有元素 空间复杂度 O(n) 扩充了一倍 Redis跳跃表的实现 /* ZSETs use a specialized version of Skiplists */ typedef struct zskiplistNode { /* 存储字符串类型数据 redis3.0版本中使用robj类型表示,但是在redis4.0.1中直接使用sds类型表示 */ sds ele; /*存储排序的分值*/ double score; /*后退指针,指向当前节点最底层的前一个节点*/ struct zskiplistNode *backward; /*层,柔性数组,随机生成1-64的值*/ struct zskidictEntryplistLevel { struct zskiplistNode *forward; //指向本层下一个节点 unsigned long span; //本层下个节点到本节点的元素个数 } level[]; } zskiplistNode; typedef struct zskiplist { //表头节点和表尾节点 struct zskiplistNode *header, *tail; //表中节点的数量 unsigned long length; //表中层数最大的节点的层数 int level; } zskiplist; 完整的跳跃表结构体:
跳跃表的优势: 字典(重要) 字典dict又称散列表(hash),是用来存储键值对的一种数据结构。 数组 数组:用来存储数据的容器,采用头指针+偏移量的方式能够以O(1)的时间复杂度定位到数据所在的内存地址。 Redis 海量存储 快 Hash函数 Hash(散列),作用是把任意长度的输入通过散列算法转换成固定类型、固定长度的散列值。 hash函数可以把Redis里的key:包括字符串、整数、浮点数统一转换成整数。 key=100.1 String “100.1” 5位长度的字符串 Redis-cli :times 33 Redis-Server : MurmurHash 数组下标=hash(key)%数组容量(hash值%数组容量得到的余数) Hash冲突 不同的key经过计算后出现数组下标一致,称为Hash冲突。 采用单链表在相同的下标位置处存储原始key和value 当根据key找Value时,找到数组下标,遍历单链表可以找出key相同的value Redis字典的实现Redis字典实现包括:字典(dict)、Hash表(dictht)、Hash表节点(dictEntry)。 Hash表 typedef struct dictht { dictEntry **table; // 哈希表数组 unsigned long size; // 哈希表数组的大小 unsigned long sizemask; // 用于映射位置的掩码,值永远等于(size-1) unsigned long used; // 哈希表已有节点的数量,包含next单链表数据 } dictht; 1、hash表的数组初始容量为4,随着k-v存储量的增加需要对hash表数组进行扩容,新扩容量为当前量的一倍,即4,8,16,32 2、索引值=Hash值&掩码值(Hash值与Hash表容量取余) Hash表节点 typedef struct dictEntry { void *key; // 键 union { // 值v的类型可以是以下4种类型 void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; // 指向下一个哈希表节点,形成单向链表 解决hash冲突, 单链表中会存储key和value } dictEntry; key字段存储的是键值对中的键 v字段是个联合体,存储的是键值对中的值。 next指向下一个哈希表节点,用于解决hash冲突 dict字典 typedef struct dict { dictType *type; //该字典对应的特定操作函数 void *privdata; //上述类型函数对应的可选参数 dictht ht[2];/* 两张哈希表,存储键值对数据,ht[0]为原生哈希表,ht[1]为 rehash 哈希表 */ long rehashidx; /* rehash标识 当等于-1时表示没有在rehash,否则表示正在进行rehash操作, 存储的值表示hash表 ht[0]的rehash进行到哪个索引值(数组下标)*/ unsigned long iterators; /* 当前运行的迭代器数量 */ } dict; type字段,指向dictType结构体,里边包括了对该字典操作的函数指针 typedef struct dictType { // 计算哈希值的函数 uint64_t (*hashFunction)(const void *key); // 复制键的函数 void *(*keyDup)(void *privdata, const void *key); // 比较键的函数 void *(*valDup)(void *privdata, const void *obj); // 比较键的函数 int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 销毁键的函数 void (*keyDestructor)(void *privdata, void *key); // 销毁值的函数 void (*valDestructor)(void *privdata, void *obj); } dictType; Redis字典除了主数据库的K-V数据存储以外,还可以用于:散列表对象、哨兵模式中的主从节点管理等在不同的应用中,字典的形态都可能不同,dictType是为了实现各种形态的字典而抽象出来的操作函数(多态)。 完整的Redis字典数据结构: 字典扩容字典达到存储上限(阈值 0.75),需要rehash(扩容) 说明: 初次申请默认容量为4个dictEntry,非初次申请为当前hash表容量的一倍。rehashidx=0表示要进行rehash操作。新增加的数据在新的hash表h[1]修改、删除、查询在老hash表h[0]、新hash表h[1]中(rehash中)将老的hash表h[0]的数据重新计算索引值后全部迁移到新的hash表h[1]中,这个过程称为rehash。 渐进式rehash 当数据量巨大时rehash的过程是非常缓慢的,所以需要进行优化。 压缩列表压缩列表(ziplist)是由一系列特殊编码的连续内存块组成的顺序型数据结构 节省内存 压缩列表的数据结构如下: zlbytes:压缩列表的字节长度 previous_entry_length:前一个元素的字节长度 ziplist结构体如下: struct ziplist<T>{ unsigned int zlbytes; // ziplist的长度字节数,包含头部、所有entry和zipend。 unsigned int zloffset; // 从ziplist的头指针到指向最后一个entry的偏移量,用于快速反向查询 unsigned short int zllength; // entry元素个数T[] entry; // 元素值 unsigned char zlend; // ziplist结束符,值固定为0xFF } typedef struct zlentry { unsigned int prevrawlensize; //previous_entry_length字段的长度 unsigned int prevrawlen; //previous_entry_length字段存储的内容 unsigned int lensize; //encoding字段的长度 unsigned int len; //数据内容长度 unsigned int headersize; //当前元素的首部长度,即previous_entry_length字段长度与 encoding字段长度之和。 unsigned char encoding; //数据类型 unsigned char *p; //当前元素首地址 } zlentry; 应用场景: list用快速链表(quicklist)数据结构存储,而快速链表是双向列表与压缩列表的组合。(间接使用) 整数集合整数集合(intset)是一个有序的(整数升序)、存储整数的连续存储结构。 127.0.0.1:6379> SADD set:1 12 6 8 (integer) 3 127.0.0.1:6379> OBJECT encoding set:1 "intset" 127.0.0.1:6379> SADD set:2 1 1000000000000000000000000000000000000000000000000000000 99999999999999999999999999999 (integer) 3 127.0.0.1:6379> OBJECT encoding set:2 "hashtable" intset的结构图如下: typedef struct intset{ //编码方式 uint32_t encoding; //集合包含的元素数量 uint32_t length; //保存元素的数组 int8_t contents[]; }intset; 应用场景: 快速列表(重要) 快速列表(quicklist)是Redis底层重要的数据结构。是列表的底层实现。(在Redis3.2之前,Redis采用双向链表(adlist)和压缩列表(ziplist)实现。)在Redis3.2以后结合adlist和ziplist的优势Redis设计出了quicklist。 127.0.0.1:6379> LPUSH list:001 2 3 5 6 7 (integer) 5 127.0.0.1:6379> OBJECT encoding list:001 "quicklist" 双向列表(adlist)
双向链表优势:
快速列表
quicklist的结构定义如下: #if UINTPTR_MAX == 0xffffffff /* 32-bit */ # define QL_FILL_BITS 14 # define QL_COMP_BITS 14 # define QL_BM_BITS 4 #elif UINTPTR_MAX == 0xffffffffffffffff /* 64-bit */ # define QL_FILL_BITS 16 # define QL_COMP_BITS 16 # define QL_BM_BITS 4 /* we can encode more, but we rather limit the user since they cause performance degradation. */ #else # error unknown arch bits count #endif /* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist. * 'count' is the number of total entries. * 'len' is the number of quicklist nodes. * 'compress' is: 0 if compression disabled, otherwise it's the number * of quicklistNodes to leave uncompressed at ends of quicklist. * 'fill' is the user-requested (or default) fill factor. * 'bookmakrs are an optional feature that is used by realloc this struct, * so that they don't consume memory when not used. */ typedef struct quicklist { quicklistNode *head; // 指向quicklist的头部 quicklistNode *tail; // 指向quicklist的尾部 unsigned long count; /* 列表中所有数据项的个数总和 */ unsigned long len; /* quicklist节点的个数,即ziplist的个数 */ int fill : QL_FILL_BITS; /* ziplist大小限定,由list-max-ziplist-size给定 (Redis设定)*/ unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */ unsigned int bookmark_count: QL_BM_BITS; /*节点压缩深度设置,由list-compress-depth给定*/ quicklistBookmark bookmarks[]; /*可选新特性 使用realloc重新分配空间的时候会用到*/ } quicklist; quicklistNode的结构定义如下: /* quicklistNode is a 32 byte struct describing a ziplist for a quicklist. * We use bit fields keep the quicklistNode at 32 bytes. * count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k). * encoding: 2 bits, RAW=1, LZF=2. * container: 2 bits, NONE=1, ZIPLIST=2. * recompress: 1 bit, bool, true if node is temporary decompressed for usage. * attempted_compress: 1 bit, boolean, used for verifying during testing. * extra: 10 bits, free for future use; pads out the remainder of 32 bits */ typedef struct quicklistNode { struct quicklistNode *prev; // 指向上一个ziplist节点 struct quicklistNode *next; // 指向下一个ziplist节点 unsigned char *zl; // 数据指针,如果没有被压缩,就指向ziplist结构,反之指向 quicklistLZF结构 unsigned int sz; // 表示指向ziplist结构的总长度(内存占用长度) unsigned int count : 16; // 表示ziplist中的数据项个数 unsigned int encoding : 2; // 编码方式,1--ziplist,2--quicklistLZF unsigned int container : 2; // 预留字段,存放数据的方式,1--NONE,2--ziplist unsigned int recompress : 1; // 解压标记,当查看一个被压缩的数据时,需要暂时解压,标记此参数为 1,之后再重新进行压缩 unsigned int attempted_compress : 1; // 测试相关 unsigned int extra : 10; // 扩展字段,暂时没用 } quicklistNode; 数据压缩 压缩过后的数据可以分成多个片段,每个片段有两个部分:解释字段和数据字段。quicklistLZF的结构体如下: /* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'. * 'sz' is byte length of 'compressed' field. * 'compressed' is LZF data with total (compressed) length 'sz' * NOTE: uncompressed length is stored in quicklistNode->sz. * When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */ typedef struct quicklistLZF { unsigned int sz; /* LZF压缩后占用的字节数*/ char compressed[]; //柔性数组,指向数据部分 } quicklistLZF; 应用场景 流对象 stream主要由:消息、生产者、消费者和消费组构成。
Redis Stream的底层主要使用了listpack(紧凑列表)和Rax树(基数树)。 listpack
Rax树
Rax 被用在 Redis Stream 结构里面用于存储消息队列,在 Stream 里面消息 ID 的前缀是时间戳 + 序号,这样的消息可以理解为时间序列消息。使用 Rax 结构 进行存储就可以快速地根据消息 ID 定位到具体的消息,然后继续遍历指定消息 之后的所有消息。
应用场景: 10种encoding /* Objects encoding. Some kind of objects like Strings and Hashes can be * internally represented in multiple ways. The 'encoding' field of the object * is set to one of this fields for this object. */ #define OBJ_ENCODING_RAW 0 /* Raw representation */ #define OBJ_ENCODING_INT 1 /* Encoded as integer */ #define OBJ_ENCODING_HT 2 /* Encoded as hash table */ #define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */ #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */ #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */ #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */ encoding 表示对象的内部编码,占 4 位。 Redis通过 encoding 属性为对象设置不同的编码 对于少的和小的数据,Redis采用小的和压缩的存储方式,体现Redis的灵活性 大大提高了 Redis 的存储量和执行效率 比如Set对象: intset : 元素是64位以内的整数 hashtable:元素是64位以外的整数 127.0.0.1:6379> sadd set:001 1 3 5 6 2 (integer) 5 127.0.0.1:6379> object encoding set:001 "intset" 127.0.0.1:6379> sadd set:004 1 100000000000000000000000000 9999999999 (integer) 3 127.0.0.1:6379> object encoding set:004 "hashtable" String int、raw、embstr int REDIS_ENCODING_INT(int类型的整数) 127.0.0.1:6379> set n1 123 OK 127.0.0.1:6379> object encoding n1 "int" embstr REDIS_ENCODING_EMBSTR(编码的简单动态字符串) 127.0.0.1:6379> set name:001 zhangfei OK 127.0.0.1:6379> object encoding name:001 "embstr" raw REDIS_ENCODING_RAW (简单动态字符串) 127.0.0.1:6379> set address:001 asdasdasdasdasdasdsadasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdas OK 127.0.0.1:6379> object encoding address:001 "raw" list 列表的编码是quicklist。 127.0.0.1:6379> lpush list:001 1 2 5 4 3 (integer) 5 127.0.0.1:6379> object encoding list:001 "quicklist" hash 散列的编码是字典和压缩列表 dict REDIS_ENCODING_HT(字典) 127.0.0.1:6379> hmset user:003 username111111111111111111111111111111111111111111111111111111111111111111111111 11111111111111111111111111111111 zhangfei password 111 num 2300000000000000000000000000000000000000000000000000 OK 127.0.0.1:6379> object encoding user:003 "hashtable" ziplist REDIS_ENCODING_ZIPLIST(压缩列表) 127.0.0.1:6379> HSET user:001 username zhanyun password 123456 (integer) 2 127.0.0.1:6379> OBJECT encoding user:001 "ziplist" set 集合的编码是整形集合和字典 intset REDIS_ENCODING_INTSET(整数集合) 127.0.0.1:6379> sadd set:001 1 3 5 6 2 (integer) 5 127.0.0.1:6379> object encoding set:001 "intset" dict REDIS_ENCODING_HT(字典) 127.0.0.1:6379> sadd set:004 1 100000000000000000000000000 9999999999 (integer) 3 127.0.0.1:6379> object encoding set:004 "hashtable" zset 有序集合的编码是压缩列表和跳跃表+字典 ziplist REDIS_ENCODING_ZIPLIST(压缩列表) 当元素的个数比较少,且元素都是小整数或短字符串时。 127.0.0.1:6379> zadd hit:1 100 item1 20 item2 45 item3 (integer) 3 127.0.0.1:6379> object encoding hit:1 "ziplist" skiplist + dict REDIS_ENCODING_SKIPLIST(跳跃表+字典) 当元素的个数比较多或元素不是小整数或短字符串时。 127.0.0.1:6379> zadd hit:2 100 item1111111111111111111111111111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111 20 item2 45 item3 (integer) 3 127.0.0.1:6379> object encoding hit:2 "skiplist" 到此这篇关于Redis底层数据结构的文章就介绍到这了,更多相关Redis数据结构内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论