米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

米哈伊爾·薩洛辛(Mikhail Salosin,以下簡稱「MS」): - 大家好! 我的名字叫麥克。 我在 MC2 Software 擔任後端開發人員,我將討論在 Look+ 行動應用程式的後端中使用 Go。

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

這裡有人喜歡曲棍球嗎?

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

那麼這個應用程式適合您。 它適用於Android和iOS,用於線上觀看和錄製各種體育賽事的廣播。 該應用程式還包含各種統計數據、文字廣播、會議表格、錦標賽和其他對球迷有用的信息。

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

此外,該應用程式還提供視訊時刻,即您可以觀看比賽最重要的時刻(進球、戰鬥、點球大戰等)。 如果您不想觀看整個廣播,您可以只觀看最有趣的部分。

你在開發中使用了什麼?

主要部分是用 Go 寫的。 行動客戶端通訊的 API 是用 Go 編寫的。 也用 Go 編寫了向手機發送推播通知的服務。 我們還必須編寫自己的 ORM,有一天我們可能會討論它。 好吧,一些小服務是用 Go 編寫的:為編輯調整大小和載入圖像...

我們使用 PostgreSQL 作為資料庫。 編輯器介面是使用 ActiveAdmin gem 用 Ruby on Rails 編寫的。 從統計提供者匯入統計資料也是用 Ruby 寫的。

對於系統 API 測試,我們使用 Python 單元測試。 Memcached 用於限制 API 支付調用,「Chef」用於控製配置,Zabbix 用於收集和監控內部系統統計資料。 Graylog2用於收集日誌,Slate是為客戶端提供的API文件。

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

協議選擇

我們遇到的第一個問題:我們需要選擇後端和行動用戶端之間互動的協議,基於以下幾點...

  • 最重要的要求:客戶端資料必須即時更新。 也就是說,目前正在觀看廣播的每個人都應該幾乎立即收到更新。
  • 為了簡化事情,我們假設與客戶端同步的資料不會被刪除,而是使用特殊標誌隱藏。
  • 各種稀有請求(如統計、球隊組成、球隊統計等)都是透過普通的GET請求獲取的。
  • 另外,系統必須能夠輕鬆地同時支援 100 萬用戶。

基於此,我們有兩種協定選擇:

  1. 網路套接字。 但我們不需要從客戶端到伺服器的通道。 我們只需要將更新從伺服器傳送到客戶端,因此 websocket 是一個多餘的選項。
  2. 伺服器發送事件 (SSE) 出現得恰到好處! 非常簡單,基本上滿足了我們所有的需求。

服務器發送的事件

關於這個東西是如何運作的幾句話...

它運行在 http 連接之上。 客戶端發送請求,伺服器回應 Content-Type: text/event-stream 並且不關閉與客戶端的連接,而是繼續寫入資料到連接:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

資料可以按照與客戶商定的格式發送。 在我們的例子中,我們以這種形式發送它:更改的結構的名稱(人員,玩家)被發送到事件字段,並且帶有新的、更改的玩家字段的 JSON 被發送到資料字段。

現在我們來談談互動本身是如何運作的。

  • 客戶端要做的第一件事是確定上次執行與服務同步的時間:它查看其本機資料庫並確定其記錄的上次變更的日期。
  • 它發送帶有該日期的請求。
  • 作為回應,我們向他發送自該日期以來發生的所有更新。
  • 之後,它會與即時頻道建立連接,並且在需要這些更新之前不會關閉:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

我們向他發送一份更改清單:如果有人進球,我們會更改比賽的比分,如果他受傷,這也會即時發送。 因此,客戶可以立即收到比賽事件來源中的最新數據。 我們每隔 15 秒發送時間戳,讓客戶端知道伺服器沒有死機,沒有發生任何事情,這樣客戶端就知道一切正常,不需要重新連線。

