栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 其他

Redisの数据库设计

其他 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Redisの数据库设计

Redisの数据库设计

redis的结构图如下:

​ 下面,分别对各个数据结构进行介绍


RedisServer

​ 众所周知,redis的配置文件是redis.conf,redis在启动时会加载配置文件。那么配置文件的配置项都加载在哪里呢?

​ 阅读redis源码,会看到Redis将配置文件的配置数据加载到数据结构redisServer,并将所有的数据库保存在db数组中。根据配置文件中的database,决定要创建多少个数据库dbnum。

struct redisServer {
	//其余配置...
    // 数据库
    redisDb *db;
	//数据库的数量
    int dbnum;                      
	//其余配置...
};

​ 默认情况下,redis会创建16个数据库。


RedisClient

​ RedisClient的数据结构如下,可以看到,每个Redis客户端都有自己的目标数据库。db是一个指向了redisDb的指针,记录了客户端当前的目标数据库。当执行读、写等命令时,redis客户端就是通过操作目标数据库来执行这些命令的。

​ 当执行select来切换数据库时,redisClent通过修改db指针,让它指向不同的数据库。

struct redisClient {
	//其余配置...
    // 客户端正在使用的数据库
    redisDb *db;           
	//其余配置...
};

redisDb

​ redisDb的数据结构如下。dict(字典)保存了这个数据库所有的键值对,称为键空间。本质上,redis是个k-v数据库服务器。

​ dict和我们见到的数据库是直接对应的,dict可以理解为是个hashtable,它的key是字符串对象,值就是数据库的值,可以是字符串对象、列表对象、哈希表对象、集合或者有序集合,任意一种redis对象。

typedef struct redisDb {
    // 数据库键空间,保存着数据库中的所有键值对
    dict *dict;                 
    // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
    dict *expires;              
    // 正处于阻塞状态的键
    dict *blocking_keys;        
    // 可以解除阻塞的键
    dict *ready_keys;           
    // 正在被 WATCH 命令监视的键
    dict *watched_keys;         
    struct evictionPoolEntry *eviction_pool;    
    // 数据库号码
    int id;                     
    // 数据库的键的平均 TTL ,统计信息
    long long avg_ttl;          
} redisDb;

dict

​ 字典的数据结构如下所示。

type属性是一个指向dictType结构的指针,每个dictType保存了一簇用于操作特定类型键值对的函数。Redis为用途不同的字典设置了不同的类型特定函数

privdata属性保存了需要传给那些类型特定函数的可选参数

ht属性是包含两个dictht的数组,一般情况下,字典只使用ht[0]哈希表,在rehash过程中,才会使用ht[1]

rehashidx属性记录了rehash的状态,如果为-1,就表示当前没有在进行rehash。如果不是-1,就表示正在rehash的哈希表ht[0]的索引

typedef struct dict {
    // 类型特定函数
    dictType *type;
    // 私有数据
    void *privdata;
    // 哈希表
    dictht ht[2];
    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; 
    // 目前正在运行的安全迭代器的数量
    int iterators; 

} dict;

Redis添加数据的过程

​ 例如在命令行执行lpush命令,会发生什么呢?

127.0.0.1:6379> lpush mylist 11 22 33 44 55
(integer) 5

不完全准确的添加数据过程:

那么,必然会存在哈希冲突

键冲突

当有两个或者两个以上的键被分配到了哈希表的同一个索引上,就称这些键发生了键冲突。

redis使用链地址法解决键冲突

rehash

哈希表的负载因子=哈希表已保存的节点数量/哈希表大小

当哈希表保存的键值对过多或者过少时,为了让负载因子维持在合理的范围内,会通过执行rehash操作,来对哈希表进行扩容或者收缩。

扩展的rehash操作:

​ 服务器正在执行bgsave或者bgrewriteaof操作的时候,需要进行rehash的判断条件-负载因子更大,是因为在执行bgsave或者bgrewriteaof操作的时候,redis需要创建当前服务器进程的子进程,而大多数操作系统使用了写时复制技术来优化子进程使用效率。所以服务器提高了执行扩展操作所需要的负载因子,从而尽可能避免在子进程存在期间进行哈希表扩展或者收缩操作。

​ 当负载因子<0.1 会自动进行收缩操作

渐进式rehash

