【Redis源码剖析】 - Redis IO操作之rio

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51433696

Reids内部封装了一个I/O层,称之为rio。今天我们就来简单介绍一下rio模块的具体实现。

本文主要涉及rio.h和rio.c两个文件。


1、rio结构体

关于文件读写操作和buffer的操作主要基于rio对象进行操作,我们先来看看rio结构体的定义,如下:

/* 系统IO操作的封装 */
struct _rio {
    /* Backend functions.
     * Since this functions do not tolerate short writes or reads the return
     * value is simplified to: zero on error, non zero on complete success. */
    /*  后端方法:函数的返回值为0表示发生错误,返回值为非0表示操作成功。 */

    // 数据流读操作
    size_t (*read)(struct _rio *, void *buf, size_t len);
    // 数据流写操作
    size_t (*write)(struct _rio *, const void *buf, size_t len);
    // 读或写操作的当前偏移量
    off_t (*tell)(struct _rio *);
    // flush操作
    int (*flush)(struct _rio *);
    /* The update_cksum method if not NULL is used to compute the checksum of
     * all the data that was read or written so far. The method should be
     * designed so that can be called with the current checksum, and the buf
     * and len fields pointing to the new block of data to add to the checksum
     * computation. */
    // 更新校验和
    void (*update_cksum)(struct _rio *, const void *buf, size_t len);

    /* The current checksum */
    // 当前校验和
    uint64_t cksum;

    /* number of bytes read or written */
    // 已读或已写的字节数
    size_t processed_bytes;

    /* maximum single read or write chunk size */
    // 每次读或写操作的最大字节数
    size_t max_processing_chunk;

    /* Backend-specific vars. */
    // io变量
    union {
        /* In-memory buffer target. */
        // 内存缓冲区buffer结构体
        struct {
            // buffer中的内容,实际就是char数组
            sds ptr;
            // 偏移量
            off_t pos;
        } buffer;
        /* Stdio file pointer target. */
        // 文件结构体
        struct {
            // 打开的文件句柄
            FILE *fp;
            // 最后一个fsync后写入的字节数
            off_t buffered; /* Bytes written since last fsync. */
            // 多少字节进行一次fsync操作
            off_t autosync; /* fsync after ‘autosync‘ bytes written. */
        } file;
        /* Multiple FDs target (used to write to N sockets). */
        // 封装了多个文件描述符结构体(用于写多个socket)
        struct {
            // 文件描述符数组
            int *fds;       /* File descriptors. */
            // 状态位,与fds对应
            int *state;     /* Error state of each fd. 0 (if ok) or errno. */
            // 文件描述符的个数
            int numfds;
            // 偏移量
            off_t pos;
            // 缓冲区
            sds buf;
        } fdset;
    } io;
};

我们可以看到rio结构体主要包含以下三方面内容:

  1. 读写操作、获取偏移量操作等相关的回调函数。rio可以处理buffer、file、socket三种不同类型的I/O对象,不同的rio对象底层使用相应的系统调用完成read、write、tell、flush操作。比如,对于file rio对象,底层通过fwrite函数完成写操作,通过fread函数完成读操作(具体见源码)。
  2. 校验和操作。rio使用了RCR64算法计算校验和,具体实现可以参看crc64.h和crc64.c文件。
  3. IO变量。_rio中的io成员是一个联合体,针对不同的I/O情况进行不同的处理:当执行内存buffer的I/O操作时,使用rio.buffer结构体;当执行文件I/O操作时,使用rio.file结构体;当执行socket的I/O操作时,使用rio.fdset结构体。

介绍完rio结构体后,我们来看看buffer rio对象的实现(file rio对象和socket rio对象的现实大家可以参考注释版源码,这里就不一一讲解)。

buffer的写操作实际上就是将数据存入r->io.buffer中,由rioBufferWrite函数实现:

/* 将buf中指定长度len的内容追加到rio对象的缓冲区中,操作成功返回1,否则返回0。*/
static size_t rioBufferWrite(rio *r, const void *buf, size_t len) {
    // 调用sdscatlen实现append操作
    r->io.buffer.ptr = sdscatlen(r->io.buffer.ptr,(char*)buf,len);
    // 更新长度信息
    r->io.buffer.pos += len;
    return 1;
}

buffer的写操作实际上就是将数据从r->io.buffer中读出,由rioBufferRead函数实现:

/* 从rio对象的缓冲区中读取长度为len的内容到buf中,操作成功返回1,否则返回0。*/
static size_t rioBufferRead(rio *r, void *buf, size_t len) {
    // 如果rio对象的缓冲区中内容的长度小于len,读取失败
    if (sdslen(r->io.buffer.ptr)-r->io.buffer.pos < len)
        return 0; /* not enough buffer to return len bytes. */
    // 将缓冲区中的内容复制到buf
    memcpy(buf,r->io.buffer.ptr+r->io.buffer.pos,len);
    // 更新偏移量
    r->io.buffer.pos += len;
    return 1;
}

