redis client原理分析


  • 1:連接池
  • 2:發送命令
  • 3:解析結果

1:連接池
連接池結構體如下:
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 //雙向鏈表指針
}

1:空閑連接池實現
空閑連接池存在一個雙向鏈表中,一個連接用完后回收,就會從表頭插入這個鏈表,當需要一個連接時也是從鏈表的表頭取,從表頭插入的時候會寫入當前時間,所以鏈表是一個按時間倒序的鏈表,判斷一個連接有沒有空閑超時,就從鏈表表尾開始判斷,如果空閑超時,就從表尾移除並關閉連接。從表頭插入一個元素后,如果空閑數量超過閾值,會從表尾移除一個元素,保證空閑的連接數不超過指定的值,防止空閑的連接過多浪費系統資源
 
獲取可用連接過程
對應方法:Pool.Get
1:刪除超時空閑連接。從鏈表的表尾開始往表頭判斷,如果到達空閑超時時間,從鏈表中移除並釋放連接
2:從空閑鏈表中獲取可用連接。從鏈表表頭往表尾查找可用的連接,如果連接能ping通,將當前連接從鏈表中移除,返回當前連接
3:建立一個新的連接。如果空閑連接為空,或者空閑鏈表中的所有連接都不可用,則從新建立一個新的連接並返回
在獲取連接的時候刪除超時空閑連接,這是一種惰性刪除的方式
 
以上實現方式同時解決了斷開重連的問題。
如在某一時刻redis server重啟了,那么空閑鏈表中的連接都會變得不可用,由於在獲取可用連接前會先ping一下,但是所有連接都ping不通,最后只能重新建立,而空閑連接會在空閑時間超時后自動釋放,於是很好的解決了斷開重連的問題,同時也做了一些犧牲,不ping一下怎么知道連接是否可用,每次獲取可用連接的時候都ping了一下,但是大部分時候連接都是可用的。
 
回收可用連接:
對應方法:pooledConnection.Close  -> Pool.put
1:終止相關操作,如果是事務/監聽/訂閱,停止相關操作
2:將連接放入空閑鏈表。將連接存入鏈表表頭,如果空閑連接數量超過閾值,就將表尾元素移除並關閉連接
 
 
2:發送命令&接收回復並解析
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,前面多了數量

接收命令其實就是解析以上格式

 

 

 

 

 


免責聲明!

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



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