Redis Java对象形式展示Redis数据结构

Redis作为key-value内存数据库,拥有丰富的数据类型(string、hash、list、set、zset等)。
在这些数据类型背后必然有多种数据结构支持,数据结构记录在redisObject.encoding属性(重点), 本文将以Java对象形式展示Redis数据结构。
无论哪种数据类型都是以键值对为单位存储的,而每个键值对会封装成dictEntry对象,因此这个对象是通用的。

dictEntry
public class dictEntry {
    private sdshdr key;
    private redisObject v;
    private dictEntry next;
}

key:指向封装key值的sdshdr对象。
v:指向封装value值的Object对象。
next:指向下一个dictEntry对象。

sdshdr
public class sdshdr {
    int len;
    int free;
    char[] buf;
}

len:记录buf已使用的字节的数量。
free:记录buf未使用的字节的数量。
buf:字节数组。
SDS遵循C字符串以空字符’\0’结尾的惯例,这么做的好处是SDS可以直接重用一部分C字符串函数库里的函数。
比如buf长度16,保存字符串"helloworld",那么len=10,free=5,空字符"\0"占一位。
为什么SDS数据结构比C字符串更适合Redis

  • 降低获取字符串长度的复杂度:SDS获取字符串长度的复杂度O(1);C字符串获取字符串长度的复杂度O(N)。
  • SDS杜绝缓冲区溢出:SDS在修改前检查空间是否满足修改所需的要求,如果不满足SDS会自动扩展至修改所需的大小(或更多),然后再执行实际修改操作。
  • 减少修改字符串时的内存重分配次数:SDS实现了空间预分配和惰性空间释放两种优化策略。
  • SDS可以保存文本或者二进制数据,支持字符串中间夹杂空字符’\0’;C字符串只能保存文本。

空间预分配策略
如果len < 1MB,预分配大小等同于len。
如果len >= 1MB,预分配1MB大小。
惰性空间释放策略
SDS进行缩短字符串操作时,不会执行内存重分配。

redisObject
public class redisObject {
    private String type;
    private String encoding;
    private String lru;
    private int refcount;
    private Object ptr;
}

type:表示属于哪种数据类型(REDIS_STRING \ REDIS_LIST \ REDIS_HASH \ REDIS_SET \ REDIS_ZSET)。
encoding:表示属于哪种数据结构。

type encoding 描述
REDIS_STRING REDIS_ENCODING_INT 使用整数值实现的字符串对象
REDIS_ENCODING_EMBSTR 使用embstr编码的简单动态字符串实现的字符串对象
REDIS_ENCODING_RAW 使用简单动态字符串实现的字符串对象
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用压缩列表实现的列表对象
REDIS_ENCODING_LINKEDLIST 使用双端链表实现的列表对象
REDIS_HASH REDIS_ENCODING_ZIPLIST 使用压缩列表实现的哈希对象
REDIS_ENCODING_HT 使用字典实现的哈希对象
REDIS_SET REDIS_ENCODING_INTSET 使用整数集合实现的集合对象
REDIS_ENCODING_HT 使用字典实现的集合对象
REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用压缩列表实现的有序集合对象
REDIS_ENCODING_SKIPLIST 使用跳跃表和字典实现的有序集合对象

lru:表示该对象最后被访问时间,用于过期策略。
refcount:表示该对象被引用次数。
ptr:指向封装value值的对象,由于不同encoding封装value值的对象数据结构不同,所以先用Object表示。

无环双端链表

encoding=“REDIS_ENCODING_LINKEDLIST”,ptr指向一个list对象。

list
public class list {
    listNode head;
    listNode tail;
    long len;
    boolean dup(listNode ptr);// 节点值复制函数
    boolean free(listNode ptr);// 节点值释放函数
    boolean match(listNode ptr, String value);// 节点值对比函数
}

head:指向头结点指针。
tail:指向尾结点指针。
len:链表包含节点数量。
list对象的三个函数成员不在本文讨论范围内。

listNode
public class listNode {
    listNode prev;
    listNode next;
    redisObject value;
}

prev:前置节点(若当前节点为头节点,该属性为null)。
next:后置节点(若当前节点为尾节点,该属性为null)。
value:指向封装value值的redisObject对象,支持多种类型。

字典

encoding=“REDIS_ENCODING_HT”,ptr指向一个dict对象。

dict
public class dict {
    dictType type;
    sdshdr privdata;
    dictht[2] ht;
    int rehashidx;
}

type:指向dictType的指针。
privdata:与type属性一起针对不同类型键值对,为创建多态字典表而设置。
ht:哈希表数组,长度为2。一般只使用ht[0],进行rehash时使用到ht[1]。
rehashidx:渐进式rehash索引,记录的是rehash进行到dict.ht[0].table的哪个位置;当rehash不在进行时,值为-1。

