形象化设计模式实战             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. Dispatch Groups

    Dispatch Groups are objects that allow several tasks to be grouped for later joining. Tasks can be a ...

  2. IIS支持10万个同时请求的设置

    1. 调整IIS 7应用程序池队列长度 由原来的默认1000改为65535. IIS Manager > ApplicationPools > Advanced Settings Queu ...

  3. [APIO2012]派遣 可并堆(左偏树)

    没啥说的,自底向上合并大根堆即可. 一边合并,一边贪心弹堆顶直到堆的总和不大于预算. Code: #include <cstdio> #include <algorithm> ...

  4. Android Studio 开发安卓软件时下载的工程项目 Sync with gradle 失败

    Sync with gradle 失败的原因有很多,其中很多时候会遇到下载下来的工程同步失败,目前的经验来看下载的工程同步失败均是由于下图中的两个配置其中某个缺少了 google() 或者 jcent ...

  5. String值传递剖析

    转载自 http://www.iteye.com/topic/412531 提要:本文从实现原理的角度上阐述和剖析了:在Java语言中,以String作为类型的变量在作为方法参数时所表现出的“非对象” ...

  6. pycharm日常填坑

    pycharm坑很多,今天又遇见了,好吧 ,填坑 场景:从别的地方拷贝过来的项目,导入本地的pycharm 然后选择 然后 还会报错....心累 报错内容: django commands canno ...

  7. php5权限控制修饰符

    1.public:public表明该数据成员.成员函数是对所有用户开放的,所有用户都可以直接进行调用 2.private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直 ...

  8. vue中使用viewerjs

    项目创建 插件Viewer.js vue init webpack mytest001 安装viewerjs npm install viewerjs 删掉生成的项目里面的helloWord.vue ...

  9. Python学习————字典的增删改查

    增加:dic1['KEY'] = value -->若之前有KEY,则会覆盖.若没有KEY,则新增至尾处dic.setdefault('KEY',value/None) --->若之前有K ...

  10. python基础4(小数据池,编码,深浅拷贝)

    1.==与is == 比较值是否相等 is比较内存地址是否相同 2.小数据池 为了节省内存,当数据在一个范围里的时候,两个值相同的变量指向的是小数据池里的同一个地址 数字范围:-5 ~ 256 num ...