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


Redis io抽象層

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

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聲明了三個對應的初始化函數:

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

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

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

2. buffer

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

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

 /* 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中下一個應該發送的數據的位置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM