redis之字符串命令源代码解析(二)
形象化设计模式实战 HELLO!架构
redis命令源代码解析
在redis之字符串命令源代码解析(一)中讲了get的简单实现,并没有对怎样取到数据做深入分析,这里将深入。
1、redisObject 数据结构。以及Redis 的数据类型
/* The actual Redis Object */
/*
* Redis 对象
*/
#define REDIS_LRU_BITS 24
#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */
#define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
typedef struct redisObject { // 类型,冒号后面跟数字,表示包括的位数。这样更节省内存
unsigned type:4; // 编码
unsigned encoding:4; // 对象最后一次被訪问的时间
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ // 引用计数
int refcount; <span style="color:#ff0000;">// 指向实际值的指针,指向上节的sdshdr->buf,而不是sdshdr,这还要归因于sds.c中的方法sdsnewlen返回的buf部分,而不是整个sdshdr</span>
void *ptr; } robj;
#define REDIS_STRING 0 // 字符串
#define REDIS_LIST 1 // 列表
#define REDIS_ZSET 3 // 有序集
#define REDIS_HASH 4 // 哈希表
#define REDIS_ENCODING_RAW 0 // 编码为字符串
#define REDIS_ENCODING_INT 1 // 编码为整数
#define REDIS_ENCODING_HT 2 // 编码为哈希表
#define REDIS_ENCODING_ZIPMAP 3 // 编码为zipmap
#define REDIS_ENCODING_LINKEDLIST 4 // 编码为双端链表
#define REDIS_ENCODING_ZIPLIST 5 // 编码为压缩列表
#define REDIS_ENCODING_INTSET 6 // 编码为整数集合
#define REDIS_ENCODING_SKIPLIST 7 // 编码为跳跃表
2、内部数据结构之dict(俗称字典)
1.1 dict结构
dict.h中定义例如以下:
/*
* 字典
*/
typedef struct dict { // 类型特定函数
dictType *type; // 私有数据
void *privdata; // 哈希表
dictht ht[2]; // rehash 索引
// 当 rehash 不在进行时,值为 -1
int rehashidx; /* rehashing not in progress if rehashidx == -1 */ // 眼下正在执行的安全迭代器的数量
int iterators; /* number of iterators currently running */ } dict;
哈希表dictht的结构:
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
/*
* 哈希表
*
* 每一个字典都使用两个哈希表,从而实现渐进式 rehash 。
*/
typedef struct dictht { // 哈希表数组
dictEntry **table; // 哈希表大小
unsigned long size; // 哈希表大小掩码。用于计算索引值
// 总是等于 size - 1
unsigned long sizemask; // 该哈希表已有节点的数量
unsigned long used; } dictht;
哈希表数组dictEntry的结构:
/*
* 哈希表节点
*/
typedef struct dictEntry { // 键
void *key; // 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v; // 指向下个哈希表节点。形成链表
struct dictEntry *next; } dictEntry;
那么一个dict能够图解表示为:
由图可清晰地看出redis字典哈希表所使用的哈希碰撞解决方法是链地址法,这种方法就是使用链表将多个哈希值同样的节点串连在一起,从而解决冲突问题。
1.2 dict实现setCommand
set命令终于会调用dict.c中的dictAdd方法将test => "hello redis" 保存到字典中
/* Add an element to the target hash table */
/*
* 尝试将给定键值对加入到字典中
*
* 仅仅有给定键 key 不存在于字典时。加入操作才会成功
*
* 加入成功返回 DICT_OK ,失败返回 DICT_ERR
*
* 最坏 T = O(N) ,平滩 O(1)
*/
int dictAdd(dict *d, void *key, void *val)
{
// 尝试加入键到字典,并返回包括了这个键的新哈希节点
// T = O(N)
dictEntry *entry = dictAddRaw(d,key); // 键已存在,加入失败
if (!entry) return DICT_ERR; // 键不存在,设置节点的值
// T = O(1)
dictSetVal(d, entry, val); // 加入成功
return DICT_OK;
}
整个set可简略例如以下图(此图省去了很多其他操作):
从图中你会发现,事实上key的过期时间就相当于是key的还有一个val,保存在还有一个dict中,简单地说就是有两个dict,一个是key=>value,一个是key=>expire。
1.3 dict哈希表的rehash
dict有两个ht。就是每一个字典有两个哈希表,为毛要有两个,其作用是对dict进行扩容和收缩,由于假设节点数量比哈希表的大小要大非常多的话,那么哈希表就会退化成多个链表,哈希表本身的性能优势就不再存在。
dict.c中的_dictExpandIfNeeded方法对哈希表何时可rehash作了推断:
// 一下两个条件之中的一个为真时,对字典进行扩展
// 1)字典已使用节点数和字典大小之间的比率接近 1:1
// 而且 dict_can_resize 为真
// 2)已使用节点数和字典大小之间的比率超过 dict_force_resize_ratio(默认值为5)
if (d->ht[0].used >= d->ht[0].size &&
(dict_can_resize ||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
{
// 新哈希表的大小至少是眼下已使用节点数的两倍
// T = O(N)
return dictExpand(d, d->ht[0].used*2);
<span style="white-space:pre"> </span>//dictExpand的过程就是获取ht[0]的size,然后copy到ht[1]中,就是table大小是文件夹使用节点数的两倍。最后再将rehashidx设为0,标识着能够进行rehash了
}
rehash的代码这里不贴出,由于实现简单。大致的过程是
1. 释放ht[0] 的空间。
2. 用ht[1] 来取代ht[0] ,使原来的ht[1] 成为新的ht[0] ;
3. 创建一个新的空哈希表。并将它设置为ht[1] 。
4. 将字典的rehashidx 属性设置为-1 ,标识rehash 已停止;
但我在看源码时,发现并非一将rehashidx设为0就进行rehash操作的,而是当再次dictAdd时,才dictRehash(d,1),第二个參数是1,也就是说每次rehash仅仅会对单个索引上的节点进行迁移,这样的做法差点儿不会消耗什么时间。client能够高速的得到响应。当然这样的除了这样的方式进行rehash外,Redis还有个定时任务调用dictRehashMilliseconds方法,在规定的时间内。尽可能地对数据库字典中那些须要rehash的字典进行rehash,从而加速rehash的进程。
如今我知道Redis并非一下子就rehash完毕,而是须要一定时间的,那么假设client在这段时间内向Redis发送get set del请求,那Redis会怎样处理,从而保证数据的完整和正确呢?
• 由于在rehash 时,字典会同一时候使用两个哈希表。所以在这期间的全部查找、删除等操作,除了在ht[0] 上进行。还须要在ht[1] 上进行。
• 在运行加入操作时,新的节点会直接加入到ht[1] 而不是ht[0] 。这样保证ht[0] 的节点数量在整个rehash 过程中都仅仅减不增。
redis之字符串命令源代码解析(二)的更多相关文章
- Spring源代码解析
Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的启动:http://www.itey ...
- Spring源代码解析(收藏)
Spring源代码解析(收藏) Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的 ...
- Redis 使用指南:深度解析 info 命令
Redis 是一个使用 ANSI C 编写的开源.基于内存.可选持久性的键值对存储数据库,被广泛应用于大型电商网站.视频网站和游戏应用等场景,能够有效减少数据库磁盘 IO, 提高数据查询效率,减轻管 ...
- 2016022608 - redis字符串命令集合
redis字符串命令: Redis字符串命令用于在Redis管理字符串值.使用Redis字符串命令的语法如下所示: redis 127.0.0.1:6379> COMMAND KEY_NAME ...
- Redis之字符串类型命令
String(字符串) string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value. string 类型是二进制安全的.意思是 ...
- redis位图(bitmap)常用命令的解析
描述 bitmap是redis封装的用于针对位(bit)的操作,其特点是计算效率高,占用空间少,常被用来统计用户签到.登录等场景 常用命令及解析 常用命令 setbit key offset va ...
- C#使用zxing,zbar,thoughtworkQRcode解析二维码,附源代码
最近做项目需要解析二维码图片,找了一大圈,发现没有人去整理下开源的几个库案例,花了点时间 做了zxing,zbar和thoughtworkqrcode解析二维码案例,希望大家有帮助. zxing是谷歌 ...
- NIO框架之MINA源代码解析(二):mina核心引擎
NIO框架之MINA源代码解析(一):背景 MINA的底层还是利用了jdk提供了nio功能,mina仅仅是对nio进行封装.包含MINA用的线程池都是jdk直接提供的. MINA的server端主要有 ...
- SDWebImage源代码解析(二)
上一篇:SDWebImage源代码解析(一) 2.缓存 为了降低网络流量的消耗.我们都希望下载下来的图片缓存到本地.下次再去获取同一张图片时.能够直接从本地获取,而不再从远程server获取.这样做的 ...
随机推荐
- SQL 自动记录存储过程,表,函数的创建修改和删除 -相当于SVN一样
在项目开发过程中,项目管理者通常都很希望对项目的开发进展有一个日志的记录.代码的记录和管理可以通过TFS或者VSS等工具去管理.但是数据库却没有记录开发日志这一功能.这在实际开发中很不方便, ...
- How Javascript works (Javascript工作原理) (九) 网页消息推送通知机制
个人总结: 1.介绍了网页消息推送通知机制 全文地址:https://github.com/Troland/how-javascript-works 这是 JavaScript 工作原理的第九章. 现 ...
- (WC2018模拟十二)【FJOI2016集训Day7T1】挑选子序列
题解: 这题太神仙了,先咕着,放个令人****的官方题解(精确覆盖问题是NP完全问题啊摔) Dancing Link X学习资料:QAQ TAT qwq Orz Donald Knuth大神... p ...
- HDU Integer's Power(容斥原理)
题意 求[l,r]的最大指数和(1<=l,r<=10^18) 最大指数和(如64=8^2=4^3=2^6,所以64的最大指数和是6) 题解 很明显我们可以先求出[1,n]的最大指数和,然后 ...
- 学习《PythonWeb开发实战(董伟明)》中文PDF+源代码
python可以用了进行数据分析,也可以进行Web开发,一般会使用django或者flask等进行开发. 国内介绍python web的书有写的不错的,推荐看看<PythonWeb开发实战> ...
- caioj 1114 树形动态规划(TreeDP)3.0:多叉苹果树【scy改编ural1018二叉苹果树】
一波树上背包秒杀-- #include<cstdio> #include<cstring> #include<algorithm> #include<vect ...
- 网页里如何使用js禁用F12事件
接上一篇,突然想起来,类似于网页里如何使用js禁用鼠标右击事件,还有禁用F12事件也可以禁用一下,总所周知,对于Web开发人员来说,常常要进行界面的调试.使用F12调试工具能够很方便地进行调试,查看h ...
- C#调用带结构体指针的C Dll的方法
在C#中调用C(C++)类的DLL的时候,有时候C的接口函数包含很多参数,而且有的时候这些参数有可能是个结构体,而且有可能是结构体指针,那么在C#到底该如何安全的调用这样的DLL接口函数呢?本文将详细 ...
- Eclipse WEB项目更改项目名
可能很多人都碰到过 WEB 项目需要改名字的事情,不过不是每个人都能很顺畅地完成这个事情.这里简单给大家介绍两种改项目名的方法 1. 在Eclipse 中修改项目名 没错这种方法跟你预料的一样简单,当 ...
- 在Windows上面安装多个Memcached
在Windows上面安装多个Memcached sc create "memcached Server3" start= auto binPath= "D:\memcac ...