形象化设计模式实战             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. ActiveMQ学习笔记(21)----ActiveMQ集成Tomcat

    1. 监控和管理Broker Web Console 方式:直接访问ActiveMQ的管理页面:http://localhost:8161/admin,默认的用户名和密码是admin/admin.具体 ...

  2. ActiveMQ学习笔记(5)----Broker的启动方式

    Broker:相当于一个ActiveMQ服务器实例,在实际的开发中我们可以启动多个Broker. 命令行启动参数示例如下: 1. activemq start 使用默认的activemq.xml来启动 ...

  3. 【BZOJ3730】震波 - 动态点分治

    题意: Description 在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i]. 不幸的是,这片土地常常发生地震, ...

  4. 【BZOJ2006】【NOI2010】超级钢琴

    题意: Description 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号为1至n.第i个音符 ...

  5. [arc076e]connected?

    题意: 给出一个$R\times C$的棋盘,其中$1$到$N$之间的每个正整数都会在棋盘上出现两次,第$i$个数出现的位置是$(X_{i,1},Y_{i,1})$和$(X_{i,2},Y_{i,2} ...

  6. sql知识小记

    1.在sql语句中,单引号嵌套时,使用单引号做转义

  7. 队列(Queue)-c实现

    相对而言,队列是比较简单的. 代码还有些warning,我改不动,要找gz帮忙. #include <stdio.h> typedef struct node { int data; st ...

  8. docker系列之一 image和container

    docker images往往不知不觉就占满了硬盘空间,为了清理冗余的image,可采用以下方法: 1.进入root权限 sudo su 2.停止所有的container,这样才能够删除其中的imag ...

  9. vue源码之响应式数据

    分析vue是如何实现数据响应的. 前记 现在回顾一下看数据响应的原因. 之前看了vuex和vue-i18n的源码, 他们都有自己内部的vm, 也就是vue实例. 使用的都是vue的响应式数据特性及$w ...

  10. Qt之QDesktopServices

    简述 QDesktopServices类提供的函数用于访问常见的桌面服务. 许多桌面环境都会提供一系列服务,可以通过应用程序来执行常见任务,如:以用户应用程序首选项的方式,打开一个网页. 此类包含为服 ...