我們是如何在兩週內完成 ElastiCache 遷移的?

本文主要講述 Beat 公司的 ElastiCache 遷移故事。

Beat 的系統是由一個比較大但規模不斷縮小的單體系統,和不斷增加的微服務組成的。爲了支撐持久化服務,ElastiCache 使用了多種數據庫來存儲其狀態,同時選擇 Redis 作爲前面的第二層存儲。到目前爲止,Beat 一直在禁用集羣模式下使用 AWS ElastiCache 託管服務,在一段時間內容,ElastiCache 爲 Beat 提供了很好的服務,但是最近它帶來了一些麻煩,甚至導致無法再進行擴展。

禁用集羣的 AWS ElastiCache 架構

從上圖很容易看出,主節點是瓶頸,目前唯一的擴展方法是垂直擴展。我們嘗試了幾次垂直擴展,但是擴展過程很痛苦,而且會導致停機。此外,垂直擴展使得我們的成本上升了很多,無法充分利用實例的能力,即使是在集羣中添加一個節點也非常耗時,有時還會導致小停機甚至大停機。

我們的單體服務傾向於創建熱鍵,特定事件會導致負載峯值。這樣設計是不合理的,我們也有計劃去重構,但這都需要時間。在沒重構之前,我們希望有一個可以更好地擴展的系統。

爲了解決這個問題,並防止將來出現更大停機時間的情況,我們決定組建一個由後端、QA 和基礎設施工程師組成的子團隊,並提出可伸縮的替代解決方案。經過與 AWS 技術客戶經理和支持工程師的幾輪討論之後,我們決定採用啓用集羣模式的架構。從理論上講,這將讓我們可以擴展重負荷的主節點並平衡其流量。

新的架構如下圖所示:

啓用集羣的 AWS ElastiCache 架構

壓力測試

在真正投入到新架構之前,我們要先來測試一下它是否能夠滿足我們的期望和增長需求。

我們的需求包括:

  • 如果一個特定的 shard 節點過載,我們應該能夠添加副本節點,而不會對現有集羣產生任何影響。

  • 如果某個特定 shard 節點的負載比其他 shard 節點大很多,我們應該能夠創建一個新的 shard 並重新平衡集羣,而不需要停機或對客戶端造成任何影響。

  • 如果我們想要垂直地擴展集羣並更改實例類型,那麼應該不需要停機。

對於每一項測試,我們都創建了一個測試集羣,加載了一些虛擬數據,並開始從多個客戶端進行查詢。

我們使用了像 memtier 和 redis-benchmark 這樣的工具,以及一些自己開發的腳本,這些腳本能夠使測試平臺儘可能接近產品,並且允許測試我們的用例。

測試通過之後,我們就可以進入到新集羣能力規劃的階段。

能力規劃

在壓力測試階段,我們檢查了當前的系統,並計算了當時服務於當前負載所需的資源。我們的目標是使新設置的初始版本能夠支撐兩倍的負載。畢竟,擴展需求隨時都可能出現。

事實上,Redis 服務器是單線程的,這使得我們可以關注整個集羣的內存、網絡帶寬和連接數量等指標。

出於某些原因,我們打算保守地規劃能力,並且使得以後可以輕鬆添加更多的 shard 和副本節點,而不需要停機。同時,我們在管道中進行了一些改進,這將有助於減少集羣負載。

遷移階段

當準備好了新的集羣和支持它的代碼庫,我們就開始執行一個由多個階段組成的發佈計劃,儘可能在每個階段都更少的引入更改。

“試水”階段

在這一階段,除了應該支持 Redis 集羣模式之外,我們並沒有對後端進行任何大幅的更改,只針對一小部分用戶(最初是在希臘市場)啓用了集羣。在質量保證工程師的支持下,我們做了切換。然而,結果並沒有讓人眼前一亮。

啓用集羣后的集羣 CPU 使用情況

上圖展示了我們的新集羣以某種方式更好地平衡了流量,並且負載在多個主機之間進行了分配。然而,流量分佈並沒有達到預期,因此,我們需要繼續深入研究,使流量更好地分佈在節點上。

“熱身”階段

我們確實有一張隱藏的王牌,我們懷疑新客戶端沒有使用持久連接。使用來自 bcc 工具 的 tcpconnect 腳本,我們觀察到有大量的新連接連接到 Redis 集羣的 TCP 端口。

