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技术分析及运用 #### 一、Redis简介与特性 Redis是一种开源的键值(Key-Value)存储系统,属于非关系型数据库(NoSQL)的一种,它将数据存储在内存中,以提高数据访问速度。由于其高效的数据结构和丰富的...
**一、Redis简介** 1. **什么是Redis**:Redis是一个开源的、基于键值对的数据存储系统,支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。它以内存中的数据作为主要存储方式,同时提供了持久化功能,...
- **数据分析**:对于非技术人员来说,使用RDM可以更直观地理解存储在Redis中的数据结构和内容,便于进行数据分析工作。 #### 四、总结 Redis Desktop Manager是一款功能全面、易于使用的Redis管理工具,无论是对于...
### Redis 数据库简介 #### 一、Redis 概述 Redis 是一款开源的、高性能的键值存储系统,采用 BSD 许可证发布。它的主要特点是数据存储在内存中,因此能够实现非常快速的数据访问速度。Redis 不仅可以作为数据库...
- 实时数据分析:由于数据存储在内存中,Redis非常适合用于实时分析和处理快速变化的数据。 五、Redis运维与优化 - 监控:使用Redis内置的INFO命令或第三方工具(如RedisInsight)监控Redis的运行状态,包括内存...
├<1.redis的简介和安装> ├<2.redis系统命令简介> ├<3.redis系统命令源代码剖析> ├命令介绍> ├命令中二进制的妙用> ├命令的源代码剖析> ├的命令介绍> ├命令的源代码剖析> ├的命令介绍> ├命令的源代码剖析> ...
#### 一、Redis简介与特点 **Redis**(Remote Dictionary Server)是一种开源的内存数据结构存储系统,用作数据库、缓存和消息中间件。它支持多种数据结构,如字符串(strings)、哈希(hashes)、列表(lists)、...
#### 一、Redis简介与背景 - **开源与商业服务**:Redis Labs是Redis开源项目的发源地,同时也是Redis企业版(Redis Enterprise,简称 Redise)软件和Redis作为服务(Redis-as-a-Service)的商业提供商。 - **高...
1. **Redis简介**:Redis(Remote Dictionary Server)是一个开源的、支持网络、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息代理。Redis提供了丰富的数据结构,如字符串、哈希表、列表、集合和有序...
2. Redis简介 Redis是一个开源的、基于内存的数据结构存储系统,支持多种数据结构如字符串、哈希、列表、集合和有序集合。由于数据存储在内存中,Redis提供了极高的读写速度,使得它成为理想的缓存解决方案。此外,...
1. **Redis简介**:Redis是一个开源(BSD许可)的,基于网络的,支持多种数据结构如字符串、哈希、列表、集合、有序集合的键值存储系统。它提供了持久化选项,可以通过RDB或AOF两种方式来保存数据到磁盘,同时支持...
- **Spring-data-redis简介**:Spring Data Redis是Spring Data项目的一部分,它为Redis提供了一组抽象层,使得开发人员能够更容易地使用Redis。 - **Spring-data-redis配置**:通过配置文件或者Java配置类,可以...
1. **Redis 简介**:Redis 是 Remote Dictionary Server 的缩写,由 Salvatore Sanfilippo 创建,最初设计为内存数据结构存储系统,支持多种数据结构如字符串、哈希表、列表、集合和有序集合。它通过持久化机制确保...
1. **Redis简介** - Redis是一个内存数据结构存储系统,它可以持久化数据到磁盘,并提供多种数据结构如字符串、哈希、列表、集合和有序集合。 - 由于其高效性能和丰富的数据类型,Redis常被用于高速读写场景,如...
阿里云Redis技术架构是针对企业级应用而设计的高性能、高可用的数据存储解决方案。该架构主要分为单机、集群、容灾和多活四种模式,分别适用于不同的业务场景。 单机模式适合对协议敏感且对性能有较高要求的场景,...
##### 1.1 NoSQL 数据库简介 - **定义**: NoSQL(Not Only SQL)泛指非关系型数据库,它们通常不使用传统的表格关系来存储数据。 - **特性**: NoSQL 数据库的设计目的是解决传统关系型数据库在大数据量下的性能瓶颈...
1. **Redis 简介**: Redis 是一款高性能的键值存储系统,它常被用作数据库、缓存和消息中间件。它的特点是数据持久化、支持多种数据结构(如字符串、哈希、列表、集合、有序集合)以及丰富的操作命令。由于其内存...