write smart proxy step by step 3 (集群實現)

趙雷

有點長界睁,直接看結語好了

集群功能

第二篇筆記只實現 Redis 協議單機轉發(fā)前域,這次要實現完整集群功能尔店,涉及以下幾點:

1. 代碼邏輯模塊劃分: Server,集群拓撲欢伏,后端連接池入挣,Session管理

2. Pipeline 實現,對每一個請求封裝 Sequence硝拧,嚴格保證應答順序(實現有些投機径筏,后文再說)

3. 對后端返回的 ErrorResp 做解析葛假,特殊處理 MOVED 和 ASK 請求并異步更新集群拓撲

4. 性能,永遠的話題滋恬,通過 Pprof 一步一步去調

模塊劃分

Server 層:該層用來解析生成全局配置聊训,初始化其它模塊,開啟監(jiān)聽端口恢氯,接收外部訪問請求带斑。其中 Filter 用來對接收的 Redis 協議數據進行過濾,檢測是否危險禁止或不支持的命令勋拟,并粗略檢測命令參數個數勋磕,以接口形式實現。

type Proxy struct {

l net.Listener // 監(jiān)聽 Listener

filter Filter // Redis 有效協議檢測過濾器

pc *ProxyConfig // 全局配置文件

sm *SessMana // Session 管理

cluster *Cluster // 集群實現

}

type Filter interface {

Inspect(Resp) (string, error)

}

集群拓撲:隨機挑選 Redis 節(jié)點敢靡,根據 Cluster Nodes 輸出信息生成邏輯拓撲結構挂滓。默認每10min 定期 Reload 拓撲信息,每當 reloadChan 接收到數據時啸胧,強制 Reload杂彭。

e32929a56d00a28934669d8e473f68c5de84abce 10.10.200.11:6479 myself,master - 0 0 0 connected 0-5461

type Topology struct {

conf *ProxyConfig // 全局配置

rw sync.RWMutex // 讀寫鎖

slots []*Slot // Cluster Slot 邏輯拓撲結構

reloadChan chan int // Reload 消息 channel

}

拓撲最重要功能,根據給定 Key 返回對應后端 Redis 節(jié)點信息吓揪。Key 解析出 hash tag 按照 crc16 算法生成并對16384取余,Session 拿到 Node ID 后從連接池獲取連接所计。

GetNodeID

Session 管理:每個客戶端連接封裝成一個 Session柠辞,Server 層維護著 Session 管理工作,關閉超時的連接主胧,默認 30s

type SessMana struct {

l sync.Mutex // Session 鎖

pool map[string]*Session // Session Map

idle time.Duration // 超時時長

}

SessMana 實現簡單叭首,三個方法:添加,刪除以及定期檢查 Idle 連接

func (sm *SessMana) Put(remote string, s *Session) {

}

func (sm *SessMana) Del(remote string, s *Session) {

}

func (sm *SessMana) CheckIdleLoop() {

}

連接池:最開始想自已寫踪栋,發(fā)現有很多細節(jié)想不到焙格,就直接使用 Golang Redis Driver 的連接池

type pool interface {

First() Conn

Get() (Conn, error)

Put(Conn) error

Remove(Conn) error

Len() int

FreeLen() int

Close() error

}

上面是連接池 interface ,看似簡單夷都,具體代碼請看 pool.go眷唉,有幾點細節(jié)需要仔細思考:

1. 為了實現通用的連接池,調用方需要傳入自定義 Dialer 以及定義 Conn 接口方便擴展囤官。

2. 流控的問題冬阳,比如說在正常超時時間內,打開連接數不能超過一定次數党饮。這里采用 ratelimit 實現肝陪。想起以前在趕集,蔡導提過搶狗食的問題刑顺。

3. 連接池維護的連接有效性氯窍,用 LastUsed 超時饲常,還是使用 Ping 來處理是個問題。內網總是假設穩(wěn)定狼讨,所以 LastUsed 問題不大贝淤。

4. 如果使用 LastUsed 超時檢測,那么連接池內部檢測間隔熊楼,一定要短于后端 Redis Idle Timeout 超時時間霹娄。

Pipeline

對于不支持 Pipeline 的流程: client -> proxy -> redis - > proxy -> client . 所以有兩層可以支持 Pipeline,第一層從 client -> proxy鲫骗,這層很簡單犬耻,開啟 Channel 接收請求,Proxy 去阻塞式處理請求执泰,然后返回到 client 枕磁。

