縮放播放! 數以千計的並發請求
已發表: 2022-03-11Scala Web 開發人員經常沒有考慮成千上萬的用戶同時訪問我們的應用程序的後果。 也許是因為我們喜歡快速製作原型; 也許是因為測試這樣的場景簡直太難了。
無論如何,我要爭辯說,忽略可伸縮性並不像聽起來那麼糟糕——如果您使用正確的工具集並遵循良好的開發實踐。
洛金哈和戲劇! 框架
前段時間,我開始了一個名為 Lojinha 的項目(在葡萄牙語中意為“小商店”),我嘗試建立一個拍賣網站。 (順便說一下,這個項目是開源的)。 我的動機如下:
- 我真的很想賣一些我不再使用的舊東西。
- 我不喜歡傳統的拍賣網站,尤其是我們在巴西的那些。
- 我想和 Play 一起“玩”! 框架 2(雙關語)。
很明顯,如上所述,我決定使用 Play! 框架。 我沒有準確計算構建需要多長時間,但肯定沒多久我的網站就啟動並運行了在 http://lojinha.jcranky.com 上部署的簡單系統。 實際上,我將至少一半的開發時間花在了使用 Twitter Bootstrap 的設計上(記住:我不是設計師……)。
上面的段落至少應該說明一件事:我並沒有太擔心性能,如果在創建 Lojinha 時完全擔心的話。
這正是我的觀點:使用正確的工具是有力量的——這些工具可以讓你走在正確的軌道上,這些工具可以鼓勵你通過構建來遵循最佳開發實踐。
在這種情況下,這些工具就是 Play! 框架和 Scala 語言,Akka 做了一些“客串”。
讓我告訴你我的意思。
不變性和緩存
人們普遍認為,最小化可變性是一種很好的做法。 簡而言之,可變性使您的代碼更難推理,尤其是當您嘗試引入任何並行性或併發性時。
表演! Scala 框架使您在大部分時間都使用不變性,Scala 語言本身也是如此。 例如,控制器生成的結果是不可變的。 有時您可能會認為這種不變性“煩人”或“煩人”,但這些“良好做法”之所以“好”是有原因的。
在這種情況下,當我最終決定運行一些性能測試時,控制器的不變性絕對是至關重要的:我發現了一個瓶頸,為了解決它,只需緩存這個不可變的響應。
通過緩存,我的意思是保存響應對象並按原樣為任何新客戶端提供相同的實例。 這使服務器不必重新計算結果。 如果此結果是可變的,則不可能為多個客戶端提供相同的響應。
缺點:在短時間內(緩存過期時間),客戶端可能會收到過時的信息。 這僅在您絕對需要客戶端訪問最新數據且不能容忍延遲的情況下才會出現問題。
作為參考,這裡是加載帶有產品列表的起始頁面的 Scala 代碼,沒有緩存:
def index = Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) }
現在,添加緩存:
def index = Cached("index", 5) { Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) } }
很簡單,不是嗎? 這裡, “index”是要在緩存系統中使用的鍵,5 是過期時間,以秒為單位。
為了測試此更改的效果,我在本地運行了一些 JMeter 測試(包含在 GitHub 存儲庫中)。 在添加緩存之前,我實現了每秒大約 180 個請求的吞吐量。 緩存後,吞吐量上升到每秒 800 個請求。 對於不到兩行代碼,這是一個超過4 倍的改進。

內存消耗
正確的 Scala 工具可以產生重大影響的另一個領域是內存消耗。 在這裡,再次播放! 將您推向正確(可擴展)的方向。 在 Java 世界中,對於使用 servlet API 編寫的“普通”Web 應用程序(即,幾乎所有的 Java 或 Scala 框架),在用戶會話中放置大量垃圾是非常誘人的,因為 API 提供了易於...調用允許您這樣做的方法:
session.setAttribute("attrName", attrValue);
因為向用戶會話添加信息非常容易,所以經常被濫用。 因此,無緣無故用盡過多內存的風險同樣高。
隨著遊戲! 框架,這不是一個選項——框架根本沒有服務器端會話空間。 表演! 框架用戶會話保存在瀏覽器 cookie 中,您必須忍受它。 這意味著會話空間的大小和類型受到限制:您只能存儲字符串。 如果您需要存儲對象,則必須使用我們之前討論過的緩存機制。 例如,您可能希望在會話中存儲當前用戶的電子郵件地址或用戶名,但如果您需要存儲域模型中的整個用戶對象,則必須使用緩存。
再一次,這乍一看似乎很痛苦,但實際上,玩! 讓您走在正確的軌道上,迫使您仔細考慮內存使用情況,這會產生實際上已為集群做好準備的首次通過代碼——特別是考慮到沒有必須在整個集群中傳播的服務器端會話,從而使生命無限輕鬆。
異步支持
本劇的下一個! 框架回顧,我們將研究如何玩! 在 async(hronous) 支持方面也很出色。 除了其原生功能之外,Play! 允許您嵌入 Akka,這是一個強大的異步處理工具。
Altough Lojinha 尚未充分利用 Akka,它與 Play 的簡單集成! 非常容易:
- 安排異步電子郵件服務。
- 同時處理各種產品的報價。
簡而言之,Akka 是 Erlang 著名的 Actor 模型的實現。 如果你不熟悉 Akka Actor Model,可以把它想像成一個只通過消息進行通信的小單元。
要異步發送電子郵件,我首先創建正確的消息和參與者。 然後,我需要做的就是:
EMail.actor ! BidToppedMessage(item.name, itemUrl, bidderEmail)
電子郵件發送邏輯在actor內部實現,消息告訴actor我們要發送哪封電子郵件。 這是在即發即棄的方案中完成的,這意味著上面的行發送請求,然後繼續執行我們之後擁有的任何內容(即,它不會阻塞)。
有關 Play! 的原生 Async 的更多信息,請查看官方文檔。
結論
總結:我快速開發了一個小型應用程序 Lojinha,它能夠很好地擴展和擴展。 當我遇到問題或發現瓶頸時,修復既快速又簡單,由於我使用的工具(Play!、Scala、Akka 等)而備受讚譽,這促使我在效率和可擴展性。 幾乎不關心性能,我能夠擴展到數千個並發請求。
在開發下一個應用程序時,請仔細考慮您的工具。