>astrikos@co-247-api-100:~$ sudo /usr/share/bcc/tools/tcpconnect -P 6379
>PID COMM IP SADDR DADDR DPORT
>28083 php-fpm7.1 4 10.9.0.92 10.9.3.61 6379
>23983 php-fpm7.1 4 10.9.0.92 10.9.3.251 6379
>17563 php-fpm7.1 4 10.9.0.92 10.9.2.214 6379
>21281 php-fpm7.1 4 10.9.0.92 10.9.2.248 6379
>757 php-fpm7.1 4 10.9.0.92 10.9.2.214 6379
>13566 php-fpm7.1 4 10.9.0.92 10.9.2.138 6379
>4982 php-fpm7.1 4 10.9.0.92 10.9.2.27 6379
>1084 php-fpm7.1 4 10.9.0.92 10.9.3.120 6379
>21281 php-fpm7.1 4 10.9.0.92 10.9.3.219 6379
>...

對於每個 Redis 命令,我們都在創建到服務器的新連接。對於系統級的 ElastiCache 節點來說,這樣做成本非常高,因爲它會導致 Linux 內核在打開和關閉這些新連接時做大量的工作。在使用新的持久連接標識部署代碼之後,我們很高興地看到了以下效果。

左側:集羣當前的連接——右側:集羣新的連接

啓用持久連接後的集羣 CPU 使用情況

如你所見,CPU 大幅下降,當前連接增加,因爲它們是長時間存在的,而新連接幾乎減少到 0。同時,重新運行 tcpconnect 工具,我們看到,實例中新連接的比例顯著降低。

“大海撈針”階段

然而,我們仍然沒有解決特定 shard 主節點不能平衡負載的問題。我們知道,Redis 流量模式是寫 / 讀命令 1:7,這意味着,如果在主節點和副本節點之間分配流量,主節點的負載就不應該那麼重。現在是進行網絡檢查的時候了,看看我們與不同的 Redis 集羣節點交換的是什麼類型的流量。在我們的一個正在運行集羣客戶端的實例中觸發 tcpdump 之後,我們注意到一件有趣的事情:

>20:54:36.071016 IP **10.3.2.202**.52244 > **10.3.2.246**.6379: Flags [P.], seq 119034:119078, ack 77591, win 852, options [nop,nop,TS val 9057048 ecr 2717873315], length 25: **RESP “GET” “core_settings”**
>>20:54:36.081016 IP **10.3.2.246**.6379 > **10.3.2.202**.52244: Flags [P.], seq 119000:119034, ack 119078, win 227, options [nop,nop,TS val 3670031817 ecr 10018445], length 29: **RESP “MOVED 13782 10.3.2.35:6379”**

我們的客戶端實例是 IP 爲 10.3.2.202 的機器,Redis 副本節點 IP 是 10.3.2.246。

我們從集羣分片映射中得知,特定的 Redis 副本是分片的一部分,負責請求的密鑰。我們得到的響應是一個 MOVED 響應,它將我們重定向到另一個 IP 爲 10.3.2.35 的實例,這個實例恰好是這個分片的主節點。經過研究之後,我們發現,爲了使副本響應 READONLY 命令,我們必須在命令前面加上一個 READONLY 前綴。我們的後端工程師在代碼庫中做了更改,一旦部署了新的更改,我們就看到了以下內容:

集羣 CPU 使用情況變化

這樣就完成了任務,主節點和副本節點之間的差距明顯縮小了。

“收尾”階段

如果你仔細查看上面的圖表,就會發現我們的主節點獲得的流量低於預期。我們把流量從主節點轉移到了副本,導致了一個非同質的流量模式。通過與後端工程師交談,這被證明是我們內部庫的一個特性,它是作爲我們之前設置的一部分開發的。因爲現在不再需要它了,所以我們禁用了它,並允許主節點也獲得只讀查詢的一部分。在完成這最後一項工作之後,我們得到了以下令人滿意的結果。

READONLY 變更後的 CPU 使用情況

作者介紹:
Andreas Strikos 是一名高級 DevOps 工程師,是 Beat DevOps 小組的成員。他不斷嘗試在編寫代碼和構建健壯的系統之間找到平衡。他熱衷於網絡和複雜的系統架構。

原文鏈接:

https://build.thebeat.co/an-elasticache-migration-story-9090a524b3f8

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章