即時連線是如何提供服務的?

  • 首先,我們建立一個接收緩衝更新的通道。
  • 之後,我們訂閱該頻道以接收更新。
  • 我們設定正確的標頭,以便客戶端知道一切正常。
  • 發送第一個 ping。 我們只是記錄當前連接時間戳。
  • 之後,我們循環讀取通道,直到更新通道關閉。 該通道定期接收當前時間戳記或我們已經寫入開啟連線的變更。

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

我們遇到的第一個問題如下:對於與客戶端打開的每個連接,我們創建了一個每15 秒計時一次的計時器- 事實證明,如果我們與一台機器(具有一個API 伺服器)打開6個連接,則 6創建了數千個計時器。 這導致機器無法承受所需的負載。 這個問題對我們來說並不那麼明顯,但我們得到了一些幫助並解決了它。

因此,現在我們的 ping 來自更新的相同通道。

因此,只有一個計時器每 15 秒滴答一次。

這裡有幾個輔助功能 - 發送標頭、ping 和結構本身。 也就是說,表的名稱(人物、比賽、季節)和有關此條目的資訊都傳輸在這裡:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

發送更新的機制

現在來談談這些變化從何而來。 我們有幾個人,編輯,即時觀看廣播。 他們創造了所有的事件:有人被罰下場、有人受傷、某種替代......

使用 CMS,資料進入資料庫。 此後,資料庫使用監聽/通知機制將此情況通知 API 伺服器。 API 伺服器已將此資訊傳送給客戶端。 因此,我們基本上只有少數伺服器連接到資料庫,並且資料庫上沒有特殊負載,因為客戶端不會以任何方式直接與資料庫互動:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

PostgreSQL:監聽/通知

Postgres 中的監聽/通知機制可讓您通知事件訂閱者某些事件已變更 - 資料庫中已建立某些記錄。 為此,我們編寫了一個簡單的觸發器和函數:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

當插入或更改記錄時,我們在 data_updates 通道上呼叫通知函數,向其中傳遞表的名稱以及已更改或插入的記錄的標識符。

對於必須與客戶端同步的所有表,我們定義一個觸發器,該觸發器在更改/更新記錄後調用下面幻燈片中所示的函數。
API 如何訂閱這些變更?

創建了扇出機制 - 它將訊息發送到客戶端。 它收集所有客戶管道並發送透過這些管道收到的更新:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

這裡,標準 pq 庫連接到資料庫並表示它想要監聽通道 (data_updates),檢查連接是否開啟並且一切正常。 我省略了錯誤檢查以節省空間(不檢查是危險的)。

接下來,我們非同步設定 Ticker,它將每 15 秒發送一次 ping,並開始監聽我們訂閱的頻道。 如果我們收到 ping,我們就會發布此 ping。 如果我們收到某種條目,那麼我們會將此條目發佈給該扇出的所有訂閱者。

扇出如何運作?

在俄語中,這翻譯為“分裂者”。 我們有一個物件來註冊想要接收一些更新的訂閱者。 一旦更新到達該對象,它就會將此更新分發給其所有訂閱者。 夠簡單:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

在 Go 中是如何實現的:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

有一個結構,它使用互斥體來同步。 它有一個字段保存Fanout與資料庫的連接狀態,即當前正在監聽並將接收更新,以及所有可用通道的列表-map,其中的關鍵是通道和結構體,其形式為值(本質上它不以任何方式使用)。

兩種方法 - Connected 和 Disconnected - 讓我們告訴 Fanout 我們與底座有連接,它已經出現,並且與底座的連接已斷開。 在第二種情況下,您需要斷開所有客戶端的連接,並告訴它們無法再偵聽任何內容,並且需要重新連接,因為與它們的連接已關閉。

還有一個 Subscribe 方法將頻道新增至「偵聽器」:

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

有一個 Unsubscribe 方法,如果客戶端斷開連接,它會從偵聽器中刪除通道;還有一個 Publish 方法,它允許您向所有訂閱者發送訊息。

問題: – 透過該通道傳輸什麼?

多發性硬化症: – 已更改的模型或 ping 被傳輸(本質上只是一個數字、整數)。

