java使⽤xml存储数据_聊⼀聊Redis数据内部存储使⽤到的数
据结构
Redis 数据库虽然⼀直都在使⽤,但是对其内部存储结构之类的,都没有研究过,哪怕是⾯试的时候都没有准备过这⽅⾯的东西。最近在看⼀门⽹课,⾥⾯有讲到过这⼀块的内容,结合了《Redis 设计与实现》这本书,粗略的整理了 Redis 的内部存储结构。就是下⾯这张图。
对于 Redis 数据库,绝⼤多数⼈都知道有每个 Redis 实例有 16 个数据库,但是对于内部是怎么扭转的
⼤部分⼈可能不太清楚,反正我是不清楚。整体流程差不多就是上图表⽰的那样吧,知识⾯有限,难免存在缺漏,凑合着看吧。
其实前⾯的这些都不是太重要,重要的是后⾯那四种数据结构和 redisObject。不管重不重要了,都来过⼀遍吧。
redisDb
redisDb 就是数据库实例,存储了真实的数据,每个 Redis 实例都会有 16 个 redisDb。redisDb 的结构定义如下:
typedef struct redisDb {
dict *dict;                /* The keyspace for this DB */
dict *expires;              /* Timeout of keys with a timeout set */
dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys;          /* Blocked keys that received a PUSH */
dict *watched_keys;        /* WATCHED keys for MULTI/EXEC CAS */
int id;                    /* Database ID */
long long avg_ttl;          /* Average TTL, just for stats */
list *defrag_later;        /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
redisDb 结构体中有 8 个参数:
dict:dict 是⽤来存储数据的,当前 DB 下的所有数据都存放在这⾥。
expires:存储 key 与过期时间的映射。
blocking_keys:存储处于阻塞状态的 key 及 client 列表。⽐如在执⾏ Redis 中 list 的阻塞命令 blpop、brpop 或者 brpoplpush 时,如果对应的 list 列表为空,Redis 就会将对应的 client 设为阻塞状态,同时将该 client 添加到 DB 中 blocking_keys 这个阻塞dict。
ready_keys:存储解除阻塞的 Key。当有其他调⽤⽅在向某个 key 对应的 list 中增加元素时,Redis
会检测是否有 client 阻塞在这个 key 上,即检查 blocking_keys 中是否包含这个 key,如果有则会将这个 key 加⼊ read_keys 这个 dict 中。同时也会将这个 key 保存到 server 中的⼀个名叫 read_keys 的列表中。
watched_keys:当 client 使⽤ watch 指令来监控 key 时,这个 key 和 client 就会被保存到 watched_keys 这个 dict 中。
id:数据库编号。
Dict
Dict 数据结构在 Redis 中⾮常的重要,你可以看到在 redisDb 中,8 个字段中有 5 个是 dict,并且在其他地⽅也有⼤量的应⽤。dict 结构体定义如下:
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;
dict 本⾝是⽐较简单的,字段也不多,其中有三个字段⽐较重要,有必要了解⼀下:
type:⽤于保存 hash 函数及 key/value 赋值、⽐较函数。
ht[2]:⽤来存储数据的数组。默认使⽤的是 0 号数组,如果 0 号哈希表元素过多,则分配⼀个 2 倍 0 号哈希表⼤⼩的空间给 1 号哈希表,然后进⾏逐步迁移。
rehashidx:⽤来做标志迁移位置。
Dictht & DictEntry
typedef struct dictht {
# 哈希表数组
dictEntry **table;
# 哈希表⼤⼩
unsigned long size;
#哈希表⼤⼩掩码,⽤于计算索引值
unsigned long sizemask;
# 该哈希表已有节点的数量
unsigned long used;
} dictht;
typedef struct dictEntry {
# 键
void *key;
union {
# 值
void *val;redis支持的五种数据类型
uint64_t u64;
int64_t s64;
double d;
} v;
# 指向下个哈希表节点,形成链表
struct dictEntry *next;
} dictEntry;
dictht 数据结构没啥说的,dictEntry 是真正挂载数据的节点,跟 Java 中的 Map 有⼀点像,采⽤ key-value 的映射⽅式。key 采⽤的是sds 结构的字符串,value 为存储各种数据类型的 redisObject 结构。
redisObject、sds还有其他⼏种数据结构才是重点,⾯试的时候有可能会出现,作为使⽤者,其实了解这⼏个就够了。redisObject
redisObject 可以理解成 Redis 数据的数据头,⾥⾯定义了⼀些数据的信息。redisObject 结构体定义如下:
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
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;
redisObject 结构体字段不多,就 5 个字段,但是这⼏个字段都挺重要的,过⼀下这 5 个字段的含义:
type
type 表⽰的是 Redis 对象的数据类型,代表这条数据是什么类型,⽬前 Redis 有 7 种类型。分别为:
OBJ_STRING:字符串对象。
OBJ_LIST:列表对象。
OBJ_SET:集合对象。
OBJ_ZSET:有序集合对象。
OBJ_HASH:哈希对象。
OBJ_MODULE:模块对象。
OBJ_STREAM:消息队列/流对象。
encoding
encoding 是 Redis 对象的内部编码⽅式,即这条数据最终在内部是以哪种数据结构存放的。这个字段的作⽤还是相当⼤的,我看了⼀下源码,⽬前 Redis 中有 10 种编码⽅式,如下:
OBJ_ENCODING_RAW
OBJ_ENCODING_INT
OBJ_ENCODING_HT
OBJ_ENCODING_ZIPLIST
OBJ_ENCODING_ZIPMAP
OBJ_ENCODING_SKIPLIST
OBJ_ENCODING_EMBSTR
OBJ_ENCODING_QUICKLIST
OBJ_ENCODING_STREAM
OBJ_ENCODING_INTSET
LRU
LRU 存储的是淘汰数据⽤的 LRU 时间或 LFU 频率及时间的数据。
refcount
refcount 记录 Redis 对象的引⽤计数,⽤来表⽰对象被共享的次数,共享使⽤时加 1,不再使⽤时减 1,当计数为 0 时表明该对象没有被使⽤,就会被释放,回收内存。
ptr
ptr 是真实数据存储的引⽤,它指向对象的内部数据结构。⽐如⼀个 string 的对象,内部可能是 sds 数据结构,那么 ptr 指向的就是sds,除此之外,ptr 还可能指向 ziplist、quicklist、skiplist。
redisObject ⼤概就这些,下⾯在聊⼀聊 Redis 中内存常⽤的四种数据结构。
1.sds(简单动态字符串)
Redis没有直接使⽤C语⾔传统的字符串表⽰(以空字符结尾的字符数组,以下简称C字符串),⽽是⾃⼰
构建了⼀种名为简单动态字符串(simple dynamic string,SDS)的抽象类型,并将 SDS ⽤作 Redis 的默认字符串表⽰。
实现者为了较少开销,就 sds 定义了 5 种结构体,分别为:sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64。这样最终存储的时候 sds 会根据字符串实际的长度,选择不同的数据结构,以更好的提升内存效率。5 种结构体的源代码如下:
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[];
};
除了 sdshdr5 之外,其他的⼏个数据结构都包含 4 个字段:
len:字符串的长度。
alloc:给字符串分配的内存⼤⼩。
flags:当前字节数组的属性。
buf:存储字符串真正的值和⼀个结束符 \0。
在 redisObject 中有⼀个编码⽅式的字段,sds 数据结构有三种编码⽅式,分别为 INT、RAW 、EMBSTR。INT 就相对⽐较简单,ptr 直接指向了具体的数据。在这⾥就简单的说⼀说 RAW 和 EMBSTR 的区别。
在 Redis 源码中,有这么⼀段代码,来判断采⽤哪种编码⽅式。当保存的字符串长度⼩于等于 44 ,采⽤的是 embstr 编码格式,否则采⽤ RAW 编码⽅式。(具体的长度可能每个版本定义不⼀样)
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}
embstr 和 raw 编码⽅式最主要的区别是在内存分配的时候。embstr 编码是专门⽤于保存短字符串的⼀种优化编码⽅式,raw 编码会调⽤两次内存分配函数来分别创建 redisObject 结构和 sdshdr 结构,⽽ embstr 编码则通过调⽤⼀次内存分配函数来分配⼀块连续的空间,空间中依次包含redisObject和sdshdr两个结构。
embstr 编码
raw 编码
raw 编码
sds 主要是作为字符串的内部数据结构,同时 sds 也是 hyperloglog、bitmap 类型的内部数据结构。
2.ziplist(压缩列表)
ziplist 是专门为了节约内存,并减少内存碎⽚⽽设计的数据结构,ziplist是⼀块连续的内存空间,可以连续存储多个元素,没有冗余空间,是⼀种连续内存数据块组成的顺序型内存结构。
ziplist 主要包含 5 个部分:
zlbytes:ziplist所占⽤的总内存字节数。
Zltail:尾节点到起始位置的字节数。
Zllen:总共包含的节点/内存块数。
Entry:ziplist 保存的各个数据节点,这些数据点长度随意。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。