1
简介
redis是一个类似
memcached的
key/value存储系统,它支持存储的
value类型相对较多,包括
string(字符串
)、
list(链表
)、
set(集合
)和
zset(有序集合
)。在此基础上,
redis支持各种不同方式的排序。与
memcached一样,为了保证效率,数据都是缓存在内存中。区别的是
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了
master-slave(主从
)同步。
2
分析
2.1
协议
本文是基于当前最新版本
redis 1.2.6版本进行阅读分析的,支持的主要功能协议列表如下:
string
|
get/set/setnx/del/exists/incr/decr/mget
|
list
|
rpush/lpush/rpop/lpop/llen/lindex/lset/lrange/ltrim/lrem/rpoplpush
|
set
|
sadd/srem/smove/sismember/scard/spop/srandmember/
sinter/sinterstore/sunion/sunionstore/sdiff/sdiffstore/smembers
|
zset
|
zadd/zincrby/zrem/zremrangebyscore/zrange/zrangebyscore/
zcount/zrevrange/zcard/zscore/incrby/descrby/
|
|
select/move/rename/renamenx/expire/expireat/sort/sync
|
请求采用文本协议,整体分为两种:
non-bulk(不带二进制数据
)和
multi-bulk(带二进制数据的
)协议,具体协议格式分别如下:
non-bulk
|
command argv … argv bulk_len\r\n
|
bulk_data\r\n
|
2.2
总体结构
程序运行的主流程如下图所示:
2.3
事件循环
redis网络实现没有采用开源的网络框架,具体的源文件为
ae.h和
ae.c。用
aeEventLoop保存基于事
件系统的事件主循环,把
event分为句柄事件
(FileEvent)和超时事件
(TimeEvent),用
aeFiredEvent保存激活后的
fd和
event。相关的数据结构如下:
/* File event structure */
typedef struct aeFileEvent {
int mask; /* one of AE_(READABLE|WRITABLE|EXCEPTION) */
aeFileProc *rfileProc;
aeFileProc *wfileProc;
aeFileProc *efileProc;
void *clientData;
} aeFileEvent;
/* Time event structure */
typedef struct aeTimeEvent {
long long id; /* time event identifier. */
long when_sec; /* seconds */
long when_ms; /* milliseconds */
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeTimeEvent *next;
} aeTimeEvent;
/* A fired event */
typedef struct aeFiredEvent {
int fd;
int mask;
} aeFiredEvent;
/* State of an event based program */
typedef struct aeEventLoop {
int maxfd;
long long timeEventNextId;
aeFileEvent events[AE_SETSIZE]; /* Registered events */
aeFiredEvent fired[AE_SETSIZE]; /* Fired events */
aeTimeEvent *timeEventHead;
int stop;
void *apidata; /* This is used for polling API specific data */
} aeEventLoop;
回调接口的定义如下:
// 句柄事件回调函数
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
// 超时回调函数
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
// 超时
event删除回调函数
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
在网络相关操作中,定义了一组公共操作接口:
aeApiCreate/ aeApiFree/aeApiAddEvent/aeApiDelEvent/aeApiPoll/aeApiName
。在
ae_epoll.c、
ae_kqueue.c和
ae_select.c中,分别实现了基于
epoll/kqueue和
select系统调用的接口。在
ae.c中会根据宏来
include对应的文件,系统调用的选择顺序依次为
epoll -> kqueue -> select。
事件主循环实现在
aeMain中,内部会调用
aeProcessEvents函数。函数内部会先调用
aeApiPoll处理
fd上的注册事件,若
fd上有对应事件激活,调用对应的
aeFileProc进行处理;然后调用
processTimeEvents函数扫描超时链表处理已超时的超时事件,调用对应的回调
aeTimeProc处理。需要注意的是,
aeEventLoop中采用单向链表实现了超时
event链表,并且插入
timeEvent时没有保证按超时时刻有序,导致每次查找
(aeSearchNearestTimer)离当前时间最近的
timeEvent的复杂度为
O(N),
aeDeleteTimeEvent删除
timeEvent时的复杂度也为
O(N), 删除
timeEvent后需要从链表开始处重新查找已超时的
timeEvent。在
timeEvent链表长度较长时,操作效率会相对较低。
2.4
数据结构
2.4.1
sds (字符串
)
redis采用结构
sdshdr和
sds封装了字符串,字符串相关的操作实现在源文件
sds.h/sds.c中。
sdshdr
数据结构定义如下:
typedef char *sds;
struct sdshdr {
long len;
long free;
char buf[];
};
2.4.2
list(双向链表
)
对
list的定义和实现在源文件
adlist.h/adlist.c,相关的数据结构定义如下:
// list
迭代器
typedef struct listIter {
listNode *next;
int direction;
} listIter;
// list
数据结构
typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned int len;
listIter iter;
} list;
2.4.3
dict(hash表
)
在源文件
dict.h/dict.c中实现了
hashtable的操作,数据结构的定义如下:
// dict
中的元素项
typedef struct dictEntry {
void *key;
void *val;
struct dictEntry *next;
} dictEntry;
// dict
相关配置函数
typedef struct dictType {
unsigned int (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
// dict
定义
typedef struct dict {
dictEntry **table;
dictType *type;
unsigned long size;
unsigned long sizemask;
unsigned long used;
void *privdata;
} dict;
// dict
迭代器
typedef struct dictIterator {
dict *ht;
int index;
dictEntry *entry, *nextEntry;
} dictIterator;
dict
中
table为
dictEntry指针的数组,数组中每个成员为
hash值相同元素的单向链表。
set是在
dict的基础上实现的,指定了
key的比较函数为
dictEncObjKeyCompare,若
key相等则不再插入。
2.4.4
zset(排序
set)
typedef struct zskiplistNode {
struct zskiplistNode **forward;
struct zskiplistNode *backward;
double score;
robj *obj;
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
zset利用
dict维护
key -> value的映射关系,用
zsl(zskiplist)保存
value的有序关系。
zsl实际是叉数
不稳定的多叉树,每条链上的元素从根节点到叶子节点保持升序排序。如果把
zsl中出现在不同层的同一个节点当作不同的节点,则整颗树上只有根节点包含多个子节点,其他的子节点都至多有一个子节点。
insert时
Node的指针可能会插入到多条链中,实现时保证插入的每一个元素会出现在下标为
0的左子树上,因此可以通过该左子树遍历所有元素。
zsl的结构图如下所示:
插入节点时会随机生成
level,
level决定了节点会插入到哪几条路径上。由于
level>=1,因此每个节点都会在下标为
0的路径上存在。可以看到,
level越大的路径上节点数目越少。在查找
>=score的节点时,利用这一特点,按照
level递减的顺序从最大的
level对应的路径开始查找。找到该路径上小于
score的最大节点
x后,对
level-1转换路径从
x->forward[i]继续开始查找,直到
level最后减为
0。返回
x->forward[0],若不存在
>=score的元素,则
x->forward[0]为
NULL。
zsl
的插入过程如下图所示:
2.5
主从
(master-slave)同步
启动时
slave会向
master发出指令
SYNC进行全数据同步,
slave会阻塞式等待数据同步完成加载到
内存中进行初始化后,才能开始请求处理。运行时数据同步是通过
master转发请求给
slave列表实现的,仅转发对数据有修改操作的请求。同步的状态机如下:
2.6
存储文件格式
redis使用了两种文件格式:全量数据和增量请求。全量数据格式是把内存中的数据写入磁盘,
便于下次读取文件进行加载;增量请求文件则是把内存中的数据序列化为操作请求,用于读取文件进行
replay得到数据,序列化的操作包括
SET、
RPUSH、
SADD、
ZADD。
redis对
interger根据值范围采用不同的编码存储,具体如下:
值范围
|
字节数
(byte)
|
编码格式
|
[0, 1 << 6 – 1]
|
1
|
00 | value
|
[1 << 6, 1 << 14 – 1]
|
2
|
01 | (value >> 8) | value & oxFF
|
[1 << 14, 1 << 31 – 1]
|
5
|
10 000000 | htonl(value)
|
redis对数值类的
string object编码存储格式如下:
值范围
|
字节数
(byte)
|
编码格式
|
[-(1 << 7), 1 << 7 – 1]
|
2
|
1100 0000 | value & 0xFF
|
[-(1 << 15), 1 << 15 – 1]
|
3
|
1100 0001 |
(value >> 8) & 0xFF
| value & oxFF
|
[-(1 << 31)], 1 << 31 – 1]
|
5
|
1100 0010 |
value & oxFF |
(value >> 8) & 0xFF
| (value >> 16) & 0xFF
| (value >> 24) & 0xFF
|
redis支持字符串压缩存储,压缩的编码格式如下:
1100 0011
|
compl_len
(
压缩后的长度
)
|
orig_len
(
压缩前的长度
)
|
comp_value
(
压缩后的内容
)
|
2.6.1
data文件格式
2.6.2
append文件格式
RedisDb
|
*2
|
$6
|
SELECT
|
$length(index)
|
index:long
|
entry
|
*3
|
$3
|
SET
|
$length(key)
|
key
|
$length(value)
|
value
|
entry
|
*3
|
$5
|
RPUSH
|
$length(key)
|
key
|
$length(value)
|
value
|
entry
|
*3
|
$4
|
SADD
|
$length(key)
|
key
|
$length(value)
|
value
|
entry
|
*3
|
$4
|
ZADD
|
$length(key)
|
key
|
$length(score)
|
score:double
|
$length(value)
|
value
|
entry
|
…
|
RedisDb
|
…
|
|
|
|
|
|
|
RedisDb
|
…
|
entry
|
|
entry
|
entry
|
3
问题
3.1
内存泄露
在源文件
hiredis.c中函数
redisReadIntergerReply内部,会出现没有释放分配的内存块。
static redisReply *redisReadIntegerReply(int fd) {
//
在函数
redisReadLine
内部分配了
sds
需要的内存
sds buf = redisReadLine(fd);
redisReply *r = zmalloc(sizeof(*r));
// PROBLEM: buf
为
NULL
时
,
没有释放上面分配的
redisRely
指向的内存块
if (buf == NULL) return redisIOError();
r->type = REDIS_REPLY_INTEGER;
// PROBLEM: buf
不为
NULL
时
,
只是把
buf
的内容转化为
interger,
没有释放
buf
指向的内存块
r->integer = strtoll(buf,NULL,10);
// FIX:
需要在这里手动调用
sdsfree(buf)
return r;
}
3.2
同步机制
如
2.5节所分析,
redis实现的同步机制相对简单,缺少同步机制常见的
check point和校验机制。
在运行时,如果
master -> slave同步请求转发被丢弃
, slave将无法恢复该请求的相关信息,直到
slave重启时从
master全量加载数据时才能修复。因此,建议使用
redis尽量利用其
key/value和
value支持多种类型的特性,存储一些相对不重要的数据。
分享到:
相关推荐
阿里云Redis技术架构简介及后续规划 移动安全 漏洞挖掘 数据分析 威胁情报 移动安全
├<1.redis的简介和安装> ├<2.redis系统命令简介> ├<3.redis系统命令源代码剖析> ├命令介绍> ├命令中二进制的妙用> ├命令的源代码剖析> ├的命令介绍> ├命令的源代码剖析> ├的命令介绍> ├命令的源代码剖析> ...
3,结合工作实践及分析应用,培养解决实际问题的能力。 4,企业级方案设计,完全匹配工作场景。 适用人群 1、对大数据感兴趣的在校生及应届毕业生。 2、对目前职业有进一步提升要求,希望从事大数据行业高薪...
14_统计类型分析 15_bitmap日活统计 16_uvpvdau简介 17_去重复统计 18_hyper的基础命令 19_天猫网站首页亿级UV的Redis统计方案 20_GEO简介 21_GEO的命令 22_美团地图位置附近的酒店推送 23_布隆过滤器BloomFilter...
2.2 IDEA简介 8 2.3 MYSQL数据库 9 2.4 redis缓存数据库 9 2.5 RabbitMQ消息队列 10 3 需求分析与设计 10 3.1 可行性分析 10 3.1.1技术可行性 10 3.1.2 经济可行性 11 3.1.3操作可行性 11 3.2 系统功能分析 11 3.3 ...
3-4 Go客户端:redigo简介.mp4 3-3 Python客户端:redis-py.mp4 3-2 Java客户端:Jedis.mp4 3-1 课程目录.mp4 2-9 list(2).mp4 2-8 list(1).mp4 2-7 hash (2).mp4 2-6 hash (1).mp4 2-5 字符串.mp4 2-4 单...
一、压缩链表ziplist数据结构简介 首先从整体上看下ziplist的结构,如下图: 压缩链表ziplist结构图 可以看出字段很多,字节大小也不同,但这也就是压缩链表的精髓所在了,我们依次总结一下。 字段 含
前言 Redis服务是一种C/S模型,提供请求-响应式协议的TCP服务,所以当客户端请求发出,服务端处理并返回结果到客户端,...Pipeline简介 Redis客户端执行一条命令: 发送命令 命令排队 执行命令 返回结果 其中发
本repo是对redis简单使用和源码分析文档的集合,主要先从redis的简单使用入手,介绍redis的五种数据结构及其常用的命令。在了解了基本使用之后,再从源码角度入手,分析这些命令的底层实现。 ###基本使用 基本使用...
本项目是以学习的目的来一步一步实现一个最简单的基于Redis实现的分布式锁 简介 在分布式环境中,需要一种跨JVM的互斥机制来控制共享资源的访问 例如,为避免用户操作重复导致交易执行多次,使用分布式锁可以将某...
本次课程以SpringData为中心,重点讲解了其JPA组件,扩展讲解了redis,mongDB,ES组件,并且对部分组件做了必要的源码分析。而且在课程的最后部分加入了一个综合案例,可以将前面章节所学知识点应用到一个项目中,帮助...
2.4.5 实验分析 2.4.6 相关工作 2.4.7 未来的工作 2.4.8 总结 2.5 Codis 集群部署实战 2.5.1 集群概要 2.5.2 系统架构 2.5.3 角色分配 2.5.4 部署安装 2.5.5 服务启动及初始化集群 2.5.6 codis-server 的HA 2.5.7 ...
.NET内存性能分析指南 DbContext 生存期、配置和初始化 ASP.NET Core 依赖注入 Entity Framework Core 简介 管道模型及中间件使用解读 MySQL 基础 高级知识点 优化问题 必修:事务 阿里二面:怎么解决 MySQL 死锁...
NOSQL简介及MongoDB支持的数据类型分析 MongoDB可视化客户端及JavaApi实践 手写基于MongoDB的ORM框架 MongoDB企业级集解决方案 MongoDB聚合、索引及基本执行命令 MongoDB数据分片、转存及恢复策略 MyCat ...
3.1.4 Redis 集群简介 3.2系统工作流程 3.3数据库设计 3.4系统功能模块设计 3.4.1代理商管理页面 3.4.2共享设备管理页面 3.4.3产品及套餐管理页面 3.4.4共享订单分成提现页面 3.4.5基础配置页面 3.5本章小结 第四章...
作品简介: 豆瓣探索者这个作品是依托豆瓣这个平台制作的一个数据分析系统。本作品使用Python的BeautifulSoup库爬取了电影、图书、音乐这三个方向的数据存入MongoDB的NoSQL数据库,使用Pyecharts库得到了诸如单部...
作者简介 Ivan Idris,实验物理学硕士,学位论文侧重于应用计算机科学。毕业后,他曾经效力于多家公司,从事Java开发、数据仓库开发以及QA分析等方面的工作;目前,他的兴趣主要集中在商业智能、大数据和云计算等...
## 作品简介: 豆瓣探索者这个作品是依托豆瓣这个平台制作的一个数据分析系统。本作品使用Python的BeautifulSoup库爬取了电影、图书、音乐这三个方向的数据存入MongoDB的NoSQL数据库,使用Pyecharts库得到了诸如单...
数据挖掘 中图分类号:TP311 文献标识码:A 文章编号:1007-9416(2017)03-0104- 02 1 综述 1.1 简介 在数字化时代,需要新一代系统架构提升业务创新能力。在新一代系统架构中 ,大数据是核心要素。业务应用能否...
课程主要讲解项目环境篇:项目依赖环境的安装部署,项目数据准备篇:项目中所使用的数据介绍和准备,项目分析篇:项目中涉及的模块及指标介绍和研发,项目数据服务篇:项目中分析出来的数据提供服务,项目展示篇:...