第二層 proxy -> redis - > proxy 不好實現,對于 Redis Cluster 集群术吝,命令分發(fā)到后端不同實例计济。由于網絡問題,Redis 服務問題排苍,MOVED跳轉造成的先發(fā)后至沦寂,結果集亂序肯定發(fā)生,并且是常態(tài)淘衙。所以簡單直觀的解決辦法传藏,對每一個請求封裝,增加64位的 Seq, 這個序號是 Session 級別的彤守。

type wrappedResp struct {

seq? int64 // Session 級別的自增64位ID

resp Resp? // Redis 協議結果

}

第二層開啟 goroutine毯侦,每當 Proxy 收到響應,都會檢查 Seq 是否與發(fā)送端的序號一致具垫。會出現三種情況:

1. Seq 與發(fā)送端序號相等:這是最理想的情況侈离,在 Session 層直接 WriteProtocol 寫到 Client

2. Seq 大于發(fā)送端序號: 說明發(fā)生了亂序,將該 Seq 結果暫緩存起來筝蚕,但是不能無限緩存卦碾,如果序號相隔過多,或是等待時間過長饰及,那么生成一個 ErrorResp 返回客戶端蔗坯。當前只判斷序號,沒有采用超時來解決燎含。

3. Seq 小于發(fā)送端序號: 接收的 Seq 小宾濒,說明已經被跳過了。直接忽略屏箍,并記日志 debug绘梦。

MOVED與ASK

后端 Client -> Proxy橘忱,檢測是否為 ErrorResp,不是走正常邏輯即可卸奉。否則進一步判斷钝诚,錯誤代碼前輟是否為 MOVED或ASK,再執(zhí)行 Redirect 邏輯執(zhí)行請求榄棵。如果為 MOVED凝颇,那么要異步刷新拓撲結構。

性能優(yōu)化

開啟 Pprof 查看性能疹鳄,參考 官方文檔yjf博客

import _ "net/http/pprof"

go func() {

log.Warning(http.ListenAndServe(":6061", nil))

}()

go tool pprof -pdf ./archer http://localhost:6061/debug/pprof/profile -output=/tmp/report.pdf

或是進入內部執(zhí)行命令查看

go tool pprof ./archer http://localhost:6061/debug/pprof/profile

Int 轉 []byte

pprof util.Itob

在Pprof 圖中看到 util.Itob 調用效率比較低拧略,這個函數將 Int 轉換成 []byte,用于 Resp.Encoding 時生成長度瘪弓,第一版實現如下:

func Itob(i int) []byte {

return []byte(strconv.Itoa(i))

}

第二版 Iu32tob

func Iu32tob(i int) []byte {

return strconv.AppendUint(nil, uint64(i), 10)

}

第三版本 Iu32tob2

func Iu32tob2(i int) []byte {

buf := make([]byte, 10) // 大量小對象的創(chuàng)建是個問題垫蛆,同樣需要對象池

idx := len(buf) - 1

for i >= 10 {

buf[idx] = byte('0' + i%10)

i = i / 10

idx--

}

buf[idx] = byte('0' + i)

return buf[idx:]

}

做 Benchmark 結果如下,將 Itob 替換成第三版本的 Iu32tob2

localhost:util dzr$ go test -v -bench=".*"

testing: warning: no tests to run

PASS

Benchmark_Itob-4? ? ? ? 10000000? ? ? ? ? ? ? 116 ns/op

Benchmark_Iu32tob-4? ? 20000000? ? ? ? ? ? ? ? 98.4 ns/op

Benchmark_Iu32tob2-4? ? 20000000? ? ? ? ? ? ? ? 80.2 ns/op

ok? ? ? github.com/dongzerun/archer/util? ? ? ? 5.101s

再次開啟 Pprof 查看 ReadProtocol 和 WriteProtocol 的 syscall 量最大腺怯,并且 Resp.Encode() 會有大量的 bytes.Buffer 對象產生袱饭,應該將做成對象池。那么 Resp 的Encode 方法要改:

Resp.Encode() []byte

變成

Resp.Encode(w *bufio.Writer) error

Pprof


壓測數據

單機本機原生單臺 Redis?

PING_INLINE: 139664.81 requests per second

PING_BULK: 144092.22 requests per second

SET: 146412.89 requests per second

GET: 145921.48 requests per second

INCR: 142166.62 requests per second

LPUSH: 144634.08 requests per second

LPOP: 141302.81 requests per second

SADD: 139567.34 requests per second

SPOP: 142714.42 requests per second

