開源|如何開發一個高性能的redis cluster proxy?

文|曹佳俊node

網易智慧企業資深服務端開發工程師react

背    景git

redis cluster簡介github

Redis cluster是redis官方提供集羣方案,設計上採用非中心化的架構,節點之間經過gossip協議交換互相的狀態,redis cluster使用數據分片的方式來構建集羣,集羣內置了16384個哈希槽,每一個key都屬於這16384這個哈希槽中的一個,經過crc16算法計算哈希值,再取餘可得每一個key歸屬的哈希槽;redis cluster支持動態加入新節點,動態遷移slot,自動的故障轉移等。redis

Redis cluster的架構要求客戶端須要直接與redis集羣中的每一個節點創建鏈接,而且當出現新增節點加入、節點宕機failover、slot遷移等事件時,客戶端須要可以經過redis cluster協議去更新本地的slot映射表,而且能處理ASK/MOVE語義,所以,咱們通常稱實現了redis cluster協議的客戶端爲smart redis client算法

Redis cluster最多能夠構建超過100個主節點的集羣(超過以後gossip協議開銷過大,且可能引發集羣不穩定),按照單節點10G容量(單實例內存過大可能致使性能降低),單集羣最多能夠支撐1T左右的容量。spring

問    題segmentfault

Redis cluster有不少優勢(好比能夠構建大容量集羣,性能好,擴縮容靈活),可是當一些項目工程指望從redis遷移到redis cluster時,客戶端卻面臨着大量的改造工做,與此同時帶來的是須要大量的測試工做以及引入的新風險,這對於一些穩定運行的線上工程代價無疑是巨大的。後端

需    求緩存

爲了更方便的將業務遷移到redis cluster,最指望的是客戶端SDK的API徹底兼容redis/redis-cluster,spring提供的RedisTemplate是一個很好實現,可是對於沒有使用SpringRedisTemplate的項目,不少客戶端實現的redis和redis-cluster訪問API是不一致的(好比Java中流行的Jedis),這無形中提升了遷移工做的工做量和複雜性,此時redis cluster proxy是不錯的選擇,有了proxy,就能夠像操做單實例redis同樣操做redis cluster,客戶端程序就不須要作任何的修改。 

固然,增長一層proxy,必然會致使性能有必定程度的降低,可是proxy做爲無狀態的服務,理論上能夠水平擴展,而且因爲proxy層的存在減小了後端redis server的鏈接數,在某些極限場景下甚至能提升redis集羣總體的吞吐量。此外,基於proxy,咱們還能夠作不少額外的事情:

  • 好比能夠在proxy層作分片邏輯,這樣當單集羣的redis cluster不知足需求(內存/QPS)時,就能夠經過proxy層實現透明的同時訪問多個redis cluster集羣。
  • 再好比能夠在proxy層作雙寫邏輯,這樣在遷移或者拆分緩存類型的redis時,就不須要使用redis-migrate-tool之類的工具進行全量遷移,而只須要按需雙寫,便可完成遷移。
  • 此外由於proxy實現了redis協議,所以能夠在proxy層利用其它存儲介質實現redis相關命令,從而能夠模擬成redis對外服務。一個典型的場景就是冷熱分離存儲。

    功    能

介於上述各類緣由和需求,咱們基於netty開發了camellia-redis-proxy這樣一箇中間件,支持以下特性

  • 支持設置密碼
  • 支持代理到普通redis,也支持代理到redis cluster
  • 支持配置自定義的分片邏輯(能夠代理到多個redis/redis-cluster集羣)
  • 支持配置自定義的雙寫邏輯(服務器會識別命令的讀寫屬性,配置雙寫以後寫命令會同時發往多個後端)
  • 支持外部插件,從而能夠複用協議解析模塊(當前包括camellia-redis-proxy-hbase插件,實現了zset命令的冷熱分離存儲)
  • 支持在線變動配置(需引入camellia-dashboard)
  • 支持多個業務邏輯共享一套proxy集羣,如:A業務配置轉發規則1,B業務配置轉發規則2(須要在創建redis鏈接時經過client命令設置業務類型)
  • 對外提供了一個spring-boot-starter,3行代碼便可快速搭建一個proxy集羣

如何提高性能?

客戶端向camellia-redis-proxy發起一條請求,到收到請求回包的過程當中,依次經歷了以下過程:

  • 上行協議解析(IO讀寫)
  • 協議轉發規則匹配(內存計算)
  • 請求轉發(IO讀寫)
  • 後端redis回包解包(IO讀寫)
  • 後端redis回包下發到客戶端(IO讀寫)

能夠看到做爲一個proxy,大量的工做是在進行網絡IO的操做,爲了提高proxy的性能,作了如下工做:

多線程

咱們知道redis自己是單線程的,可是做爲一個proxy,徹底可使用多線程來充分利用多核CPU的性能,可是過多的線程引發沒必要要的上下文切換又會引發性能的降低。camellia-redis-proxy使用了netty的多線程reactor模型來確保服務器的處理性能,默認會開啓cpu核心數的work線程。 此外,若是服務器支持網卡多隊列,開啓它,能避免CPU不一樣核心之間的load不均衡;若是不支持,那麼將業務進程綁核到非CPU0的其餘核心,從而讓CPU0專心處理網卡中斷而不被業務進程過多的影響。

