形象化设计模式实战             HELLO!架构 
                   redis命令源代码解析

在redis之字符串命令源代码解析(一)中讲了get的简单实现,并没有对怎样取到数据做深入分析,这里将深入。

1、redisObject 数据结构。以及Redis 的数据类型


(一)中说set test "hello redis",“hello redis”会终于保存在robj中,redisObject是Redis的核心,数据库的每一个键、值,以及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_SET 2 // 集合

#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结构


Redis使用的是高效且实现简单的哈希作为字典的底层实现。


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之字符串命令源代码解析(二)的更多相关文章

  1. Spring源代码解析

    Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的启动:http://www.itey ...

  2. Spring源代码解析(收藏)

    Spring源代码解析(收藏)   Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的 ...

  3. Redis 使用指南:深度解析 info 命令

    Redis 是一个使用  ANSI C 编写的开源.基于内存.可选持久性的键值对存储数据库,被广泛应用于大型电商网站.视频网站和游戏应用等场景,能够有效减少数据库磁盘 IO, 提高数据查询效率,减轻管 ...

  4. 2016022608 - redis字符串命令集合

    redis字符串命令: Redis字符串命令用于在Redis管理字符串值.使用Redis字符串命令的语法如下所示: redis 127.0.0.1:6379> COMMAND KEY_NAME ...

  5. Redis之字符串类型命令

    String(字符串) string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value. string 类型是二进制安全的.意思是 ...

  6. redis位图(bitmap)常用命令的解析

    描述   bitmap是redis封装的用于针对位(bit)的操作,其特点是计算效率高,占用空间少,常被用来统计用户签到.登录等场景 常用命令及解析 常用命令 setbit key offset va ...

  7. C#使用zxing,zbar,thoughtworkQRcode解析二维码,附源代码

    最近做项目需要解析二维码图片,找了一大圈,发现没有人去整理下开源的几个库案例,花了点时间 做了zxing,zbar和thoughtworkqrcode解析二维码案例,希望大家有帮助. zxing是谷歌 ...

  8. NIO框架之MINA源代码解析(二):mina核心引擎

    NIO框架之MINA源代码解析(一):背景 MINA的底层还是利用了jdk提供了nio功能,mina仅仅是对nio进行封装.包含MINA用的线程池都是jdk直接提供的. MINA的server端主要有 ...

  9. SDWebImage源代码解析(二)

    上一篇:SDWebImage源代码解析(一) 2.缓存 为了降低网络流量的消耗.我们都希望下载下来的图片缓存到本地.下次再去获取同一张图片时.能够直接从本地获取,而不再从远程server获取.这样做的 ...

随机推荐

  1. 洛谷 P3203 [HNOI2010]弹飞绵羊 分块

    我们只需将序列分成 n\sqrt{n}n​ 块,对于每一个点维护一个 val[i]val[i]val[i],to[i]to[i]to[i],分别代表该点跳到下一个块所需要的代价以及会跳到的节点编号.在 ...

  2. [细节版]Let'sEncrypt 免费通配符/泛域名SSL证书添加使用教程

    参考网址:https://lnmp.org/faq/letsencrypt-wildcard-ssl.html 使用的dns服务商:阿里云 , 更多服务商地址可见参考网址. 遇见的问题一. [Sat ...

  3. 紫书 例题 10-3 UVa 10375 (唯一分解定理)

    这道题感觉非常的秀 因为结果会很大,所以就质因数分解分开来算 非常的巧妙! #include<cstdio> #include<vector> #include<cstr ...

  4. Maven 编译打包时如何忽略测试用例

    跳过测试阶段: mvn package -DskipTests 临时性跳过测试代码的编译: mvn package -Dmaven.test.skip=true maven.test.skip同时控制 ...

  5. Step by Step Do IOS Swift CoreData Simple Demo

    简单介绍 这篇文章记录了在 IOS 中使用 Swift 操作 CoreData 的一些基础性内容,因为缺乏文档,基本上都是自行实验的结果.错漏不可避免,还请谅解. 部分内容借鉴了 Tim Roadle ...

  6. Android设置头像,手机拍照或从本地相冊选取图片作为头像

     [Android设置头像,手机拍照或从本地相冊选取图片作为头像] 像微信.QQ.微博等社交类的APP,通常都有设置头像的功能,设置头像通常有两种方式: 1,让用户通过选择本地相冊之类的图片库中已 ...

  7. spfile

    1 让ORACLE自己主动从spfile启动  SQL> create spfile='/dev/vx/rdsk/vgora/lv_spfile' from pfile;  SQL> sh ...

  8. caffe 训练測试自己的数据集

    简单记录一下自己使用caffe的过程和遇到的一些问题. 下载caffe以及安装不具体叙述了. 可參照 http://caffe.berkeleyvision.org/installation.html ...

  9. 自己封装js组件 - 中级中高级

    接着做关于alert组件的笔记 怎么又出来个中高级呢 对没错 就是出一个中高级来刷流量呵呵呵,但是中高级也不是白叫的 这次主要是增加了widget类,增加了自己绑定的事件和触发事件的方法!这么做是为什 ...

  10. 10.bitset

    #include <iostream> //位运算,处理二进制非常方便,线性存储 #include <bitset> #include <string> using ...