`
sharkl
  • 浏览: 40855 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类

redis 分析 简介

阅读更多

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简介与特性 Redis是一种开源的键值(Key-Value)存储系统,属于非关系型数据库(NoSQL)的一种,它将数据存储在内存中,以提高数据访问速度。由于其高效的数据结构和丰富的...

    redis客户端连接工具 RedisDesktopManager

    **一、Redis简介** 1. **什么是Redis**:Redis是一个开源的、基于键值对的数据存储系统,支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。它以内存中的数据作为主要存储方式,同时提供了持久化功能,...

    redis桌面链接工具redis-desktop

    - **数据分析**:对于非技术人员来说,使用RDM可以更直观地理解存储在Redis中的数据结构和内容,便于进行数据分析工作。 #### 四、总结 Redis Desktop Manager是一款功能全面、易于使用的Redis管理工具,无论是对于...

    Redis数据库简介.docx

    ### Redis 数据库简介 #### 一、Redis 概述 Redis 是一款开源的、高性能的键值存储系统,采用 BSD 许可证发布。它的主要特点是数据存储在内存中,因此能够实现非常快速的数据访问速度。Redis 不仅可以作为数据库...

    Redis 6.2.6安装包

    - 实时数据分析:由于数据存储在内存中,Redis非常适合用于实时分析和处理快速变化的数据。 五、Redis运维与优化 - 监控:使用Redis内置的INFO命令或第三方工具(如RedisInsight)监控Redis的运行状态,包括内存...

    Redis数据库的最佳实践 19章节完全解读Redis Redis从入门到精通视频教程.txt

    ├&lt;1.redis的简介和安装&gt; ├&lt;2.redis系统命令简介&gt; ├&lt;3.redis系统命令源代码剖析&gt; ├命令介绍&gt; ├命令中二进制的妙用&gt; ├命令的源代码剖析&gt; ├的命令介绍&gt; ├命令的源代码剖析&gt; ├的命令介绍&gt; ├命令的源代码剖析&gt; ...

    Redis实战.pdf

    #### 一、Redis简介与特点 **Redis**(Remote Dictionary Server)是一种开源的内存数据结构存储系统,用作数据库、缓存和消息中间件。它支持多种数据结构,如字符串(strings)、哈希(hashes)、列表(lists)、...

    redislabs/home of Redis

    #### 一、Redis简介与背景 - **开源与商业服务**:Redis Labs是Redis开源项目的发源地,同时也是Redis企业版(Redis Enterprise,简称 Redise)软件和Redis作为服务(Redis-as-a-Service)的商业提供商。 - **高...

    windows redis-2.0.2 附带redis.conf和安装教程

    1. **Redis简介**:Redis(Remote Dictionary Server)是一个开源的、支持网络、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息代理。Redis提供了丰富的数据结构,如字符串、哈希表、列表、集合和有序...

    狂神说-Redis完整版笔记

    2. Redis简介 Redis是一个开源的、基于内存的数据结构存储系统,支持多种数据结构如字符串、哈希、列表、集合和有序集合。由于数据存储在内存中,Redis提供了极高的读写速度,使得它成为理想的缓存解决方案。此外,...

    项目整合redis实战

    1. **Redis简介**:Redis是一个开源(BSD许可)的,基于网络的,支持多种数据结构如字符串、哈希、列表、集合、有序集合的键值存储系统。它提供了持久化选项,可以通过RDB或AOF两种方式来保存数据到磁盘,同时支持...

    技术文档笔记Redis

    - **Spring-data-redis简介**:Spring Data Redis是Spring Data项目的一部分,它为Redis提供了一组抽象层,使得开发人员能够更容易地使用Redis。 - **Spring-data-redis配置**:通过配置文件或者Java配置类,可以...

    Redis-x64-5.0.14.1.msi

    1. **Redis 简介**:Redis 是 Remote Dictionary Server 的缩写,由 Salvatore Sanfilippo 创建,最初设计为内存数据结构存储系统,支持多种数据结构如字符串、哈希表、列表、集合和有序集合。它通过持久化机制确保...

    redis3.0 windows 版

    1. **Redis简介** - Redis是一个内存数据结构存储系统,它可以持久化数据到磁盘,并提供多种数据结构如字符串、哈希、列表、集合和有序集合。 - 由于其高效性能和丰富的数据类型,Redis常被用于高速读写场景,如...

    阿里云Redis技术架构简介及后续规划.pdf

    阿里云Redis技术架构是针对企业级应用而设计的高性能、高可用的数据存储解决方案。该架构主要分为单机、集群、容灾和多活四种模式,分别适用于不同的业务场景。 单机模式适合对协议敏感且对性能有较高要求的场景,...

    redis学习笔记Redis.md

    ##### 1.1 NoSQL 数据库简介 - **定义**: NoSQL(Not Only SQL)泛指非关系型数据库,它们通常不使用传统的表格关系来存储数据。 - **特性**: NoSQL 数据库的设计目的是解决传统关系型数据库在大数据量下的性能瓶颈...

    redis-faina-master

    1. **Redis 简介**: Redis 是一款高性能的键值存储系统,它常被用作数据库、缓存和消息中间件。它的特点是数据持久化、支持多种数据结构(如字符串、哈希、列表、集合、有序集合)以及丰富的操作命令。由于其内存...

Global site tag (gtag.js) - Google Analytics