dictType
public class dictType {
    int hashFunction(String key);// 计算hash值函数
    boolean keyDup(String privdata, String key);// 复制键函数
    boolean valDup(String privdata, String obj);// 复制值函数
    int keyCompare(String privdata, String key1, String key2);// 对比键函数
    boolean keyDestructor(String privdata, String key);// 销毁键函数
    boolean valDestructor(String privdata, String obj);// 销毁值函数
}

dictType对象的六个函数成员不在本文讨论范围内。

dictht
public class dictht {
    dictEntry[] table;
    long size;
    long sizemask;
    long used;
}

table:哈希节点数组。
size:记录table大小。
sizemask:该值总是等于size-1,用于计算索引值。
used:记录table数组已使用节点数量。

dictEntry
public class dictEntry {
    sdshdr key;
    redisObject v;
    dictEntry next;
}

key:键。
v:值,可以存储指针、uint64整数、int64整数。
next:哈希冲突时形成无环单向链表,新元素总是添加在链表头部。
Redis使用的哈希算法
hash = dict.type.hashFunction(key);
index = hash & dict.ht[0].sizemask;
hashFunction函数底层使用MurmurHash2算法,其优点是随机分布性高。
哈希表扩展和收缩触发条件
负载因子 = dict.ht[0].used / dict.ht[0].table.size;
没有执行BGSAVE的时候,负载因子>=1触发哈希表扩展。
正在执行BGSAVE的时候,负载因子>=5触发哈希表扩展。
当负载因子<0.1时触发哈希表收缩。
哈希表扩展机制

  1. 初始化dict.ht[1].table,大小为大于dict.ht[0].used的第一个2的次方。
  2. dict.ht[0].table中的元素通过rehash移动至dict.ht[1].table。
  3. dict.ht[0]释放,并将dict.ht[1]设置为dict.ht[0]。
  4. 创建新的dict.ht[1]为下次rehash做准备。

渐进式rehash
渐进式rehash的目标是解决dict.ht[0].table保存键值对很多的情况下,rehash过程漫长的问题。
渐进式rehash触发条件:字典进行增删改查的操作时,进行一次渐进式rehash,dict.rehashidx+1。
渐进式rehash过程中,查询键值对会先查dict.ht[0],再查dict.ht[1]。
渐进式rehash过程中,所有新增动作都会添加至dict.ht[1]。

跳跃表

encoding=“REDIS_ENCODING_SKIPLIST”,ptr指向一个zskiplist对象。

zskiplist
public class zskiplist {
    zskiplistNode header;
    zskiplistNode tail;
    long length;
    int level;
}

header:指向跳跃表头节点。
tail:指向跳跃表尾节点。
length:跳跃表中节点数量(跳跃表头节点不计算在内,因为头节点比较特殊)
level:跳跃表中节点最大层数(跳跃表头节点不计算在内,因为头节点层数一直是32)

zskiplistNode
public class zskiplistNode {
    zskiplistLevel[] level;
    zskiplistNode backward;
    double score;
    Object obj;
}

level:跳跃表节点包含的层,每个节点层数是1至32之间的随机数。
backward:返回至上一个zskiplistNode的指针,返回时不能跳跃。
score:该节点分值。
obj:该节点成员对象。

zskiplistLevel
class zskiplistLevel {
    zskiplistNode forward;
    int span;
}

该类为zskiplistNode内部类。
forward:跳跃至下一个zskiplistNode的指针。
span:记录跨度值。

整数集合

encoding=“REDIS_ENCODING_INTSET”,ptr指向一个intset对象。

intset
public class intset {
    int encoding;
    int length;
    int[] contents;
}

encoding:INTSET_ENC_INT16 \ INTSET_ENC_INT32 \ INTSET_ENC_INT64。
length:集合包含元素数量。
contents:元素数组。

压缩列表

encoding=“REDIS_ENCODING_ZIPLIST”,ptr指向一个sequential对象。

sequential
public class sequential {
    int zlbytes;
    int zltail;
    int zlen;
    entry entry1;
    ...
    entry entryN;
    int zlend;
}

sequential对象其实是一个Object[],其内部约定了以下属性的位置。
zlbytes:记录整个压缩列表占用字节数。
zltail:记录压缩列表尾节点位置,在第几个字节。
zlen:压缩列表节点数量。
entry1-N:压缩列表节点。
zlend:特殊值"0xFF",表示压缩列表结尾。

entry
class entry{
    int previous_entry_length;
    String encoding;
    Object content;
}

previous_entry_length:前一节点字节长度,用于从后往前遍历。
encoding:记录content数据结构和长度,前2位表示数据结构(00、01、10代表char[],11代表int),后面表示长度。
content:具体记录的内容,可以是int,可以是char[]。
源码地址:RedisDataStructure模块

发布了14 篇原创文章 · 获赞 3 · 访问量 878

猜你喜欢

转载自blog.csdn.net/qq_37956177/article/details/103335652
今日推荐