多發性硬化症: – 您可以發送任何內容,發送任何結構,然後發布它 – 它只是變成 JSON,僅此而已。

多發性硬化症: – 我們收到來自 Postgres 的通知 – 它包含表格名稱和識別碼。 根據表名和標識符,我們得到我們需要的記錄,然後我們發送這個結構進行發布。

基礎設施

從基礎設施的角度來看,這是什麼樣的? 我們有 7 台硬體伺服器:其中一台完全專用於資料庫,其他六台運行虛擬機器。 API 有 6 個副本:每個具有 API 的虛擬機器都運行在單獨的硬體伺服器上 - 這是為了可靠性。

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

我們有兩個安裝了 Keepalived 的前端,以提高可訪問性,這樣,如果發生問題,一個前端可以替換另一個前端。 另外 - CMS 的兩份。

還有一個統計導入器。 有一個 DB Slave,定期進行備份。 有 Pigeon Pusher,一個向客戶端發送推播通知的應用程序,以及基礎設施:Zabbix、Graylog2 和 Chef。

事實上,這個基礎設施是多餘的,因為用更少的伺服器就可以為 100 萬個服務提供服務。 但是有鐵 - 我們使用了它(我們被告知這是可能的 - 為什麼不呢)。

圍棋的優點

當我們做了這個應用程式之後,Go 的如此明顯的優勢就顯現出來了。

  • 很酷的 http 庫。 有了它,您可以創建很多開箱即用的東西。
  • 另外,通道使我們能夠非常輕鬆地實現向客戶端發送通知的機制。
  • 很棒的事情是,競賽偵測器使我們能夠消除幾個關鍵錯誤(暫存基礎設施)。 所有與舞台相關的內容都已啟動,並使用 Race 鍵進行編譯; 因此,我們可以查看臨時基礎設施,看看我們有哪些潛在問題。
  • 極簡主義和語言的簡單性。

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

我們正在尋找開發商! 如果有人想要,請。

問題

觀眾提問(以下簡稱 - B): - 在我看來,您錯過了有關扇出的一個重要觀點。 我的理解是否正確:當您向客戶端發送回應時,如果客戶端不想閱讀,您就會阻止?

多發性硬化症: - 不,我們沒有阻止。 首先,我們把這一切都放在nginx後面,也就是說,慢速客戶端不存在問題。 其次,客戶端有一個帶有緩衝區的通道 - 事實上,我們可以在那裡放置最多一百個更新......如果我們無法寫入通道,那麼它會刪除它。 如果我們發現通道被阻塞,那麼我們將簡單地關閉通道,僅此而已 - 如果出現任何問題,客戶端將重新連接。 因此,原則上這裡不存在阻塞。

在: – 是否可以立即向 Listen/Notify 發送記錄而不是標識符表?

多發性硬化症: – Listen/Notify 對其傳送的預先載入的限制為 8 位元組。 原則上,如果我們處理少量數據,則可以發送,但在我看來,這種方式[我們這樣做的方式]更可靠。 限制在於 Postgres 本身。

在: – 顧客是否會收到他們不感興趣的比賽的更新?

多發性硬化症: - 一般來說,是的。 一般來說,會有 2-3 場比賽同時進行,但即便如此,也很少見。 如果客戶正在觀看某些內容,那麼通常他正在觀看正在進行的比賽。 然後,客戶端有一個本地資料庫,所有這些更新都添加到其中,即使沒有互聯網連接,客戶端也可以查看他有更新的所有過去的比賽。 本質上,我們將伺服器上的資料庫與客戶端的本機資料庫同步,以便他可以離線工作。

在: – 為什麼要製作自己的 ORM?

Alexey(Look+ 的開發者之一): – 當時(一年前)ORM 比現在少,當時 ORM 數量相當多。 對於大多數 ORM,我最喜歡的一點是它們大多數都在空介面上運行。 也就是說,這些 ORM 中的方法可以接受任何東西:結構、結構指標、數字、完全不相關的東西...

