redis源碼分析(二)-rio(讀寫抽象層)

Redis io抽象層

  Redis中涉及到多種io,如socket與file,爲了統一對它們的操做,redis設計了一個抽象層,即rio,使用rio能夠實現將數據寫入到不一樣的底層io,可是接口相同。rio的實如今rio.h與rio.c源文件中,支持內存、文件、socket集合三類底層io。redis

1. struct rio

  struct rio中聲明瞭統一的io操做接口,而且包含一個底層io對象的union結構。使用不一樣的底層io初始化rio實例後,調用rio的抽象接口即會調用對應底層io的實現。以面向對象的思想便是,rio爲抽象類,它擁有三個子類:buffer、file及fdset,這三個子類實現了抽象類聲明的接口。使用者可使用它們的父類rio進行編程,實現多態性。編程

如下是struct rio中抽象接口的聲明(此處省略了一些其它的成員):緩存

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. */
    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 *);
    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);

  ...
/* Backend-specific vars. */
  ...
};

  每個底層對象都須要實現它須要支持的接口,實例化rio時,rio結構中的函數指針將指向底io的實現。Redis是C語言實現,所以針對 三個底層io聲明瞭三個對應的初始化函數:app

void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s);
void rioInitWithFdset(rio *r, int *fds, int numfds);

這三個函數將初始化rio實例中的函數指針爲它對應的抽象接口實現,並初始化union結構指向正確的底層io對象。socket

注意:rio接口中雖然聲明瞭write操做與read操做,可是redis中僅將它們用於單向操做,即一個rio實例或者使用write操做,或者使用read操做,同一個rio實例不能既讀又寫。函數

2. buffer

  以buffer爲底層io的rio實例,write操做將參數buf中的數據copy到一個sds中(redis的字符串實現)。反之,它的read操做將會從一個sds中讀取數據到參數buf指向的地址中。this

  抽象接口不支持seek操做,所以寫操做僅能append,而讀操做也只能從當前位置讀數據。buffer對象的結構聲明以下:spa

 /* In-memory buffer target. */
        struct {
            sds ptr;
            off_t pos;
        } buffer;

這裏的pos記錄了讀寫操做在buffer中的當前位置。設計

3. file

  以file爲底層io的rio實例,write操做將參數buf中的數據寫入到文件中,而read操做則將file中的數據讀到參數buf指向的內存地址中。file對象的抽象接口實現只須要簡單的調用c語言的庫函數便可。指針

一樣因爲抽象接口未聲明seek操做,它的具體實現也沒有實現seek操做。file對象的結構聲明以下:

        /* Stdio file pointer target. */
        struct {
            FILE *fp;
            off_t buffered; /* Bytes written since last fsync. */
            off_t autosync; /* fsync after 'autosync' bytes written. */
        } file;

這裏的buffered記錄了寫操做的累計數據量,而autosync爲設置一個同步值,當buffered值超過autosync值後,會執行sync操做使數據同步到磁盤上,sync操做後將buffered值清零。

4. fdset

  以fdset爲底層io的rio實例能夠同時將數據向多個目標寫,在redis中主要用做master向它的多個slave發送同步數據,即便用fdset的write操做能夠將一份數據向多個socket發送。對fdset的抽象大大地簡化了redis的master向它的多個slave發送同步數據的 io操做

fdset不支持read操做。此外,它使用了相似buffer的一個sds實例做爲緩存,數據首先被寫入到該緩存中,當緩存中的數據超過必定數量,或者調用了flush操做,再將緩存中的數據發送到全部的socket中。fdset的結構聲明以下:

        /* Multiple FDs target (used to write to N sockets). */
        struct {
            int *fds;       /* File descriptors. */
            int *state;     /* Error state of each fd. 0 (if ok) or errno. */
            int numfds;
            off_t pos;
            sds buf;
        } fdset;

fds即全部的目標socket的文件描述符集合,state記錄了這些文件描述符的狀態(是否發生寫錯誤),numfds記錄了集合的大小,buf爲緩存,pos表明buf中下一個應該發送的數據的位置。

相關文章
相關標籤/搜索