阿里雲某客戶發現本身使用讀寫分離實例,master的cpu特別高,而讀寫分離中承擔讀流量的slave節點卻相對空閒。用戶CPU打滿後,訪問到主節點的的線上服務受到了較大影響。git
Redis讀寫分離實例的原理是:key統一寫入到master,而後經過主從複製同步到slave,用戶的請求經過proxy作判斷,若是是寫請求,轉發到master;若是是讀請求,分散轉發到slave,這種架構適合讀請求數量遠大於寫請求數量的業務,讀寫分離架構示意圖以下所示。
圖1. 阿里雲Redis讀寫分離版讀寫命令轉發示例github
通過和客戶溝通查看後,客戶使用了大量的bitfield作讀取,首先介紹一下這個命令的用法和場景,bitfield 是針對bitmap數據類型操做的命令,bitmap一般被用來在極小空間消耗下經過位的運算(AND/OR/XOR/NOT)實現對狀態的判斷,常見的使用場景例如:redis
圖2. 一個使用Redis BITMAP設計的答題遊戲系統
答題系統設計如:數據庫
可見,Redis的bitmap接口能夠用很是高的存儲效率和計算加速效果。回到bitfiled命令,它的語法以下所示:segmentfault
BITFIELD key [GET type offset] // 獲取指定位的值 [SET type offset value] // 設置指定位的值 [INCRBY type offset increment] // 增長指定位的值 [OVERFLOW WRAP|SAT|FAIL] // 控制INCR的界限
從上文可知,bitfield的子命令中,GET命令是讀屬性,SET/INCRBY命令爲寫屬性,所以Redis將其歸類爲寫屬性,從而只能被轉發到master實例,以下圖所示爲bitfield的路由狀況。
這就是爲何客戶使用了讀寫分離版,而只有master節點cpu使用高,其他slave節點卻沒有收到這個命令的打散的緣由。後端
• 方案一:改造Redis內核,將bitfield命令屬性標記爲讀屬性,可是當其包含SET/INCRBY等寫屬性的子命令時候,仍舊將其同步到slave等。此方案優勢是外部組件(proxy和客戶端)不須要作修改,缺點是須要對bitfiled命令作特殊處理,破壞引擎命令統一處理的一致性。架構
• 方案二:增長bitfield_ro命令,相似於georadius_ro命令,用來只支持get選項,從而做爲讀屬性,這樣就避免了slave沒法讀取的問題。此方案優勢是方案清晰可靠,缺點是須要proxy和客戶端作適配才能使用。性能
通過討論,最終採起了方案二,由於這個方案更優雅,也更標準化。阿里雲
{"bitfield_ro",bitfieldroCommand,-2, "read-only fast @bitmap", 0,NULL,1,1,1,0,0,0},
完成以後,下圖是在slave上執行bitfield_ro命令,能夠看到被正確執行。url
tair-redis > SLAVEOF 127.0.0.1 6379 OK tair-redis > set k v (error) READONLY You can't write against a read only replica. tair-redis > BITFIELD mykey GET u4 0 (error) READONLY You can't write against a read only replica. tair-redis > BITFIELD_RO mykey GET u4 0 1) (integer) 0
爲了保持用戶不作代碼修改,咱們在proxy上對bitfiled命令作了兼容,即若是用戶的bitfield命令只有get選項,proxy會將此命令轉換爲bitfield_ro分散轉發到後端多個節點上,從而實現加速,用戶不用作任何改造便可完成加速,以下圖所示。
圖4. 添加BITFIELD_RO命令後處理BITFIELD邏輯流程
2.4 貢獻社區
咱們將本身的修改回饋給了社區,而且被Redis官方接受(https://github.com/antirez/redis/pull/6951)
值得一提的是,阿里雲在國內是最大的Redis社區contributer,如在新發布的Redis-6.0rc中,阿里雲的貢獻排第三,僅次於做者和Redis vendor(Redis Labs)。阿里雲仍舊在不斷的回饋和貢獻社區。
圖5. Redis6.0 RC commit數目榜
阿里雲Redis經過增長bitfield_ro命令,解決了官方bitfield get命令沒法在slave上加速執行的問題。
除過bitfield命令,阿里雲Redis也同時對georadius命令作了兼容轉換,即在讀寫分離實例上,若是georadius/georadiusbymember命令沒有store/storedist選項,將會被自動判斷爲讀命令轉發到slave加速執行。
3.2 思考
咱們思考讀寫分離版的場景,爲何用戶須要讀寫分離呢?爲何不是用集羣版呢?咱們作一下簡單對比,好比設置社區版的服務能力爲K,那麼表的對好比下(咱們只添加了加強版Tair的主備作對比,集羣版能夠直接乘以分片數):
方式
Redis社區版集羣
Redis社區版讀寫分離
Redis(Tair加強版)主備
寫(key均勻狀況)
K*分片數
K
K*3
讀(key均勻狀況)
K*分片數
K*只讀節點數
K*3
寫(單key或熱key)
K(最壞狀況)
K
K*3
讀(單key或熱key)
K(最壞狀況)
K*只讀節點數
K*3
表1. Redis社區版(集羣/讀寫分離)和加強版(主備)簡單場景對比
可見,其實讀寫分離版屬於對單個key和熱key的讀能力的擴展的一種方法,比較適合中小用戶有大key的狀況,它沒法解決用戶的突發寫的瓶頸,好比在這個場景下,若是用戶的bitfield命令是寫請求(子命令中帶有INCRBY和SET),就會遇到沒法解決的性能問題。
從表的對比看,這種狀況下,用戶若是能把key拆散,或者把大key拆成不少小key,就可使用集羣版得到良好的線性加速能力。大key帶來的問題包含但不只限於:
這也是Tair加強版在阿里集團內各個應用建議的:「避免設計出大key和慢查,能避免90%以上的Redis問題」。
可是在實際使用中,用戶仍舊不可避免的遇到熱點問題,好比搶購,好比熱劇,好比超大型直播間等;尤爲是不少熱點具有「突發性」的特色,事先並不知曉,衝擊隨時可達。Redis加強版的性能加強實例具有單key在O(1)操做40~45w ops的服務能力和極強的抗衝擊能力,單機主備版就足夠應對一場中大型的秒殺活動!同時若是用戶沒有大key,加強性能集羣版可以近乎賦予用戶千萬甚至幾千萬OPS的服務能力,這也是Tair做爲阿里重器,支持每次平穩渡過雙11購物節秒殺的關鍵,歡迎你們試用!
最後,打一個小廣告~若是對KV存儲系統,圖數據庫有興趣的小夥伴,歡迎加入咱們團隊,簡歷發送至:zongdai at taobao dot com