buffer的tell操作和flush操作如下:

/*  返回rio对象缓冲区的偏移量 */
static off_t rioBufferTell(rio *r) {
    return r->io.buffer.pos;
}

/*  该函数什么事也没有做,直接返回1。*/
static int rioBufferFlush(rio *r) {
    // REDIS_NOTUSED定义在redis.h文件中:#define REDIS_NOTUSED(V) ((void) V)
    REDIS_NOTUSED(r);
    return 1; /* Nothing to do, our write just appends to the buffer. */
}

根据上面定义的函数,我们就可以创建buffer rio对象:

/* 根据上面的方法定义的流为内存时使用的buffer rio对象 */
static const rio rioBufferIO = {
    rioBufferRead,
    rioBufferWrite,
    rioBufferTell,
    rioBufferFlush,
    NULL,           /* update_checksum */
    0,              /* current checksum */
    0,              /* bytes read or written */
    0,              /* read/write chunk size */
    { { NULL, 0 } } /* union for io-specific vars */
};

rioInitWithBuffer函数负责初始化buffer rio对象,创建io.buffer缓冲区。

/* 初始化buffer io对象 */
void rioInitWithBuffer(rio *r, sds s) {
    *r = rioBufferIO;
    r->io.buffer.ptr = s;
    r->io.buffer.pos = 0;
}

2、统一的读写操作接口

rio模块封装了Redis对buffer读写操作、file读写操作、socket读写操作,同时也定义了统一的read、write、tell、flus操作接口。

执行rio的写操作:

/*  将buf数组中的长度为len的字符写入rio对象中,写入成功返回1,写入失败返回0。*/
static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
    while (len) {
        // 判断当前要求写入的字节数是否操作了max_processing_chunk规定的最大长度
        size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
        // 写入新的数据时,更新校验和字段
        if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write);
        // 调用write方法执行写入操作
        if (r->write(r,buf,bytes_to_write) == 0)
            return 0;
        // 更新buf下次写入的位置
        buf = (char*)buf + bytes_to_write;
        len -= bytes_to_write;
        // 更新已写入的字节数
        r->processed_bytes += bytes_to_write;
    }
    return 1;
}

执行rio的读操作:

/* 从rio对象中读出长度为len字节的数据并保存到buf数组中。读取成功返回1,读取失败返回0。*/
static inline size_t rioRead(rio *r, void *buf, size_t len) {
    while (len) {
        // 判断当前要求读出的字节数是否操作了max_processing_chunk规定的最大长度
        size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
        // 调用read方法读出数据到buf中
        if (r->read(r,buf,bytes_to_read) == 0)
            return 0;
        // 更新buf下次写入的位置
        if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read);
        buf = (char*)buf + bytes_to_read;
        len -= bytes_to_read;
        // 更新已读出的字节数
        r->processed_bytes += bytes_to_read;
    }
    return 1;
}

执行rio的tell操作:

/* 返回当前偏移量 */
static inline off_t rioTell(rio *r) {
    return r->tell(r);
}

执行rio的flush操作:

/* flush操作 */
static inline int rioFlush(rio *r) {
    return r->flush(r);
}

从上面四个函数的实现中可以看出它们都是调用rio对象内部的回调函数来执行执行相应的write、read、tell、flush操作的,这些回调函数具体完成的操作又跟rio对象是buffer rio对象还是file rio对象,亦或是socket rio对象有关。

3、其它

Redis中的rio模块还封装了一些辅助生成AOF协议的函数,这些函数主要包括:

// 以"*<count>\r\n"的形式将count以字符串的格式写入rio对象中,返回写入的字节数。
size_t rioWriteBulkCount(rio *r, char prefix, int count);
// 以"$<count>\r\n<payload>\r\n"格式往rio对象中写入二进制安全字符串。
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
// 以"$<count>\r\n<payload>\r\n"的格式往rio对象中写入long long类型的值。
size_t rioWriteBulkLongLong(rio *r, long long l);
// 以"$<count>\r\n<payload>\r\n"的格式往rio对象中写入double类型的值。
size_t rioWriteBulkDouble(rio *r, double d);


Redis中的rio模块对系统I/O函数进行封装。在Redis内部实现中,RDB、AOF等都使用该模块的功能,所以这里简要介绍了rio模块的实现原理。

源码见:

  1. rio.h:https://github.com/xiejingfa/the-annotated-redis-3.0/blob/master/rio.h
  2. rio.c:https://github.com/xiejingfa/the-annotated-redis-3.0/blob/master/rio.c
时间: 2024-10-28 08:59:05