異步非阻塞

異步非阻塞的IO模型通常狀況下都是優於同步阻塞的IO模型,上述5個過程當中,除了協議轉發規則匹配這樣的內存計算,整個轉發流程都是異步非阻塞,確保不會由於個別流程的阻塞影響整個服務。

流水線

咱們知道redis協議支持流水線(pipeline),pipeline的使用,能夠有效減小網絡開銷。camellia-redis-proxy也充分利用了這樣的特性,主要包括兩方面:

  • 上行協議解析時儘量的一次性解析多個命令,從而進行規則轉發時能夠批量進行
  • 日後端redis節點進行轉發時儘量的批量提交,這裏除了對來自同一個客戶端鏈接的命令進行聚合,還能夠對來自不一樣客戶端鏈接,但轉發目標redis相同時,也能夠進行命令聚合

固然,全部這些批量和聚合的操做都須要保證請求和響應的一一對應。

TCP分包和大包處理

不論是上行協議解析,仍是來自後端redis的回包,特別是大包的場景,在碰到TCP分包時,利用合適的checkpoint的機制能夠有效減小重複解包的次數,提高性能

異常處理和異常日誌合併

若是沒有有效的處理各類異常,在異常發生時也會致使服務器性能迅速降低。想象一個場景,咱們配置了90%的流量轉發給A集羣,10%的流量轉發到B集羣,若是B集羣發生了宕機,咱們指望的是來自客戶端的90%的請求正常執行,10%的請求失敗,可是實際上卻可能遠遠超過10%的請求都失敗了,緣由是多方面的:

  • 後端操做系統層面的忽然宕機proxy層可能沒法當即感知(沒有收到TCP fin包),致使大量請求在等待回包,雖然proxy層沒有阻塞,可是客戶端表現爲請求超時
  • proxy在嘗試轉發請求到B集羣時,針對B集羣的從新鏈接請求可能拖慢整個流程
  • 宕機致使的大量異常日誌可能會引發服務器性能降低(這是一個容易忽視的地方)
  • pipeline提交上來的請求,99個請求指向A集羣,1個請求指向B集羣,可是因爲B集羣的不可用,致使指向B集羣的請求遲遲不回包或者異常響應過慢,客戶端的最終表現是100個請求所有失敗了

camellia-redis-proxy在處理上述問題時,採起了以下策略:

  • 設置對異常後端節點的快速失敗降級策略,避免拖慢整個服務
  • 異常日誌統一管理,合併輸出,在不丟失異常信息的狀況下,減小異常日誌對服務器性能的影響
  • 增長對後端redis的定時探活探測,避免宕機沒法當即感知致使業務長時間異常

    部署架構

proxy做爲無狀態的服務,能夠作到水平擴展,爲了服務的高可用,也至少要部署兩個以上的proxy節點,對於客戶端來講,想要像使用單節點redis同樣訪問proxy,能夠在proxy層以前設置一個LVS代理服務,此時,部署架構圖以下:

固然,還有另一個方案,能夠將proxy節點註冊到zk/Eureka/Consul等註冊中心,客戶端經過拉取和監聽proxy的列表,而後再向訪問單節點redis同樣訪問每一個proxy便可。以Jedis爲例,僅需將JedisPool替換爲封裝了註冊發現邏輯的RedisProxyJedisPool,便可像訪問普通redis同樣使用proxy了,此時,部署架構圖以下

應用場景

  • 須要從redis遷移到redis-cluster,可是客戶端代碼不方便修改
  • 客戶端直連redis-cluster,致使cluster服務器鏈接過多,致使服務器性能降低
  • 單個redis/redis-cluster集羣容量/QPS不知足業務需求,使用camellia-redis-proxy的分片功能
  • 緩存類redis/redis-cluster集羣拆分遷移,使用camellia-redis-proxy的雙寫功能
  • 使用雙寫功能進行redis/redis-cluster的災備
  • 混合使用分片和雙寫功能的一些業務場景
  • 基於camellia-redis-proxy的插件功能,開發自定義插件

結    語

Redis cluster做爲官方推薦的集羣方案,愈來愈多的項目已經或正在遷移到redis cluster,camellia-redis-proxy正是在這樣的背景下誕生的;特別的,若是你是一個Java開發者,camellia還提供了CamelliaRedisTemplate這樣的方案,CamelliaRedisTemplate擁有和普通Jedis一致的API,提供了mget/mset/pipeline等原生JedisCluster不支持的特性,且提供了和camellia-redis-proxy功能一致的分片/雙寫等特性。

爲了回饋社區,camellia已經正式開源了,想詳細瞭解camellia項目的請點擊【閱讀原文】訪問github,同時附上地址:

https://github.com/netease-im...

若是你有什麼好的想法或者提案,或者有什麼問題,歡迎提交issue與咱們交流!

關於做者

曹佳俊。網易智慧企業資深服務端開發工程師。中科院研究生畢業後加入網易,一直在網易雲信負責IM服務器相關的開發工做。

做者:網易雲信 連接:https://segmentfault.com/a/1190000023210717 來源:SegmentFault 思否 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索