Redis字典采用Hash表实现,针对碰撞问题,其采用的方法为“链地址法”,即将多个哈希值相同的节点串连在一起, 从而解决冲突问题。
“链地址法”的问题在于当碰撞剧烈时,性能退化严重,例如:当有n个数据,m个槽位,如果m=1,则整个Hash表退化为链表,查询复杂度O(n)
为了避免Hash碰撞攻击,Redis随机化了Hash表种子
Redis的方案是“双buffer”,正常流程使用一个buffer,当发现碰撞剧烈(判断依据为当前槽位数和Key数的对比),分配一个更大的buffer,然后逐步将数据从老的buffer迁移到新的buffer。
typedef struct dict { dictType *type; void *privdata; dictht ht[2]; //双buffer int rehashidx; int iterators; } dict; typedef struct dictht { dictEntry **table; //hash链表 unsigned long size; unsigned long sizemask; unsigned long used; } dictht; //数据节点<K,V> typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; } v; struct dictEntry *next; } dictEntry;redisObject是真正存储redis各种类型的结构,其内容如下:
typedef 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;其中type即redis支持的逻辑类型,包括:
#define REDIS_STRING 0 #define REDIS_LIST 1 #define REDIS_SET 2 #define REDIS_ZSET 3 #define REDIS_HASH 4enconding为物理存储方式,一种逻辑类型可以使用不同的存储方式,包括:
#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 */
typedef char *sds; struct sdshdr { int len; // buf 已占用长度 int free; // buf 剩余可用长度 char buf[];// 柔性数组,实际保存字符串数据的地方 }; static inline size_t sdslen(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->len; } static inline size_t sdsavail(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->free; }有时间的同学可以详细看下Sds.h和Sds.c两个文件,还是很有意思的。
以上任意条件满足则将Hash表的数据结构从REDIS_ENCODING_ZIPLIST转为REDIS_ENCODING_HT
typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; } listNode;列表的默认编码格式为REDIS_ENCODING_ZIPLIST,当满足以下条件时,编码格式转换为REDIS_ENCODING_LINKEDLIST
typedef struct intset { uint32_t encoding; //3种类型:INTSET_ENC_INT16、INTSET_ENC_INT32、INTSET_ENC_INT64 uint32_t length; //元素个数 int8_t contents[]; //元素实际存放的位置,按序排放 } intset;Redis会根据整数大小选择最适合的类型,当发生变更时,进行调整
Redis支持Value为有序集合,其逻辑类型为REDIS_ZSET,REDIS_ZSET有两种encoding方式: REDIS_ENCODING_ZIPLIST(同上)和 REDIS_ENCODING_SKIPLIST
REDIS_ENCODING_SKIPLIST使用的数据结构如下,其同事:
typedef struct zset { dict *dict; //Hash字典(同前文) zskiplist *zsl; //跳跃表 } zset;由于有续集每一个元素包括:<member,score>两个属性,为了保证对member和score都有很好的查询性能,REDIS_ENCODING_SKIPLIST同时采用字典和有序集两种数据结构来保存数据元素。字典和有序集通过指针指向同一个数据节点来避免数据冗余。
hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-entries 512 list-max-ziplist-value 64 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64