我們的 ORM 是基於資料模型生成結構。 我。 因此,所有方法都是具體的,不使用反射等。它們接受結構並期望使用這些結構。

在: – 有多少人參加?

多發性硬化症: ——初期有兩個人參加。 我們從六月的某個地方開始,八月主要部分就準備好了(第一個版本)。 九月有一個版本。

在: – 在您描述 SSE 的地方,您沒有使用逾時。 這是為什麼?

多發性硬化症: – 老實說,SSE仍然是html5協定:據我了解,SSE標準是為了與瀏覽器通訊而設計的。 它具有附加功能,以便瀏覽器可以重新連接(等等),但我們不需要它們,因為我們有可以實現連接和接收資訊的任何邏輯的客戶端。 我們沒有做SSE,而是做類似SSE的東西。 這不是協議本身。
沒有必要。 據我了解,客戶端幾乎是從頭開始實現連線機制的。 他們並不真正關心。

在: – 您還使用了哪些其他實用程式?

多發性硬化症: – 我們最積極地使用 govet 和 golint 來統一風格,還有 gofmt。 沒有使用其他任何東西。

在: – 你用什麼來調試的?

多發性硬化症: – 調試主要透過測試進行。 我們沒有使用任何調試器或 GOP。

在: – 能否回傳實現發布功能的投影片? 單字母變數名會讓您感到困惑嗎?

多發性硬化症: - 不。 他們的可見範圍相當「狹窄」。 除了這裡之外,它們沒有在其他任何地方使用(除了這個類的內部),而且它非常緊湊——只需要 7 行。

在: – 不知怎的,它仍然不直觀...

多發性硬化症: - 不,不,這是真正的程式碼! 這與風格無關。 這就是一個非常實用的、非常小的類別——類別中只有 3 個欄位...

米哈伊爾·薩洛辛. Golang 聚會。 在 Look+ 應用程式的後端使用 Go

多發性硬化症: – 總的來說,與客戶端(賽季比賽、球員)同步的所有數據都不會改變。 粗略地說,如果我們製作另一項需要更改比賽的遊戲,我們只會在新版本客戶端中考慮所有因素,而舊版本客戶端將被禁止。

在: – 是否有第三方依賴管理套件?

多發性硬化症: – 我們使用 go dep。

在: – 報告的主題中有一些關於影片的內容,但報告中沒有任何關於影片的內容。

多發性硬化症: – 不,我沒有任何關於該影片的主題。 它被稱為“Look+”——這就是應用程式的名稱。

在: – 你說它是串流給客戶的?..

多發性硬化症: – 我們沒有參與串流影音。 這完全是由 Megafon 完成的。 是的,我沒有說該應用程式是 MegaFon。

多發性硬化症: – Go – 用於發送所有數據 – 關於比分、比賽事件、統計數據...Go 是應用程式的整個後端。 客戶端必須從某個地方知道玩家要使用哪個鏈接,以便用戶可以觀看比賽。 我們有已準備好的影片和串流的連結。

一些廣告🙂

感謝您與我們在一起。 你喜歡我們的文章嗎? 想看更多有趣的內容? 通過下訂單或推薦給朋友來支持我們, 面向開發人員的雲 VPS,4.99 美元起, 我們為您發明的入門級服務器的獨特模擬: VPS (KVM) E5-2697 v3(6 核)10​​4GB DDR480 1GB SSD 19Gbps XNUMX 美元或如何共享服務器的全部真相? (適用於 RAID1 和 RAID10,最多 24 個內核和最多 40GB DDR4)。

Dell R730xd 在阿姆斯特丹的 Equinix Tier IV 數據中心便宜 2 倍? 只有這裡 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 電視低至 199 美元 在荷蘭! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - 99 美元起! 閱讀 如何建設基礎設施公司同級使用價值730歐元的Dell R5xd E2650-4 v9000服務器一分錢?

來源: www.habr.com

添加評論