【Redis源码剖析】 - Redis IO操作之rio的相关文章

【Redis源码剖析】 - Redis内置数据结构值压缩字典zipmap

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51111230 今天为大家带来Redis中zipmap数据结构的分析,该结构定义在zipmap.h和zipmap.c文件中.我把zipmap称作"压缩字典"(不知道这样称呼正不正确)是因为zipmap利用字符串实现了一个简单的hash_table结构,又通过固定的字节表示节省空间.zipmap和前面介绍的ziplist结构十分类似,我们可以对比地进行学习: Redis中

【Redis源码剖析】 - Redis之数据库redisDb

原创作品,转载请标明:http://blog.csdn.net/xiejingfa/article/details/51321282 今天,我们来讨论两点内容:一是Redis是如何存储类型对象的,二是Redis如何实现键的过期操作. 本文介绍的内容主要涉及db.c和redis.h两个文件. 1.redisDb介绍 Redis中存在"数据库"的概念,该结构由redis.h中的redisDb定义.我们知道Redis提供string.list.set.zset.hash五种数据类型的存储,在

Redis源码剖析(八)--对象系统

对象的类型与编码 在 Redis 中新创建一个键值对时, 我们至少会创建两个对象, 一个对象用作键值对的键(键对象), 另一个对象用作键值对的值(值对象).Redis 中的每个对象都由一个 redisObject 结构表示: typedef struct redisObject { // 类型 unsigned type:4; // 编码 unsigned encoding:4; // 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; /* lru time (

Redis源码剖析和注释(十八)--- Redis AOF持久化机制

Redis AOF持久化机制 1. AOF持久化介绍 Redis中支持RDB和AOF这两种持久化机制,目的都是避免因进程退出,造成的数据丢失问题. RDB持久化:把当前进程数据生成时间点快照(point-in-time snapshot)保存到硬盘的过程,避免数据意外丢失. AOF持久化:以独立日志的方式记录每次写命令,重启时在重新执行AOF文件中的命令达到恢复数据的目的. Redis RDB持久化机制源码剖析和注释 AOF的使用:在redis.conf配置文件中,将appendonly设置为y

Redis源码剖析和注释(八)--- 对象系统(redisObject)

Redis 对象系统 1. 介绍 redis中基于双端链表.简单动态字符串(sds).字典.跳跃表.整数集合.压缩列表.快速列表等等数据结构实现了一个对象系统,并且实现了5种不同的对象,每种对象都使用了至少一种前面的数据结构,优化对象在不同场合下的使用效率. 双端链表源码剖析和注释 简单动态字符串(SDS)源码剖析和注释 字典结构源码剖析和注释 跳跃表源码剖析和注释 整数集合源码剖析和注释 压缩列表源码剖析和注释 快速列表源码剖析和注释 2. 对象的系统的实现 redis 3.2版本.所有注释在

【Redis源码剖析】 - Redis持久化之RDB

原创作品,转载请标明:http://blog.csdn.net/xiejingfa/article/details/51553370 Redis是一个高效的内存数据库,所有的数据都存放在内存中.我们知道,内存中的信息会随着进程的退出或机器的宕机而消失.为此,Redis提供了两种持久化机制:RDB和AOF.这两种持久化方式的原理实际上就是把内存中所有数据的快照保存到磁盘文件上,以避免数据丢失. 今天我们主要来介绍一下RDB持久化机制RDB的实现原理,涉及的文件为rdb.h和rdb.c. RDB的主

【Redis源码剖析】 - Redis数据类型之有序集合zset

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51231967 这周事情比较多,原本计划每周写两篇文章的任务看来是完不成了.今天为大家带来有序集合zset的源码分析. Redis中的zset主要支持以下命令: zadd.zincrby zrem.zremrangebyrank.zremrangebyscore.zremrangebyrank zrange.zrevrange.zrangebyscore.zrevrangebys

【Redis源码剖析】 - Redis数据类型之列表List

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51166709 今天为大家带来Redis五大数据类型之一 – List的源码分析. Redis中的List类型是一种双向链表结构,主要支持以下几种命令: lpush.rpush.lpushx.rpushx lpop.rpop.lrange.ltrim.lrem.rpoplpush linsert.llen.lindex.lset blpop.brpop.brpoplpush Li

【Redis源码剖析】 - Redis内置数据结构之字典dict

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51018337 今天我们来讲讲Redis中的哈希表.哈希表在C++中对应的是map数据结构,但在Redis中称作dict(字典).Redis只是用了几个简单的结构体和几种常见的哈希算法就实现了一个简单的类似高级语言中的map结构.下面我们来具体分析一下dict的实现. 在学习数据结构的时候,我们接触过一种称作"散列表"的结构,可以根据关键字而直接访问记录.说的具体一点就