LPUSH (needed to benchmark LRANGE): 144655.00 requests per second

LRANGE_100 (first 100 elements): 65355.21 requests per second

LRANGE_300 (first 300 elements): 26616.98 requests per second

LRANGE_500 (first 450 elements): 18669.26 requests per second

LRANGE_600 (first 600 elements): 14510.21 requests per second

MSET (10 keys): 121995.86 requests per second

單機 Proxy 后端 Redis Cluster 3個 Master節(jié)點呛占,未使用對象池

PING_INLINE: 100361.30 requests per second

PING_BULK: 96918.01 requests per second

SET: 92131.93 requests per second

GET: 90612.54 requests per second

INCR: 91852.66 requests per second

LPUSH: 84645.34 requests per second

LPOP: 87092.84 requests per second

SADD: 88300.22 requests per second

SPOP: 90851.27 requests per second

LPUSH (needed to benchmark LRANGE): 88448.61 requests per second

LRANGE_100 (first 100 elements): 25277.42 requests per second

LRANGE_300 (first 300 elements): 10484.71 requests per second

LRANGE_500 (first 450 elements): 7604.97 requests per second

LRANGE_600 (first 600 elements): 5883.36 requests per second

MSET (10 keys): 17710.71 requests per second

單機 Proxy 后端 Redis Cluster 3個 Master節(jié)點虑乖,sync.Pool開啟bytes.Buffer對象池

PING_INLINE: 109829.77 requests per second

PING_BULK: 102743.25 requests per second

SET: 91290.85 requests per second

GET: 92790.20 requests per second

INCR: 93466.68 requests per second

LPUSH: 90604.34 requests per second

LPOP: 90277.16 requests per second

SADD: 85682.46 requests per second

SPOP: 91432.75 requests per second

LPUSH (needed to benchmark LRANGE): 89726.33 requests per second

LRANGE_100 (first 100 elements): 25667.35 requests per second

LRANGE_300 (first 300 elements): 10589.07 requests per second

LRANGE_500 (first 450 elements): 7683.91 requests per second

LRANGE_600 (first 600 elements): 5826.89 requests per second

MSET (10 keys): 17955.90 requests per second

相比未使用對象池是好一些。晾虑。决左。

結語

性能數據一般,不穩(wěn)定走贪,壓過幾次 Crash。再找找 Pprof 還有哪些可以優(yōu)化的惑芭,很多 SysCall Runtime 不是很懂坠狡,再鞏固下 Go 基礎。代碼格式也不夠美觀^_^

最近聽了十六歲少年遂跟,越陽的故事逃沿。十六歲花季,凋落的有些無耐幻锁。慶幸他的詞都被譜成了歌曲凯亮,最喜歡趙雷填曲的《讓我偷偷看你》,期待明天他會唱這首歌...

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末哄尔,一起剝皮案震驚了整個濱河市假消,隨后出現的幾起案子,更是在濱河造成了極大的恐慌岭接,老刑警劉巖富拗,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臼予,死亡現場離奇詭異,居然都是意外死亡啃沪,警方通過查閱死者的電腦和手機粘拾,發(fā)現死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來创千,“玉大人缰雇,你說我怎么就攤上這事∽仿浚” “怎么了械哟?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長氯檐。 經常有香客問我戒良,道長,這世上最難降的妖魔是什么冠摄? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任糯崎,我火速辦了婚禮,結果婚禮上河泳,老公的妹妹穿的比我還像新娘沃呢。我一直安慰自己,他們只是感情好拆挥,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布薄霜。 她就那樣靜靜地躺著,像睡著了一般纸兔。 火紅的嫁衣襯著肌膚如雪惰瓜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天汉矿,我揣著相機與錄音崎坊,去河邊找鬼。 笑死洲拇,一個胖子當著我的面吹牛奈揍,可吹牛的內容都是我干的。 我是一名探鬼主播赋续,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼男翰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了纽乱?” 一聲冷哼從身側響起蛾绎,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秘通,有當地人在樹林里發(fā)現了一具尸體为严,經...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年肺稀,在試婚紗的時候發(fā)現自己被綠了第股。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡话原,死狀恐怖夕吻,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情繁仁,我是刑警寧澤涉馅,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站黄虱,受9級特大地震影響稚矿,放射性物質發(fā)生泄漏。R本人自食惡果不足惜捻浦,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一晤揣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朱灿,春花似錦昧识、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侣灶,卻和暖如春甸祭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留码俩,地道東北人。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像处嫌,于是被迫代替她去往敵國和親栅贴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內容