你好! 我叫 Alexey Pyankov,是 Sportmaster 公司的開發人員。 在那裡面
今天我想分享另一個主題的想法 - 在網站管理面板中為 java 後端選擇快取系統。 這個情節對我來說有著特殊的意義——雖然故事展開只有兩個月,但在這2天裡我們工作了60-12個小時,沒有休息一天。 我從來沒有想過或想像過可以如此努力地工作。
因此,我將文字分成兩部分,以免完全加載它。 相反,第一部分將非常輕鬆 - 準備、介紹、關於什麼是快取的一些考慮。 如果您已經是經驗豐富的開發人員或使用過緩存,那麼從技術角度來看,本文很可能沒有什麼新內容。 但對於一個後輩來說,這樣一個小小的回顧可以告訴他,當他發現自己處於這樣的十字路口時該往哪個方向看。
當Sportmaster網站的新版本投入使用時,資料的接收方式,說得客氣一點,不太方便。 基礎是為網站的先前版本 (Bitrix) 準備的表格,必須將其引入 ETL,採用新的形式,並用來自十多個系統的各種小東西進行豐富。 為了讓新的圖片或產品描述出現在網站上,您必須等到第二天 - 僅在晚上更新,每天一次。
起初,投入生產的頭幾週有很多擔憂,因此內容管理者的不便只是小事一樁。 但是,一旦一切安定下來,專案的開發就繼續進行——幾個月後,2015 年初,我們開始積極開發管理面板。 2015年和2016年,一切都很順利,我們定期發布,管理面板涵蓋了越來越多的數據準備工作,我們正在為這樣一個事實做好準備:很快我們的團隊將被委託處理最重要和最複雜的事情-產品電路(所有產品資料的充分準備和維護)。 但在 2017 年夏天,就在商品電路啟動之前,該專案將發現自己陷入了非常困難的境地 - 正是因為快取問題。 我想在這本由兩部分組成的出版物的第二部分中談論這一集。
但在這篇文章中,我將從遠處開始,我將提出一些想法 - 關於快取的想法,這將是在大型專案之前滾動瀏覽的一個很好的步驟。
當快取任務發生時
快取任務不只是出現。 我們是開發人員,編寫軟體產品並希望它受到需求。 如果產品有需求並且成功,使用者就會來。 而且越來越多的人來了。 然後有很多用戶,然後產品就會變得負載很高。
在第一階段,我們不考慮最佳化和程式碼效能。 最重要的是功能,快速推出試點並測試假設。 如果負重增加,我們就會給熨斗打氣。 我們將其增加兩到三倍,五倍,也許十倍。 在這裡的某個地方——財政狀況不再允許這樣做。 用戶數量會增加多少倍? 不會像10-2-5那樣,但如果成功的話,會是10-100到1000萬次。 也就是說,遲早你要做優化。
假設程式碼的某些部分(我們稱這部分為函數)花費了相當長的時間,而我們希望減少執行時間。 一個函數可以是存取資料庫,也可以是執行一些複雜的邏輯——最主要的是它需要很長時間才能完成。 您可以減少多少執行時間? 在極限內,你可以將其減少到零,不再進一步。 如何將執行時間降到零? 答案:完全消除執行。 相反,立即返回結果。 你怎樣才能知道結果呢? 答案:要嘛計算一下,要嘛看看別的地方。 需要很長時間來計算。 例如,監視是記住該函數上次使用相同參數呼叫時產生的結果。
也就是說,函數的實作對我們來說並不重要。 只要知道結果取決於哪些參數就足夠了。 然後,如果參數值以物件的形式表示,可以在某些儲存中用作鍵,那麼就可以保存計算結果並在下次存取時讀取。 如果結果的寫入和讀取速度比執行函數更快,那麼我們在速度方面就有優勢。 獲利金額可以達到100倍、1000倍、100萬倍(10^5是比較例外的情況,但在基數相當滯後的情況下,是很有可能的)。
快取系統的基本要求
快取系統的首要要求可能是快速讀取速度,其次是寫入速度。 這是事實,但前提是我們將系統投入生產。
讓我們來玩一下這個案例。
假設我們已經為當前負載提供了硬件,並且現在正在逐步引入快取。 用戶數量增加了一點,負載增加了 - 我們添加了一些緩存,到處都搞砸了。 這種情況持續了一段時間,現在實際上不再呼叫繁重的函數 - 整個主要負載都落在快取上。 這段時間的用戶數增加了N倍。
如果硬體的初始供應量可以是 2-5 倍,那麼在快取的幫助下,我們可以將效能提高 10 倍,或者在良好的情況下提高 100 倍,在某些地方可能提高 1000 倍100。也就是說,在相同的硬體上,我們處理的請求多了XNUMX 倍。 太棒了,你值得擁有薑餅!
但現在,在一個好的時刻,一次偶然的機會,系統崩潰了,快取也崩潰了。 沒什麼特別的——畢竟快取是根據「讀寫速度高,其他無所謂」的要求來選擇的。
相對於啟動負荷,我們的鐵儲備是2-5倍,這段時間的負荷增加了10-100倍。 使用緩存,我們消除了對繁重函數的調用,因此一切正常。 而現在,如果沒有緩存,我們的系統會變慢多少倍? 我們會發生什麼事? 系統就會崩潰。
即使我們的快取沒有崩潰,只是清除了一段時間,它也需要預熱,這需要一些時間。 在此期間,主要負擔將落在功能上。
結論:高負載的生產項目要求快取系統不僅要有較高的讀寫速度,還要確保資料安全和抗故障能力。
選擇的痛苦
在一個帶有管理面板的專案中,選擇是這樣的:首先我們安裝了 Hazelcast,因為透過主站的體驗,我們已經熟悉了這個產品。 但在這裡,這個選擇被證明是不成功的——在我們的負載配置下,Hazelcast 不僅慢,而且慢得可怕。 當時我們已經預定了發布日期。
劇透:情況到底是如何發展到我們錯過了這麼大的一筆交易並最終陷入了嚴重而緊張的局面——我將在第二部分告訴你——以及我們是如何結束的以及我們是如何擺脫困境的。 但現在 - 我只想說這是一個很大的壓力,並且“思考 - 不知何故我無法思考,我們正在搖晃瓶子。” 「搖動瓶子」也是劇透,稍後會詳細介紹。
我們做了什麼:
- 我們列出了 Google 和 StackOverflow 建議的所有系統。 30多一點
- 我們使用典型的生產負載來編寫測試。 為此,我們記錄了生產環境中通過系統的數據 - 一種嗅探器,其數據不是在網路上,而是在系統內部。 測試中正是使用了該數據。
- 在整個團隊中,每個人都從清單中選擇下一個系統,對其進行配置並執行測試。 它沒有通過測試,也不能承載負載——我們把它扔掉,然後繼續處理下一個。
- 到了17號系統,一切都變得毫無希望了。 別再搖晃瓶子了,是時候認真思考了。
但當您需要選擇一個能夠在預先準備的測試中「通過速度」的系統時,這是一個選擇。 如果還沒有這樣的測試而您想快速選擇怎麼辦?
讓我們對這個選項進行建模(很難想像一個中+開發人員生活在真空中,並且在選擇時還沒有正式確定他首先嘗試哪個產品的偏好- 因此,進一步的推理更多的是理論家/哲學/關於大三)。
確定需求後,我們將開始選擇現成的解決方案。 為什麼要重新發明輪子:我們將採用現成的快取系統。
如果你剛開始用谷歌搜索,那麼就給出或接受訂單,但一般來說,指導方針會是這樣的。 首先,你會遇到Redis,它隨處可見。 然後你會發現EhCache是最古老、最成熟的系統。 接下來我們會寫Tarantool,這是一個國內開發的解決方案,具有獨特的面向。 還有 Ignite,因為它現在越來越受歡迎,並且得到了 SberTech 的支持。 最後還有 Hazelcast,因為在企業界它經常出現在大公司。
該列表並不詳盡;有數十個系統。 我們只會搞砸一件事。 我們就拿這次「選美」選出的5個系統來進行評選吧。 誰將成為贏家?
Redis的
我們在官方網站上閱讀了他們寫的內容。
看起來一切都很好,你可以拿走它並擰緊它 - 你需要的一切,它都可以。 但為了好玩,讓我們看看其他候選人。
高速緩存
Redis忘了,我準備選EhCache。
但愛國精神促使我看到塔蘭圖爾的優點。
塔蘭圖爾
讓我們來看看實現:Mail.ru 公司高速公路、Avito、Beeline、Megafon、Alfa-Bank、Gazprom...
如果對 Tarantool 仍有任何疑問,那麼萬事達卡的實施案例就讓我徹底明白了。 我選擇塔蘭圖爾。
但不管怎麼說…
點燃
…還有更多嗎
實施:Sberbank、美國航空、Yahoo! 日本。 然後我發現 Ignite 不僅在 Sberbank 中實施,SberTech 團隊也將其人員派往 Ignite 團隊本身來完善產品。 這完全令人著迷,我已經準備好接受「點燃」了。
完全不清楚為什麼,我正在看第五點。
淡褐色
我去網站
就這樣,我準備好接受 Hazelcast 了。
對照
但如果你看一下,所有五位候選人的描述方式都表明他們每個人都是最好的。 如何選擇? 我們可以看看哪一個最受歡迎,進行比較,頭痛就會消失。
我們找到一個這樣的
它們的排序如下:Redis 位居榜首,Hazelcast 位居第二,Tarantool 和 Ignite 越來越受歡迎,EhCache 一直保持不變。
但讓我們看看
所有這些系統不僅僅是快取系統。 它們還具有許多功能,包括當資料不傳送到客戶端進行處理時,反之亦然:需要在資料上執行的程式碼移動到伺服器,在那裡執行,然後返回結果。 而且它們通常不被視為獨立的快取系統。
好吧,我們不放棄,我們找系統直接比較一下。 讓我們來看看最重要的兩個選項——Redis 和 Hazelcast。 我們感興趣的是速度,我們會根據這個參數來比較它們。
Hz 與 Redis
我們發現這個
藍色是Redis,紅色是Hazelcast。 Hazelcast 無所不在,這是有原因的:它是多執行緒的,高度最佳化的,每個執行緒都有自己的分區,所以不存在阻塞。 而且 Redis 是單線程的;它無法從現代多核心 CPU 中受益。 Hazelcast 具有非同步 I/O,Redis-Jedis 具有阻塞套接字。 畢竟,Hazelcast 使用二進位協議,而 Redis 是以文本為中心的,這意味著它效率低。
以防萬一,讓我們轉向另一個比較來源。 他會向我們展示什麼?
Redis 與 Hz
其他
相反,這裡紅色的是Redis。 也就是說,Redis 在性能方面優於 Hazelcast。 Hazelcast 贏得了第一個比較,Redis 贏得了第二個。
事實證明,第一個結果實際上是被操縱的:Redis 被放在基礎盒子中,Hazelcast 是為測試案例量身定制的。 事實證明:首先,我們不能相信任何人,其次,當我們最終選擇一個系統時,我們仍然需要正確地配置它。 這些設定包括數十個、幾乎數百個參數。
搖晃瓶子
我可以用以下比喻來解釋我們現在所做的整個過程:「搖晃瓶子」。 也就是說,現在你不必編程,現在主要的是能夠閱讀 stackoverflow。 我的團隊裡有一個人,一個專業人士,在關鍵時刻就是這樣運作的。
他在做什麼? 他看到一個損壞的東西,看到一個堆疊跟踪,從中取出一些單字(這些單字是他在該程式中的專業知識),在 Google 上搜索,在答案中找到了 stackoverflow。 沒有閱讀,沒有思考,在問題的答案中,他選擇了與“做這個做那個”這句話最相似的內容(選擇這樣的答案是他的天賦,因為它並不總是獲得最多讚的答案),適用,看起來:如果有什麼改變,那就太好了。 如果沒有改變,則回滾。 並重複啟動-檢查-搜尋。 透過這種直觀的方式,他確保程式碼在一段時間後可以工作。 他不知道為什麼,他不知道自己做了什麼,他無法解釋。 但! 這種感染有效。 並且“火已經撲滅了。” 現在讓我們弄清楚我們做了什麼。 當程式運行時,它會容易一個數量級。 而且它節省了大量時間。
這個例子很好地解釋了這個方法。
曾經很流行收集一艘裝在瓶子裡的帆船。 同時,帆船又大又脆弱,而且瓶頸很窄,根本不可能推進去。 如何組裝呢?
有這樣一個方法,非常快,而且非常有效。
這艘船由一堆小東西組成:棍子、繩子、帆、膠水。 我們把所有這些都裝在一個瓶子裡。
我們用雙手拿起瓶子並開始搖晃。 我們不斷地搖晃她。 當然,通常結果證明它完全是垃圾。 但是有時。 有時它竟然是一艘船! 更準確地說,是類似船的東西。
我們向某人展示這個東西:“Seryoga,你看到了嗎!?” 事實上,從遠處看它就像一艘船。 但不能允許這種情況繼續下去。
還有另一種方法。 它們被更高級的人使用,例如駭客。
我給了這個人一個任務,他做了一切就離開了。 你看——看起來已經完成了。 而過了一段時間,當程式碼需要敲定的時候,這一切都是因為他而開始的……還好他已經跑得遠遠的了。 這些人以瓶子為例,會這樣做:你看,底部所在的地方,玻璃會彎曲。 而且其是否透明尚不完全清楚。 然後「駭客」切掉這個底部,在那裡插入一艘船,然後再把底部黏回去,就好像它應該是這樣的。
從設定問題的角度來看,一切似乎都是對的。 但以船舶為例:為什麼要製造這艘船?誰需要它? 它不提供任何功能。 通常這樣的船是送給地位很高的人的禮物,他們把它放在他們上方的架子上,作為某種象徵,作為標誌。 如果是這樣一個人,一個大企業的負責人或一個高級官員,那麼旗幟怎麼會代表這樣一個脖子被砍掉的黑客呢? 如果他從來不知道這件事就好了。 那麼,他們最終是如何製造出這些可以送給重要人物的船隻的呢?
唯一你真正無能為力的關鍵地方就是身體。 船體恰好適合頸部。 而船是在瓶子外組裝的。 但這不僅僅是組裝一艘船,而是真正的珠寶工藝品。 組件上添加了特殊的槓桿,然後可以將它們抬起。 例如,將帆折疊起來,小心地放入內部,然後在鑷子的幫助下,非常精確地拉動和升起它們。 結果是一件可以問心無愧和自豪地贈送的藝術品。
如果我們希望專案成功,團隊中必須至少有一名珠寶商。 關心產品品質並考慮所有方面的人,即使在有壓力的時刻,當情況需要以犧牲重要為代價來處理緊急情況時,也不犧牲任何東西。 所有可持續的、經受時間考驗的成功項目都是建立在這項原則之上的。 它們有一些非常精確和獨特的東西,可以利用所有可用的可能性。 在瓶中船的例子中,船體穿過頸部的事實得到了體現。
回到選擇快取伺服器的任務,如何應用此方法? 我提供了從所有現有系統中進行選擇的選項 - 不要搖晃瓶子,不要選擇,而是看看它們原則上有什麼,選擇系統時要尋找什麼。
去哪裡找瓶頸
讓我們盡量不要搖晃瓶子,不要一一檢查那裡的所有東西,但讓我們看看如果我們突然為了我們的任務,自己設計這樣一個系統,會出現什麼問題。 當然,我們不會組裝自行車,但我們會用這張圖來幫助我們弄清楚產品描述中需要注意的要點。 讓我們畫出這樣一個圖。
如果系統是分散式的,那麼我們將有幾台伺服器(6)。 假設有四個(將它們放置在圖片中很方便,但當然,可以有任意多個)。 如果伺服器位於不同的節點上,則表示它們都運行一些程式碼,負責確保這些節點形成一個集群,並在發生故障時相互連接和識別。
我們還需要程式碼邏輯(2),它實際上是關於快取的。 客戶端透過一些 API 與此程式碼互動。 客戶端程式碼 (1) 可以位於同一 JVM 中,也可以透過網路存取它。 內部實現的邏輯是決定哪些物件留在快取中以及哪些物件被丟棄。 我們使用記憶體(3)來儲存緩存,但如果有必要,我們可以將一些資料保存在磁碟(4)上。
讓我們看看負載會發生在哪些部分。 實際上,每個箭頭和每個節點都會被載入。 首先,在客戶端程式碼和api之間,如果這是網路通信,那麼下沉會非常明顯。 其次,在 api 本身的框架內 - 如果我們過度使用複雜的邏輯,我們可能會遇到 CPU 問題。 如果邏輯不把時間浪費在記憶上就好了。 並且仍然存在與檔案系統的交互 - 在通常的版本中,這是序列化/恢復和寫入/讀取。
接下來是與叢集的交互。 最有可能的是,它會在同一個系統中,但也可能是單獨的。 這裡還需要考慮向其傳輸資料、資料序列化的速度以及叢集之間的交互作用。
現在,一方面,我們可以想像在處理程式碼請求時快取系統中“哪些齒輪會轉動”,另一方面,我們可以估計我們的程式碼將為該系統產生哪些請求以及多少個請求。 這足以做出一個或多或少清醒的選擇——為我們的用例選擇一個系統。
淡褐色
讓我們看看如何將這種分解應用到我們的清單中。 例如,黑澤爾卡斯特。
為了從 Hazelcast 放入/取得數據,客戶端程式碼存取 (1) api。 Hz 允許您將伺服器作為嵌入式運行,在這種情況下,存取 api 是 JVM 內部的方法調用,可以認為是免費的。
為了使(2)中的邏輯起作用,Hz 依賴序列化密鑰的位元組數組的雜湊值 - 也就是說,密鑰在任何情況下都將被序列化。 這對於 Hz 來說是不可避免的開銷。
驅逐策略實施得很好,但對於特殊情況,您可以添加自己的策略。 您不必擔心這部分。
可連接儲存裝置 (4)。 偉大的。 嵌入式互動(5)可以被認為是即時的。 叢集中節點之間的資料交換 (6) - 是的,它存在。 這是以犧牲速度為代價的容錯投資。 Hz 功能近快取可讓您降低價格 - 從叢集中其他節點接收的資料將被快取。
在這種情況下可以採取什麼措施來提高速度?
例如,為了避免 (2) 中的鍵序列化 - 在 Hazelcast 之上附加另一個緩存,以儲存最熱的資料。 Sportmaster 為此選擇了咖啡因。
對於等級 (6) 的扭曲,Hz 提供兩種類型的儲存:IMap 和 ReplicatedMap。
值得一提的是 Hazelcast 是如何進入 Sportmaster 技術堆疊的。
2012 年,當我們致力於未來網站的第一個試點時,Hazelcast 成為搜尋引擎返回的第一個連結。 這次相識是「第一次」——我們被這樣的事實所吸引:僅僅兩個小時後,當我們將 Hz 擰入系統時,它就起作用了。 而且效果很好。 到一天結束時,我們已經完成了多項測試,並且很高興。 而這種儲備的活力足以克服赫茲隨著時間的推移所帶來的驚喜。 現在Sportmaster團隊沒有理由放棄Hazelcast。
但「搜尋引擎中的第一個連結」和「HelloWorld 很快就組裝好了」之類的論點當然是一個例外,也是選擇發生時刻的一個特徵。 對所選系統的真正測試從發佈到生產環境開始,在這個階段,您在選擇任何系統(包括快取)時都應該注意。 事實上,在我們的例子中,我們可以說我們選擇了 Hazelcast 是偶然的,但後來證明我們的選擇是正確的。
對於生產來說,更重要的是:監控、處理單一節點上的故障、資料複製、擴展成本。 也就是說,值得關注系統維護期間出現的任務 - 當負載比計劃高數十倍時,當我們不小心將某些內容上傳到錯誤的位置時,當我們需要推出新版本時代碼、替換資料並且在客戶不注意的情況下進行。
對於所有這些要求,Hazelcast 確實能夠滿足要求。
To be continued
但 Hazelcast 並不是萬靈丹。 2017 年,我們選擇 Hazelcast 作為管理緩存,只是基於過去經驗的良好印象。 這在一個非常殘酷的笑話中發揮了關鍵作用,因此我們發現自己陷入了困境,並在 60 天的時間裡「英勇地」擺脫了困境。 但下一部分將對此進行更多介紹。
同時...新程式碼快樂!
來源: www.habr.com