背景node
Why K8Sredis
How K8s數據庫
Why Proxy後端
Proxy帶來的問題安全
K8s帶來的好處bash
遇到的問題網絡
總結架構
小米的Redis使用規模很大,如今有數萬個實例,而且天天有百萬億次的訪問頻率,支撐了幾乎全部的產品線和生態鏈公司。以前全部的Redis都部署在物理機上,也沒有作資源隔離,給管理治理帶來了很大的困難。咱們的運維人員工做壓力很大,機器宕機網絡抖動致使的Redis節點下線都常常須要人工介入處理。因爲沒有作CPU的資源隔離,slave節點打RDB或者因爲流量突增致使節點QPS升高形成的節點CPU使用率升高,均可能對本集羣或其餘集羣的節點形成影響,致使沒法預測的時延增長。負載均衡
Redis分片方式採用社區的Redis Cluster協議,集羣自主分片。Redis Cluster帶來了必定的易用性的同時,也提升了應用開發的門檻,應用開發人員須要必定程度上了解Redis Cluster,同時須要使用智能客戶端訪問Redis Cluster。這些智能客戶端配置參數繁多,應用開發人員並沒有法徹底掌握並設置這些參數,踩了不少坑。同時,因爲智能客戶端須要作分片計算,給應用端的機器也帶來了必定的負載。運維
當前的Redis Cluster部署在物理機集羣上,爲了提升資源利用率節約成本,多業務線的Redis集羣都是混布的。因爲沒有作CPU的資源隔離,常常出現某Redis節點CPU使用率太高致使其餘Redis集羣的節點爭搶不到CPU資源引發時延抖動。由於不一樣的集羣混布,這類問題很難快速定位,影響運維效率。K8s容器化部署能夠指定 CPU request 和 CPU limit ,在提升資源利用率的同時避免了資源爭搶。
自動化部署。當前Redis Cluster在物理機上的部署過程十分繁瑣,須要經過查看元信息數據庫查找有空餘資源的機器,手動修改不少配置文件再逐個部署節點,最後使用redis_trib工具建立集羣,新集羣的初始化工做常常須要一兩個小時。
K8s經過StatefulSet部署Redis集羣,使用configmap管理配置文件,新集羣部署時間只須要幾分鐘,大大提升了運維效率。
客戶端經過LVS的VIP統一接入,經過Redis Proxy轉發服務請求到Redis Cluster集羣。這裏咱們引入了Redis Proxy來轉發請求。
Redis部署爲StatefulSet,做爲有狀態的服務,選擇StatefulSet最爲合理,能夠將節點的RDB/AOF持久化到分佈式存儲中。當節點重啓漂移到其餘機器上時,可經過掛載的PVC(PersistentVolumeClaim)拿到原來的RDB/AOF來同步數據。咱們選擇的持久化存儲PV(PersistentVolume)是Ceph Block Service。Ceph的讀寫性能低於本地磁盤,會帶來100~200ms的讀寫時延。但因爲Redis的RDB/AOF的寫出都是異步的,分佈式存儲帶來的讀寫延遲對服務並無影響。
測試工具: redis-benchmark
Proxy CPU: 2 core
Client CPU: 2 core
Redis Cluster: 3 master nodes, 1 CPU per node
Proxy做爲deployment部署,無狀態輕量化,經過LB對外提供服務,很容易作到動態擴縮容。同時,咱們爲Proxy開發了動態切換後端Redis Cluster的功能,可實如今線添加和切換Redis Cluster。
咱們使用K8s原生的HPA(Horizontal Pod Autoscaler)來實現Proxy的動態擴縮容。當Proxy全部pod的平均CPU使用率超過必定閾值時,會自動觸發擴容,HPA會將Proxy的replica數加1,以後LVS就會探測到新的Proxy pod並將一部分流量切過去。若是擴容後CPU使用率仍然超過規定的閾值,會繼續觸發擴容邏輯。可是在擴容成功5分鐘內,不論CPU使用率降到多低,都不會觸發縮容邏輯,這樣就避免了頻繁的擴縮容給集羣穩定性帶來的影響。
HPA可配置集羣的最少(MINPODS)和最多(MAXPODS)pod數量,集羣負載再低也不會縮容到MINPODS如下數量的pods。建議客戶能夠根據本身的實際業務狀況來決定MINPODS和MAXPODS的值。
使用Redis Cluster的Redis客戶端,都須要配置集羣的部分IP和Port,用於客戶端重啓時查找Redis Cluster的入口。對於物理機集羣部署的Redis節點,即使遇到實例重啓或者機器重啓,IP和Port均可以保持不變,客戶端依然可以找到Redis Cluster的拓撲。可是部署在K8s上的Redis Cluster,pod重啓是不保證IP不變的(即使是重啓在原來的K8s node上),這樣客戶端重啓時,就可能會找不到Redis Cluster的入口。
經過在客戶端和Redis Cluster之間加上Proxy,就對客戶端屏蔽了Redis Cluster的信息,Proxy能夠動態感知Redis Cluster的拓撲變化,客戶端只須要將LVS的IP:Port做爲入口,請求轉發到Proxy上,便可以像使用單機版Redis同樣使用Redis Cluster集羣,而不須要Redis智能客戶端。
在6.0版本以前,Redis都是單線程處理大部分任務的。當Redis節點的鏈接較高時,Redis須要消耗大量的CPU資源處理這些鏈接,致使時延升高。有了Proxy以後,大量鏈接都在Proxy上,而Proxy跟Redis實例之間只保持不多的鏈接,這樣下降了Redis的負擔,避免了由於鏈接增長而致使的Redis時延升高。
在使用過程當中,隨着業務的增加,Redis集羣的數據量會持續增長,當每一個節點的數據量太高時,BGSAVE的時間會大大延長,下降集羣的可用度。同時QPS的增長也會致使每一個節點的CPU使用率增高。這都須要增長擴容集羣來解決。目前Redis Cluster的橫向擴展能力不是很好,原生的slots搬移方案效率很低。新增節點後,有些客戶端好比Lettuce,會由於安全機制沒法識別新節點。另外遷移時間也徹底沒法預估,遷移過程當中遇到問題也沒法回退。
當前物理機集羣的擴容方案是:
按需建立新集羣
使用同步工具將數據從老集羣同步到新集羣
確認數據無誤後,跟業務溝通,重啓服務切換到新集羣
整個過程繁瑣並且風險較大,還須要業務重啓服務。
有了Proxy層,能夠將後端的建立、同步和切換集羣對客戶端屏蔽掉。新老集羣同步完成以後,向Proxy發送命令就能夠將鏈接換到新集羣,能夠實現對客戶端徹底無感知的集羣擴縮容。
Redis是經過AUTH來實現鑑權操做,客戶端直連Redis,密碼仍是須要在客戶端保存。而使用Proxy,客戶端只須要經過Proxy的密碼來訪問Proxy,不須要知道Redis的密碼。Proxy還限制了FLUSHDB、CONFIG SET等操做,避免了客戶誤操做清空數據或修改Redis配置,大大提升了系統的安全性。
同時,Redis並無提供審計功能。咱們在Proxy上增長了高危操做的日誌保存功能,能夠在不影響總體性能的前提下提供審計能力。
Proxy在客戶端和Redis實例之間,客戶端訪問Redis數據須要先訪問Proxy再訪問Redis節點,多了一跳,會致使時延增長。經測試,多一跳會增長0.2~0.3ms的時延,不過一般這對業務來講是能夠接受的。
Proxy在K8s上是經過deployment部署的,同樣會有節點重啓致使IP變化的問題。咱們K8s的LB方案能夠感知到Proxy的IP變化,動態的將LVS的流量切到重啓後的Proxy上。
LVS也會帶來時延,以下表中的測試,不一樣的數據長度get/set操做,LVS引入的時延小於0.1ms。
經過運維平臺調用K8s API部署集羣,大大提升了運維效率。
目前小米在物理機上部署Redis實例是經過端口來區分的,而且下線的端口不能複用,也就是說整個公司每一個Redis實例都有惟一的端口號。目前65535個端口已經用到了40000多,按如今的業務發展速度,將在兩年內耗盡端口資源。而經過K8s部署,每個Redis實例對應的K8s pod都有獨立的IP,不存在端口耗盡問題和複雜的管理問題。
對應用來講,只須要使用單機版的非智能客戶端鏈接VIP,下降了使用門檻,避免了繁瑣複雜的參數設置。同時因爲VIP和端口是固定不變的,應用程序再也不須要本身管理Redis Cluster的拓撲。
使用非智能客戶端還能夠下降客戶端的負載,由於智能客戶端須要在客戶端對key進行hash以肯定將請求發送到哪一個Redis節點,在QPS比較高的狀況下會消耗客戶端機器的CPU資源。固然,爲了下降客戶端應用遷移的難度,咱們讓Proxy也支持了智能客戶端協議。
Proxy支持動態添加切換Redis Cluster的功能,這樣Redis Cluster的集羣升級和擴容切換過程能夠作到對業務端徹底無感知。例如,業務方使用30個節點的Redis Cluster集羣,因爲業務量的增長,數據量和QPS都增加的很快,須要將集羣規模擴容兩倍。若是在原有的物理機上擴容,須要如下過程:
協調資源,部署60個節點的新集羣
手動配置遷移工具,將當前集羣的數據遷移到新集羣
驗證數據無誤後,通知業務方修改Redis Cluster鏈接池拓撲,重啓服務
雖然Redis Cluster支持在線擴容,可是擴容過程當中slots搬移會對線上業務形成影響,同時遷移時間不可控,因此現階段不多采用這種方式,只有在資源嚴重不足時纔會偶爾使用。
在新的K8s架構下,遷移過程以下:
經過API接口一鍵建立60個節點的新集羣
一樣經過API接口一鍵建立集羣同步工具,將數據遷移到新集羣
驗證數據無誤後,向Proxy發送命令添加新集羣信息並完成切換
整個過程對業務端徹底無感知。
集羣升級也很方便:若是業務方能接受必定的延遲毛刺,能夠在低峯時經過StatefulSet滾動升級的方式來實現;若是業務對延遲有要求,能夠經過建立新集羣遷移數據的方式來實現。
經過K8s自帶的資源隔離能力,實現和其餘不一樣類型應用混部,在提升資源利用率的同時,也能保證服務穩定性。
K8s的pod碰到問題重啓時,因爲重啓速度過快,會在Redis Cluster集羣發現並切主前將pod重啓。若是pod上的Redis是slave,不會形成什麼影響。但若是Redis是master,而且沒有AOF,重啓後原先內存的數據都被清空,Redis會reload以前存儲的RDB文件,可是RDB文件並非實時的數據。以後slave也會跟着把本身的數據同步成以前的RDB文件中的數據鏡像,會形成部分數據丟失。
StatefulSet是有狀態服務,部署的pod名是固定格式(StatefulSet名+編號)。咱們在初始化Redis Cluster時,將相鄰編號的pod設置爲主從關係。在重啓pod時,經過pod名肯定它的slave,在重啓pod前向從節點發送cluster failover命令,強制將活着的從節點切主。這樣在重啓後,該節點會自動以從節點方式加入集羣。
LVS映射時延
Proxy的pod是經過LVS實現負載均衡的,LVS對後端IP:Port的映射生效有必定的時延,Proxy節點忽然下線會致使部分鏈接丟失。爲減小Proxy運維對業務形成影響,咱們在Proxy的deployment模板中增長了以下選項:
lifecycle: preStop: exec: command: - sleep - "171"複製代碼
對於正常的Proxy pod下線,例如集羣縮容、滾動更新Proxy版本以及其它K8s可控的pod下線,在pod下線前會發消息給LVS並等待171秒,這段時間足夠LVS將這個pod的流量逐漸切到其餘pod上,對業務無感知。
K8s原生的StatefulSet不能徹底知足Redis Cluster部署的要求:
Redis Cluster不容許同爲主備關係的節點部署在同一臺機器上。這個很好理解,若是該機器宕機,會致使這個數據分片不可用。
Redis Cluster不容許集羣超過一半的主節點失效,由於若是超過一半主節點失效,就沒法有足夠的節點投票來知足gossip協議的要求。由於Redis Cluster的主備是可能隨時切換的,咱們沒法避免同一個機器上的全部節點都是主節點這種狀況,因此在部署時不能容許集羣中超過1/4的節點部署在同一臺機器上。
爲了知足上面的要求,原生StatefulSet能夠經過 anti-affinity 功能來保證相同集羣在同一臺機器上只部署一個節點,可是這樣機器利用率很低。
所以咱們開發了基於StatefulSet的CRD:RedisStatefulSet,會採用多種策略部署Redis節點。同時,還在RedisStatefulSet中加入了一些Redis管理功能。這些咱們將會在其餘文章中來繼續詳細探討。
目前集團內部已經有多個業務的數十個Redis集羣部署到了K8s上並運行了半年多。得益於K8s的快速部署和故障遷移能力,這些集羣的運維工做量比物理機上的Redis集羣低不少,穩定性也獲得了充分的驗證。
在運維過程當中咱們也遇到了很多問題,文章中提到的不少功能都是根據實際需求提煉出來的。目前仍是有不少問題須要在後續逐步解決,以進一步提升資源利用率和服務質量。
物理機的Redis實例是獨立部署的,單臺物理機上部署的都是Redis實例,這樣有利於管理,可是資源利用率並不高。Redis實例使用了CPU、內存和網絡IO,但存儲空間基本都是浪費的。在K8s上部署Redis實例,其所在的機器上可能也會部署其餘任意類型的服務,這樣雖然能夠提升機器的利用率,可是對於Redis這樣的可用性和時延要求都很高的服務來講,若是由於機器內存不足而被驅逐,是不能接受的。這就須要運維人員監控全部部署了Redis實例的機器內存,一旦內存不足,就切主和遷移節點,但這樣又增長運維的工做量。
同時,若是混部的其餘服務是網絡吞吐很高的應用,也可能對Redis服務形成影響。雖然K8s的 anti-affinity 功能能夠將Redis實例有選擇地部署到沒有這類應用的機器上,可是在機器資源緊張時,仍是沒法避免這種狀況。
Redis Cluster是一個P2P無中心節點的集羣架構,依靠gossip協議傳播協同自動化修復集羣的狀態,節點上下線和網絡問題均可能致使Redis Cluster的部分節點狀態出現問題,例如會在集羣拓撲中出現failed或者handshake狀態的節點,甚至腦裂。對這種異常狀態,咱們能夠在Redis CRD上增長更多的功能來逐步解決,進一步提升運維效率。
Redis自己只提供了Auth密碼認證保護功能,沒有權限管理,安全性較差。經過Proxy,咱們能夠經過密碼區分客戶端類型,管理員和普通用戶使用不一樣的密碼登陸,可執行的操做權限也不一樣,這樣就能夠實現權限管理和操做審計等功能。
單個Redis Cluster因爲gossip協議的限制,橫向擴展能力有限,集羣規模在300個節點時,節點選主這類拓撲變動的效率就明顯下降。同時,因爲單個Redis實例的容量不宜太高,單個Redis Cluster也很難支持TB以上的數據規模。經過Proxy,咱們能夠對key作邏輯分片,這樣單個Proxy就能夠接入多個Redis Cluster,從客戶端的視角來看,就至關於接入了一個可以支持更大數據規模的Redis集羣。
最後,像Redis這種有狀態服務的容器化部署在國內大廠都尚未很是成熟的經驗,小米雲平臺也是在摸索中逐步完善。目前咱們新增集羣已經大部分部署在K8s上,更計劃在一到兩年內將集團內大部分的物理機Redis集羣都遷移到K8s上。這樣就能夠有效地下降運維人員的負擔,在不顯著增長運維人員的同時維護更多的Redis集羣。