redis client原理分析
阿新 • • 發佈:2020-10-28
程式碼庫地址:https://github.com/garyburd/redigo
1:空閒連線池實現
空閒連線池存在一個雙向連結串列中,一個連線用完後回收,就會從表頭插入這個連結串列,當需要一個連線時也是從連結串列的表頭取,從表頭插入的時候會寫入當前時間,所以連結串列是一個按時間倒序的連結串列,判斷一個連線有沒有空閒超時,就從連結串列表尾開始判斷,如果空閒超時,就從表尾移除並關閉連線。從表頭插入一個元素後,如果空閒數量超過閾值,會從表尾移除一個元素,保證空閒的連線數不超過指定的值,防止空閒的連線過多浪費系統資源
獲取可用連線過程
對應方法:Pool.Get
1:刪除超時空閒連線。從連結串列的表尾開始往表頭判斷,如果到達空閒超時時間,從連結串列中移除並釋放連線
2:從空閒連結串列中獲取可用連線。從連結串列表頭往表尾查詢可用的連線,如果連線能ping通,將當前連線從連結串列中移除,返回當前連線
3:建立一個新的連線。如果空閒連線為空,或者空閒連結串列中的所有連線都不可用,則從新建立一個新的連線並返回
在獲取連線的時候刪除超時空閒連線,這是一種惰性刪除的方式
以上實現方式同時解決了斷開重連的問題。
如在某一時刻redis server重啟了,那麼空閒連結串列中的連線都會變得不可用,由於在獲取可用連線前會先ping一下,但是所有連線都ping不通,最後只能重新建立,而空閒連線會在空閒時間超時後自動釋放,於是很好的解決了斷開重連的問題,同時也做了一些犧牲,不ping一下怎麼知道連線是否可用,每次獲取可用連線的時候都ping了一下,但是大部分時候連線都是可用的。
回收可用連線:
對應方法:pooledConnection.Close -> Pool.put
1:終止相關操作,如果是事務/監聽/訂閱,停止相關操作
2:將連線放入空閒連結串列。將連線存入連結串列表頭,如果空閒連線數量超過閾值,就將表尾元素移除並關閉連線
2:傳送命令&接收回復並解析
- 1:連線池
- 2:傳送命令
- 3:解析結果
type Pool struct { // Dial is an application supplied function for creating and configuring a // connection. // // The connection returned from Dial must not be in a special state // (subscribed to pubsub channel, transaction started, ...). Dial func() (Conn, error) //生成網路連線物件 // TestOnBorrow is an optional application supplied function for checking // the health of an idle connection before the connection is used again by // the application. Argument t is the time that the connection was returned // to the pool. If the function returns an error, then the connection is // closed. TestOnBorrow func(c Conn, t time.Time) error //測試連線是否通暢 // Maximum number of idle connections in the pool. MaxIdle int //最大空閒連線數 // Maximum number of connections allocated by the pool at a given time. // When zero, there is no limit on the number of connections in the pool. MaxActive int //最大活動(正在執行任務)連線數 // Close connections after remaining idle for this duration. If the value // is zero, then idle connections are not closed. Applications should set // the timeout to a value less than the server's timeout. IdleTimeout time.Duration //空閒連線超時時間,超時會釋放 // If Wait is true and the pool is at the MaxActive limit, then Get() waits // for a connection to be returned to the pool before returning. Wait bool //當到達最大活動連線時,是否阻塞 chInitialized uint32 // set to 1 when field ch is initialized 初始化標記 mu sync.Mutex // mu protects the following fields 鎖 closed bool // set to true when the pool is closed. 連線池關閉標記 active int // the number of open connections in the pool 連線總數 ch chan struct{} // limits open connections when p.Wait is true 用於實現阻塞邏輯 idle idleList // idle connections 雙向連結串列,存放空閒連線 } type idleList struct { //空閒連線連結串列 count int //空閒連線數 front, back *idleConn //空閒連線資訊 } type idleConn struct { //空閒連線資訊 c Conn //連線介面 t time.Time //加入空閒佇列的時間,用於判斷空閒超時 next, prev *idleConn //雙向連結串列指標 }
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { return c.DoWithTimeout(c.readTimeout, cmd, args...) } func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { c.mu.Lock() pending := c.pending c.pending = 0 c.mu.Unlock() if cmd == "" && pending == 0 { return nil, nil } //設定寫超時時間 if c.writeTimeout != 0 { c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) } //傳送命令內容 if cmd != "" { if err := c.writeCommand(cmd, args); err != nil { return nil, c.fatal(err) } } if err := c.bw.Flush(); err != nil { return nil, c.fatal(err) } var deadline time.Time if readTimeout != 0 { deadline = time.Now().Add(readTimeout) } //設定讀超時時間 c.conn.SetReadDeadline(deadline) if cmd == "” { //獲取server回覆資訊並解析 reply := make([]interface{}, pending) for i := range reply { r, e := c.readReply() if e != nil { return nil, c.fatal(e) } reply[i] = r } return reply, nil } var err error var reply interface{} for i := 0; i <= pending; i++ { var e error if reply, e = c.readReply(); e != nil { return nil, c.fatal(e) } if e, ok := reply.(Error); ok && err == nil { err = e } } return reply, err }
redis請求協議格式
set命令訊息格式: *3\r\n$3\r\nSET\r\n$4\r\nhlxs\r\n$28\r\nhttps://www.cnblogs.com/hlxs\r\n 註釋如下: *3 //引數個數是*開頭,3個引數 $3 //引數長度是$開頭,命令長度 SET //命令名稱SET $5 //引數長度是$開頭,key長度 mykey //key的內容 $28 //引數長度是$開頭,value長度 https://www.cnblogs.com/hlxs //value內容引數個數是*開頭,引數長度是$開頭,每個引數通過\r\n隔開 redis協議格式特點:1易於實現,2可以高效地被計算機分析,3可以很容易地被人類讀懂 傳送命令其實就是構造請求協議格式,以二進位制的方式傳送出去 redis回覆協議格式
* 狀態回覆(status reply)的第一個位元組是 “+”,如:+ok\r\n * 錯誤回覆(error reply)的第一個位元組是 “-“,如:-ERR unknown command xxx\r\n 在 "-" 之後,直到遇到第一個空格或新行為止,這中間的內容表示所返回錯誤的型別 * 整數回覆(integer reply)的第一個位元組是 “:”,如::1000\r\n * 批量回復(bulk reply)的第一個位元組是 “$”,如:$6\r\nfoobar\r\n,也是長度加內容的風格 * 多條批量回復(multi bulk reply)的第一個位元組是 “*”,如:*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$6\r\nfoobar\r\n,前面多了數量
接收命令其實就是解析以上格式