上面说的rehah操作并不是一次性完成的,当哈希表保存的键值对数量过多时,如果一次性rehash的话,可能会导致服务器在这段时间内停止服务。

所以,redis选择分多次、渐进式的将ht][0]的键值对rehash到ht[1]:在rehash期间,每次对字典进行添加、删除、查找、更新操作时,除了执行指定操作外,还会将ht[0]在rehashidx索引上的键值对rehash到ht[1],当ht[0]的所有键值对都rehash到ht[1],将rehashidx置为-1,表示rehash操作已完成

添加数据的流程:


dictht

​ dictht是字典dict所使用的哈希表,哈希表的table是个数组,每个数组元素都是个指向dictEntry结构的指针,每个dictEntry结构存储了一个键值对。

typedef struct dictht {
    // 哈希表数组
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;
    // 该哈希表已用节点的数量
    unsigned long used;
} dictht;

dictEntry

​ dictEntry是哈希表的节点,每个dictEntry结构都保存着一个键值对,

key保存着键值对中的键,是以sds数据结构存储

v属性保存键值对中的值,它可以是个指针,也可以说unit64_t整数,或者是int64_t整数

next属性是哈希表节点的指针,这个指针将多个哈希值相同的键值链接,来解决键冲突问题

typedef struct dictEntry {
    //键
    void *key;
    //值,同一时间只会用下面几种的一种数据结构
    union{
    	//redisObject
    	void  *val;
    	//unit64_t整数
    	unit64_t u64;
    	//int64_t整数
    	int64_t s64;
    	double d;
    }v;
    //指向下个节点的指针
    struct dictEntry *next;
} dictEntry;

redisObject

​ redis中的每个对象都由一个redisObject结构表示

type属性记录了对象的类型,由操作的命令类型决定。如 set:string ;hset:hash

encoding属性记录了值对象底层实现的数据结构

refcount:引用计数,通过这个属性就可以跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收

lru:记录对象最后一次被程序访问的时间,用于计算键的空转时长。当回收内存算法是volatile-lru或者allkeys-lru时,这些键会优先被服务器释放

typedef struct redisObject {
    //类型:string/list/hash/set/zset
    unsigned type:4;
    //编码 int/embstr/raw/ziplist/quicklist/hashtable/intset/skiplist
    unsigned encoding:4;
    //内存淘汰算法相关
    unsigned lru:LRU_BITS;
    //引用计数法
    int refcount;
    //指向底层数据结构的指针
    void *ptr;
} redisObject;
不同类型对应的底层数据结构

用object encoding命令就可以看到不同类型的实际底层数据结构

每种数据类型根据不同的情况来使用不同的数据结构,从而极大的提升了redis的灵活性与效率,优化某一场景下的效率

string

四个字符串对象:

127.0.0.1:6379> set intstr 77
OK
127.0.0.1:6379> set doublestr 77.7
OK
127.0.0.1:6379> set shortstr hello
OK
127.0.0.1:6379> set longstr aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
OK

分别用type命令查看他们的数据类型:

127.0.0.1:6379> type intstr
string
127.0.0.1:6379> type doublestr
string
127.0.0.1:6379> type shortstr
string
127.0.0.1:6379> type longstr
string

​ 再查看他们的底层数据结构:

127.0.0.1:6379> object encoding intstr
"int"
127.0.0.1:6379> object encoding doublestr
"embstr"
127.0.0.1:6379> object encoding shortstr
"embstr"
127.0.0.1:6379> object encoding longstr
"raw"

redis保存字符串对象的判断逻辑:

raw与embstr

​ embstr编码是保存短字符串的一种优化编码方式,本质上与raw一样,都使用redisObject结构与sdshdr来表示字符串对象

​ raw编码会调用两次内存分配:创建redisObject结构与sdshdr结构

​ embstr:调用一次内存分配函数分配一块连续的内存空间,依次包含redisObject与sdshdr

​ 通过这种优化,embstrk只需要调用依次内存分配函数,释放的时候也只需要调用一次。并且因为embstr占用的内存是连续的,所以可以更好的利用缓存带来的优势。

list

3.2版本以前,redis使用ziplist与linkedlist作为列表对象的编码。但是linkedlist的附加空间太高,因为每个节点都需有prev与next指针(8byte*2),而且每个节点的内存都需要单独分配空间,影响内存管理效率,3.2版本以后,redis使用quicklist代替了ziplist与linkedlist。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/279265.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号