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