大分区表高并发性能提升100倍?阿里云 RDS PostgreSQL 12 解读
1. 問題
阿里云某客戶發現自己使用讀寫分離實例,master的cpu特別高,而讀寫分離中承擔讀流量的slave節點卻相對空閑。用戶CPU打滿后,訪問到主節點的的線上服務受到了較大影響。
1.1 讀寫分離原理
Redis讀寫分離實例的原理是:key統一寫入到master,然后通過主從復制同步到slave,用戶的請求通過proxy做判斷,如果是寫請求,轉發到master;如果是讀請求,分散轉發到slave,這種架構適合讀請求數量遠大于寫請求數量的業務,讀寫分離架構示意圖如下所示。
圖1. 阿里云Redis讀寫分離版讀寫命令轉發示例
1.2 bitfield命令
經過和客戶溝通查看后,客戶使用了大量的bitfield做讀取,首先介紹一下這個命令的用法和場景,bitfield 是針對bitmap數據類型操作的命令,bitmap通常被用來在極小空間消耗下通過位的運算(AND/OR/XOR/NOT)實現對狀態的判斷,常見的使用場景例如:
- 通過bitmap來記錄用戶每天應用登錄狀態,即如果$ID用戶登錄,就SETBIT logins:20200404 $ID 1,表示用戶$ID在20200404這一天登錄了,通過BITCOUNT logins:20200404可以得到這一天所有登錄過的用戶數量;通過對兩天的記錄求AND,可以判斷哪個用戶連續兩天登錄了,即BITOP AND logins:20200404-05 logins:20200404 logins:20200404。
- 判斷用戶是否閱讀了共同的文章,觀看了共同的視頻等。
- 前一陣子,答題領獎活動非常火爆,“答對12道題的同學有機會瓜分獎池”,這種如果使用bitmap來實現,就非常容易判斷出用戶是否全部答對。
圖2. 一個使用Redis BITMAP設計的答題游戲系統
答題系統設計如:
可見,Redis的bitmap接口可以用非常高的存儲效率和計算加速效果。回到bitfiled命令,它的語法如下所示:
BITFIELD key [GET type offset] // 獲取指定位的值 [SET type offset value] // 設置指定位的值 [INCRBY type offset increment] // 增加指定位的值 [OVERFLOW WRAP|SAT|FAIL] // 控制INCR的界限1.3 讀寫分離實例處理bitfield的問題
從上文可知,bitfield的子命令中,GET命令是讀屬性,SET/INCRBY命令為寫屬性,因此Redis將其歸類為寫屬性,從而只能被轉發到master實例,如下圖所示為bitfield的路由情況。
這就是為什么客戶使用了讀寫分離版,而只有master節點cpu使用高,其余slave節點卻沒有收到這個命令的打散的原因。
2. 思路和處理
2.1 解決方案
? 方案一:改造Redis內核,將bitfield命令屬性標記為讀屬性,但是當其包含SET/INCRBY等寫屬性的子命令時候,仍舊將其同步到slave等。此方案優點是外部組件(proxy和客戶端)不需要做修改,缺點是需要對bitfiled命令做特殊處理,破壞引擎命令統一處理的一致性。
? 方案二:增加bitfield_ro命令,類似于georadius_ro命令,用來只支持get選項,從而作為讀屬性,這樣就避免了slave無法讀取的問題。此方案優點是方案清晰可靠,缺點是需要proxy和客戶端做適配才能使用。
經過討論,最終采取了方案二,因為這個方案更優雅,也更標準化。
2.2 添加bitfield_ro
{"bitfield_ro",bitfieldroCommand,-2, "read-only fast @bitmap", 0,NULL,1,1,1,0,0,0},完成之后,下圖是在slave上執行bitfield_ro命令,可以看到被正確執行。
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) 02.3 Proxy轉發
為了保持用戶不做代碼修改,我們在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數目榜
3. 引申和討論
3.1 總結
阿里云Redis通過增加bitfield_ro命令,解決了官方bitfield get命令無法在slave上加速執行的問題。
除過bitfield命令,阿里云Redis也同時對georadius命令做了兼容轉換,即在讀寫分離實例上,如果georadius/georadiusbymember命令沒有store/storedist選項,將會被自動判斷為讀命令轉發到slave加速執行。
3.2 思考
我們思考讀寫分離版的場景,為什么用戶需要讀寫分離呢?為什么不是用集群版呢?我們做一下簡單對比,比如設置社區版的服務能力為K,那么表的對比如下(我們只添加了增強版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帶來的問題包含但不僅限于:
- 大key會造成數據傾斜,使得Redis的容量和服務能力不能線性擴展
- 大key意味著大概率這個key是熱點
- 一旦不小心針對大key有range類的操作,會出現慢查詢,還容易打爆帶寬
這也是Tair增強版在阿里集團內各個應用建議的:“避免設計出大key和慢查,能避免90%以上的Redis問題”。
但是在實際使用中,用戶仍舊不可避免的遇到熱點問題,比如搶購,比如熱劇,比如超大型直播間等;尤其是很多熱點具備“突發性”的特點,事先并不知曉,沖擊隨時可達。Redis增強版的性能增強實例具備單key在O(1)操作40~45w ops的服務能力和極強的抗沖擊能力,單機主備版就足夠應對一場中大型的秒殺活動!同時如果用戶沒有大key,增強性能集群版能夠近乎賦予用戶千萬甚至幾千萬OPS的服務能力,這也是Tair作為阿里重器,支持每次平穩渡過雙11購物節秒殺的關鍵,歡迎大家試用!
最后,打一個小廣告~如果對KV存儲系統,圖數據庫有興趣的小伙伴,歡迎加入我們團隊,簡歷發送至:zongdai at taobao dot com
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的大分区表高并发性能提升100倍?阿里云 RDS PostgreSQL 12 解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 陈旸:清华博士的模型信仰
- 下一篇: 58到家为2000万家庭提供上门生活服务