有關 VKontakte 架構和工作的常見問題解答

VKontakte 創建的歷史可以在維基百科上找到;這是 Pavel 自己講述的。 看來大家都已經認識她了。 關於 HighLoad++ Pavel 網站的內部、架構和結構 早在2010年就告訴我。 從那時起,許多伺服器都已洩漏,因此我們將更新資訊:我們將對其進行解剖,取出內部,稱重,並從技術角度審視 VK 設備。

有關 VKontakte 架構和工作的常見問題解答

阿列克謝·阿庫洛維奇 (阿特卡圖斯)VKontakte 團隊的後端開發人員。 本報告的文字記錄是對有關平台、基礎設施、伺服器以及它們之間互動的常見問題的集體回答,但不是關於開發的,即 關於鐵。 另外,關於資料庫和 VK 所擁有的內容,關於收集日誌和監控整個專案。 細節下切。



四年多來,我一直在處理與後端相關的各種任務。

  • 上傳、儲存、處理、分發媒體:影片、直播、音訊、照片、文件。
  • 基礎架構、平台、開發者監控、日誌、區域快取、CDN、專有 RPC 協定。
  • 與外部服務整合:推播通知、外部連結解析、RSS feed。
  • 幫助同事解決各種問題,而這些問題的答案需要深入研究未知的程式碼。

在此期間,我參與了該網站的許多組件。 我想分享這個經驗。

通用架構

像往常一樣,一切都從接受請求的伺服器或伺服器群組開始。

前台伺服器

前端伺服器透過 HTTPS、RTMP 和 WSS 接受請求。

HTTPS - 這些是對網站的主網路版本和行動網路版本的請求:vk.com 和 m.vk.com,以及我們 API 的其他官方和非官方客戶端:行動用戶端、即時通訊工具。 我們有接待處 RTMP- 具有獨立前端伺服器的直播流量和 WSS- 流 API 的連接。

對於伺服器上的 HTTPS 和 WSS 來說,這是值得的 nginx的。 對於 RTMP 廣播,我們最近改用了我們自己的解決方案 基韋,但這超出了報告的範圍。 為了容錯,這些伺服器通告公用 IP 位址並分組執行,這樣如果其中一台伺服器出現問題,使用者請求就不會遺失。 對於 HTTPS 和 WSS,這些相同的伺服器會對流量進行加密,以便自行承擔部分 CPU 負載。

我們不會進一步討論 WSS 和 RTMP,而只討論標準的 HTTPS 請求,這些請求通常與 Web 專案相關。

後端

前端後面通常有後端伺服器。 它們處理前端伺服器從客戶端收到的請求。

kPHP 伺服器,HTTP 守護程序正在其上運行,因為 HTTPS 已解密。 kPHP 是一個運行在 預叉型號:啟動一個主進程,一堆子進程,將偵聽套接字傳遞給它們,然後它們處理它們的請求。 在這種情況下,進程不會在使用者的每個請求之間重新啟動,而只是將其狀態重置為原始零值狀態 - 一個又一個請求,而不是重新啟動。

負載分配

我們所有的後端都不是一個可以處理任何請求的巨大機器池。 我們他們 分成不同的組:一般、移動、api、視訊、staging...單獨一組機器上的問題不會影響所有其他機器。 如果影片出現問題,聽音樂的用戶甚至不會知道問題所在。 將請求傳送到哪個後端由前端的nginx根據配置決定。

指標收集和重新平衡

為了了解每組需要有多少輛車,我們 不依賴QPS。 後端不同,它們有不同的請求,每個請求計算QPS的複雜度不同。 這就是為什麼我們 我們將伺服器負載的概念作為一個整體來操作 - CPU 和效能.

我們有數千台這樣的伺服器。 每個實體伺服器運行一個kPHP組來回收所有核心(因為kPHP是單執行緒的)。

內容服務器

CS或Content Server是一個存儲。 CS 是一個伺服器,用於儲存檔案並處理上傳的檔案以及主 Web 前端分配給它的各種後台同步任務。

我們有數以萬計的實體伺服器來儲存檔案。 用戶喜歡上傳文件,我們喜歡儲存和共享它們。 其中一些伺服器被特殊的 pu/pp 伺服器關閉。

聚氨酯/聚丙烯

如果您在 VK 中開啟網路選項卡,您會看到 pu/pp。

有關 VKontakte 架構和工作的常見問題解答

什麼是pu/pp? 如果我們關閉一台又一台伺服器,則有兩個選項可以將檔案上傳和下載到關閉的伺服器: 直接地 通過 http://cs100500.userapi.com/path透過中間伺服器 - http://pu.vk.com/c100500/path.

Pu是照片上傳的歷史名稱,pp是照片代理。 即一台伺服器用於上傳照片,另一台伺服器用於上傳。 現在不僅加載了照片,而且還保留了名稱。

這些伺服器 終止 HTTPS 會話從儲存中刪除處理器負載。 此外,由於使用者檔案是在這些伺服器上處理的,因此這些電腦上儲存的敏感資訊越少越好。 例如,HTTPS 加密金鑰。

由於這些機器被我們的其他機器關閉,我們可以不給它們“白色”外部IP,並且 給“灰色”。 透過這種方式,我們節省了 IP 池並保證保護機器免受外部存取 - 根本沒有 IP 可以進入其中。

共享 IP 的彈性。 在容錯方面,該方案的工作原理是相同的——幾台實體伺服器有一個公共的實體IP,它們前面的硬體選擇將請求發送到哪裡。 稍後我會討論其他選項。

爭議點在於,在本案中 客戶端保持較少的連接。 如果多台機器有相同的 IP - 具有相同的主機:pu.vk.com 或 pp.vk.com,則用戶端瀏覽器對一台主機的同時請求數量有限制。 但在 HTTP/2 無所不在的時代,我相信這不再那麼重要了。

該方案的明顯缺點是它必須 泵送所有流量,透過另一台伺服器進入儲存。 由於我們透過機器泵送流量,因此我們也無法使用相同的方案泵送大量流量,例如視訊。 我們直接傳輸它 - 專門用於視頻的單獨存儲的單獨直接連接。 我們透過代理傳輸較輕的內容。

不久前我們得到了 proxy 的改進版本。 現在我將告訴您它們與普通的有何不同以及為什麼這是必要的。

週日

2017年XNUMX月,先前收購了Sun的Oracle, Sun解雇了大量員工。 可以說,此時該公司已不復存在。 在為新系統選擇名稱時,我們的管理員決定向這家公司致敬,並將新系統命名為Sun。 在我們內部,我們簡稱她為「太陽」。

有關 VKontakte 架構和工作的常見問題解答

pp 有一些問題。 每組一個 IP - 快取無效。 多台實體伺服器共用一個公用IP位址,無法控制請求將傳送到哪台伺服器。 因此,如果不同的用戶訪問同一個文件,那麼如果這些伺服器上有緩存,則該文件最終會出現在每個伺服器的快取中。 這是一個非常低效率的方案,但卻無能為力。

最後 - 我們無法分割內容,因為我們無法為該群組選擇特定的伺服器 - 他們有一個共同的 IP。 另外,由於一些內部原因,我們有 不可能在地區安裝此類伺服器。 他們只站在聖彼得堡。

對於太陽隊,我們改變了選拔制度。 現在我們有 選播路由:動態路由、任播、自我檢測守護程式。 每台伺服器都有自己的獨立 IP,但有一個公共子網路。 一切都以這樣的方式配置:如果一台伺服器發生故障,流量會自動分散到同一組的其他伺服器上。 現在可以選擇特定的伺服器, 無冗餘緩存,且可靠性沒有受到影響。

重量支撐。 現在我們可以根據需要安裝不同功率的機器,而且,一旦出現臨時問題,可以改變正在工作的「太陽」的重量,以減輕它們的負載,讓它們「休息」並重新開始工作。

按內容 id 分片。 關於分片的一個有趣的事情是:我們通常會對內容進行分片,以便不同的用戶透過同一個「sun」存取同一個文件,這樣他們就有一個共同的快取。

我們最近推出了「Clover」應用程式。 這是直播中的線上問答,主持人提出問題,用戶即時回答,選擇選項。 該應用程式有一個聊天功能,用戶可以在其中聊天。 可同時連接廣播 超過100萬人。 他們都會編寫發送給所有參與者的訊息,頭像會隨訊息一起出現。 如果十萬人來追求一個「太陽」中的一個化身,那麼它有時可以滾到雲端。

為了承受對同一文件的突發請求,我們針對某種類型的內容啟用了一種愚蠢的方案,將文件分佈在該地區所有可用的「太陽」上。

陽光從裡面來

nginx 上的反向代理,快取在 RAM 或快速 Optane/NVMe 磁碟上。 例子: http://sun4-2.userapi.com/c100500/path — 指向“太陽”的鏈接,該鏈接位於第四個區域,第二個伺服器群組。 它關閉物理上位於伺服器 100500 上的路徑檔案。

緩存

我們在我們的架構方案中新增了一個節點—快取環境。

有關 VKontakte 架構和工作的常見問題解答

下面是佈局圖 區域緩存,大約有20個。 這些是緩存和“太陽”所在的地方,它們可以透過自身緩存流量。

有關 VKontakte 架構和工作的常見問題解答

這是多媒體內容的快取;這裡不儲存任何用戶資料——僅儲存音樂、影片、照片。

為了確定使用者所在的區域,我們 我們收集各地區公佈的 BGP 網路前綴。 在回退的情況下,如果我們無法透過前綴找到 IP,我們還必須解析 geoip 資料庫。 我們透過用戶的IP來確定區域。 在程式碼中,我們可以查看使用者的一個或多個區域 - 這些點是他在地理位置上最接近的點。

它是如何工作的呢?

我們按地區統計文件的受歡迎程度。 有多個用戶所在的區域快取和一個檔案標識符 - 我們採用這一對並在每次下載時增加評級。

與此同時,惡魔——區域中的服務——時不時地來到 API 中並說:“我是某某緩存,給我一份我所在區域中尚未出現的最流行文件的列表。 ” API 提供一堆按評級排序的文件,守護程序下載它們,將它們帶到區域並從那裡傳送文件。 這是 pu/pp 和 Sun 與快取之間的根本區別:它們立即透過自身提供文件,即使該文件不在快取中,並且快取首先將文件下載到自身,然後開始將其返回。

在這種情況下我們得到 內容更貼近用戶 並分散網路負載。 例如,僅在莫斯科快取中,我們在高峰時段分發的資料量就超過 1 Tbit/s。

但也有問題—— 快取伺服器不是橡膠。 對於超級流行的內容,有時沒有足夠的網路來容納單獨的伺服器。 我們的快取伺服器是 40-50 Gbit/s,但有些內容完全阻塞了這樣的通道。 我們正在努力實現該地區流行文件的多個副本的儲存。 我希望我們能在今年年底前實施它。

我們研究了總體架構。

  • 接受請求的前端伺服器。
  • 處理請求的後端。
  • 由兩種類型的代理關閉的儲存。
  • 區域緩存。

這張圖缺少什麼? 當然是我們儲存資料的資料庫。

資料庫或引擎

我們稱它們不是資料庫,而是引擎——引擎,因為我們實際上沒有普遍接受的意義上的資料庫。

有關 VKontakte 架構和工作的常見問題解答

這是必要措施。 發生這種情況是因為在 2008-2009 年,當 VK 流行度爆發式增長時,該專案完全運行在 MySQL 和 Memcache 上,並且出現了問題。 MySQL 喜歡崩潰和損壞文件,之後就無法恢復,而 Memcache 的效能逐漸下降,必須重新啟動。

事實證明,這個日益流行的項目有持久存儲,會損壞數據,還有緩存,會減慢速度。 在這種情況下,很難開發一個不斷成長的專案。 我們決定嘗試重寫該項目在我們自己的自行車上關注的關鍵問題。

解決成功了。 這樣做是有機會的,也是極為必要的,因為當時不存在其他擴展方式。 那時還沒有一堆資料庫,NoSQL 還不存在,只有 MySQL、Memcache、PostrgreSQL——僅此而已。

通用操作。 開發由我們的 C 開發團隊領導,一切都以一致的方式完成。 無論使用何種引擎,它們都具有大致相同的寫入磁碟的檔案格式、相同的啟動參數、以相同的方式處理訊號,並且在邊緣情況和問題的情況下表現大致相同。 隨著引擎的成長,管理員作業系統變得方便——沒有需要維護的動物園,每個新的第三方資料庫都需要重新學習如何操作,這使得快速、方便的增加成為可能他們的號碼。

引擎類型

該團隊編寫了相當多的引擎。 這裡只是其中的一些:朋友、提示、圖像、ipdb、信件、列表、日誌、memcached、meowdb、新聞、nostradamus、照片、播放列表、pmemcached、沙箱、搜索、存儲、喜歡、任務…

對於每個需要特定資料結構或處理非典型請求的任務,C 團隊都會編寫一個新引擎。 為什麼不。

我們有一個單獨的引擎 memcached的,它與普通的類似,但有很多好東西,並且不會減慢速度。 不是 ClickHouse,但它也可以工作。 單獨提供 記憶體快取 - 持久化記憶體緩存,它還可以將資料儲存在磁碟上,而且不適合儲存在RAM中,以免重新啟動時遺失資料。 對於各個任務有不同的引擎:佇列、清單、集合——我們的專案所需的一切。

集群

從程式碼的角度來看,無需將引擎或資料庫視為進程、實體或實例。 該程式碼專門適用於集群和引擎組 - 每個簇一種類型。 假設有一個 memcached 叢集 - 它只是一組機器。

程式碼根本不需要知道伺服器的實體位置、大小或數量。 他使用特定的標識符進入集群。

為此,您需要在程式碼和引擎之間添加一個實體 - 代理.

RPC代理

代理人 連接巴士,幾乎整個網站都在其上運行。 同時我們有 沒有服務發現 — 相反,該代理有一個配置,它知道所有集群和該集群的所有分片的位置。 這就是管理員所做的。

程式設計師根本不關心花費多少、在哪裡以及花費什麼——他們只關心集群。 這讓我們有很多。 當接收到請求時,代理會重定向請求,知道在哪裡 - 它自己決定這一點。

有關 VKontakte 架構和工作的常見問題解答

在這種情況下,代理程式是防止服務故障的保護點。 如果某個引擎變慢或崩潰,則代理程式會理解這一點並向客戶端做出相應的回應。 這允許您刪除超時 - 程式碼不會等待引擎回應,而是了解它不工作並且需要以某種不同的方式表現。 必須為資料庫並不總是有效的事實準備程式碼。

具體實現

有時我們仍然非常希望有某種非標準的解決方案作為引擎。 同時,決定不使用專門為我們的引擎創建的現成的 rpc-proxy,而是為該任務創建一個單獨的代理。

對於 MySQL(我們仍然到處都有),我們使用 db-proxy,對於 ClickHouse - 小貓屋.

它的工作原理一般是這樣的。 有一個特定的伺服器,它運行 kPHP、Go、Python - 一般來說,任何可以使用我們的 RPC 協定的程式碼。 該程式碼在 RPC 代理程式上本地運行 - 程式碼所在的每個伺服器都運行其自己的本機代理程式。 根據請求,代理知道要去哪裡。

有關 VKontakte 架構和工作的常見問題解答

如果一個引擎想要到另一個引擎,即使它是鄰居,也會透過代理,因為鄰居可能在另一個資料中心。 引擎不應依賴了解除自身以外的任何物體的位置——這是我們的標準解決方案。 但當然也有例外:)

所有引擎均按照 TL 方案運行的範例。

memcache.not_found                                = memcache.Value;
memcache.strvalue	value:string flags:int = memcache.Value;
memcache.addOrIncr key:string flags:int delay:int value:long = memcache.Value;

tasks.task
    fields_mask:#
    flags:int
    tag:%(Vector int)
    data:string
    id:fields_mask.0?long
    retries:fields_mask.1?int
    scheduled_time:fields_mask.2?int
    deadline:fields_mask.3?int
    = tasks.Task;
 
tasks.addTask type_name:string queue_id:%(Vector int) task:%tasks.Task = Long;

這是一個二進制協議,最接近的類似物是 原型緩衝區。 此模式預先描述了可選欄位、複雜類型 - 內建標量的擴充和查詢。 一切都按照這個協議進行。

基於 TCP/UDP 的 TL 上的 RPC...UDP?

我們有一個 RPC 協議,用於執行在 TL 方案之上運行的引擎請求。 這一切都透過 TCP/UDP 連線進行。 TCP很好理解,但是為什麼我們常常需要UDP呢?

UDP 有幫助 避免伺服器之間大量連線的問題。 如果每台伺服器都有一個 RPC 代理,並且一般情況下它可以到任何引擎,那麼每台伺服器就有數以萬計的 TCP 連線。 有負載,但是沒用。 在UDP 的情況下,這個問題不存在。

無冗餘 TCP 握手。 這是一個典型的問題:當啟動新引擎或新伺服器時,會立即建立許多 TCP 連線。 對於小型輕量級請求,例如 UDP 有效負載,代碼和引擎之間的所有通訊都是 兩個UDP資料包: 一隻朝一個方向飛,第二隻朝另一個方向飛。 一次往返 - 代碼在沒有握手的情況下收到了引擎的回應。

是的,一切正常 丟包率非常低。 該協定支援重傳和超時,但是如果我們丟失很多,我們將得到幾乎 TCP,這並不是有利的。 我們不會跨洋推動 UDP。

我們有幾千台這樣的伺服器,方案是一樣的:每台實體伺服器上都安裝一組引擎。 它們大多是單線程的,以便盡可能快地運行而不會阻塞,並作為單線程解決方案進行分片。 同時,我們沒有什麼比這些引擎更可靠的了,並且非常關注持久性資料儲存。

持久性資料存儲

引擎寫入二進位日誌。 binlog 是一個文件,在其末尾添加了狀態或資料更改的事件。 在不同的解決方案中它的呼叫方式不同:二進位日誌, WAL, AOF,但原理是一樣的。

為了防止引擎在重新啟動時多年重新讀取整個binlog,引擎會寫入 快照 - 目前狀態。 如果有必要,他們會先讀取它,然後再從 binlog 讀取完畢。 所有二進位日誌都根據 TL 方案以相同的二進位格式編寫,以便管理員可以使用他們的工具平等地管理它們。 不需要快照。 有一個通用標頭,指示誰的快照是 int、引擎的魔力以及哪個主體對任何人都不重要。 這是記錄快照的引擎的問題。

我將快速描述操作原理。 有一台運作引擎的伺服器。 他打開一個新的空二進制日誌進行寫入,並寫入一個更改事件。

有關 VKontakte 架構和工作的常見問題解答

在某個時刻,他要么決定自己拍攝快照,要么收到信號。 伺服器建立一個新文件,將其整個狀態寫入其中,將目前的 binlog 大小(偏移量)附加到文件末尾,然後繼續進一步寫入。 不會建立新的 binlog。

有關 VKontakte 架構和工作的常見問題解答

在某個時刻,當引擎重新啟動時,磁碟上將會同時存在 binlog 和快照。 引擎讀取整個快照並在某個點提升其狀態。

有關 VKontakte 架構和工作的常見問題解答

讀取建立快照時的位置以及 binlog 的大小。

有關 VKontakte 架構和工作的常見問題解答

讀取 binlog 的末尾以獲取當前狀態並繼續寫入更多事件。 這是一個簡單的方案;我們所有的引擎都按照它工作。

資料複製

因此,我們的資料複製 基於語句的 — 我們在 binlog 中寫入的不是任何頁面更改,而是 變更請求。 與網路上的非常相似,僅略有修改。

相同的方案不僅用於複製,還用於 建立備份。 我們有一個引擎──一個寫入binlog的寫入主機。 在管理員設定的任何其他地方,都會複製此二進位日誌,就是這樣 - 我們有一個備份。

有關 VKontakte 架構和工作的常見問題解答

如果需要的話 閱讀副本為了減少CPU的讀取負載,只需啟動讀取引擎,讀取binlog的末尾並在本地執行這些命令。

這裡的滯後非常小,並且可以找出副本落後主伺服器多少。

RPC代理程式中的資料分片

分片是如何運作的? 代理如何了解要傳送到哪個集群分片? 代碼並沒有說:“發送 15 個分片!” - 不,這是由代理完成的。

最簡單的方案是firstint — 請求中的第一個數字。

get(photo100_500) => 100 % N.

這是一個簡單的記憶體快取文字協定的範例,但是,當然,查詢可以是複雜的和結構化的。 此範例採用查詢中的第一個數字以及除以簇大小後的餘數。

當我們想要擁有單一實體的資料局部性時,這非常有用。 假設 100 是一個使用者或群組 ID,我們希望一個實體的所有資料都位於一個分片上以進行複雜查詢。

如果我們不關心請求如何在叢集中傳播,還有另一種選擇 - 散列整個分片.

hash(photo100_500) => 3539886280 % N

我們也獲得哈希值、除法的餘數和分片編號。

只有當我們準備好這樣一個事實時,這兩個選項才有效:當我們增加叢集的大小時,我們會將其拆分或增加數倍。 例如,我們有 16 個分片,我們還不夠,我們想要更多 - 我們可以安全地獲得 32 個分片而無需停機。 如果我們不想增加倍數,就會出現停機,因為我們無法在沒有損失的情況下準確地分割所有內容。 這些選項很有用,但並不總是有用。

如果我們需要新增或刪除任意數量的伺服器,我們使用 像 Ketama 一樣在環上進行一致的哈希處理。 但同時,我們完全失去了資料的局部性;我們必須將請求合併到集群,以便每個部分都返回自己的小回應,然後將回應合併到代理。

有非常具體的要求。 看起來像這樣:RPC代理接收請求,確定要去哪個群集並確定分片。 然後,要么有寫入主節點,要么,如果叢集有副本支持,則它會按需發送到副本。 代理完成這一切。

有關 VKontakte 架構和工作的常見問題解答

日誌

我們以多種方式寫入日誌。 最明顯和簡單的一個是 將日誌寫入記憶體緩存.

ring-buffer: prefix.idx = line

有一個關鍵前綴 - 日誌的名稱,一行,還有該日誌的大小 - 行數。 我們從 0 到行數減 1 之間取一個隨機數。memcache 中的鍵是與該隨機數連接的前綴。 我們將日誌行和當前時間保存到該值中。

當需要讀取日誌時,我們執行 多獲取 所有key,按時間排序,從而即時取得生產日誌。 當您需要即時偵錯生產中的某些內容時,可以使用該方案,而不會破壞任何內容,也不會停止或允許到其他機器的流量,但該日誌不會持續很長時間。

為了可靠地儲存日誌,我們有一個引擎 日誌引擎。 這正是它被創建並在大量集群中廣泛使用的原因。 據我所知,最大的叢集可儲存 600 TB 的打包日誌。

引擎很舊,有些集群已經有6-7年的歷史了。 它有一些問題我們正在努力解決,例如我們開始積極使用ClickHouse來儲存日誌。

ClickHouse中收集日誌

這張圖顯示了我們如何走進我們的引擎。

有關 VKontakte 架構和工作的常見問題解答

有一些程式碼透過 RPC 在本地發送到 RPC 代理,並且它了解去往引擎的位置。 如果我們想要在ClickHouse中寫入日誌,我們需要改變這個方案中的兩個部分:

  • 用ClickHouse取代一些引擎;
  • 將無法存取 ClickHouse 的 RPC 代理程式替換為可以透過 RPC 存取的解決方案。

引擎很簡單 - 我們將其替換為 ClickHouse 的伺服器或伺服器叢集。

為了去 ClickHouse,我們做了 小貓屋。 如果我們直接從KittenHouse轉到ClickHouse,它就應付不了了。 即使沒有請求,它也是由大量機器的 HTTP 連線累積起來的。 為了使該方案發揮作用,請在具有 ClickHouse 的伺服器上 引發本地反向代理,其編寫方式使其能夠承受所需的連接量。 它還可以相對可靠地在自身內部緩衝資料。

有關 VKontakte 架構和工作的常見問題解答

有時我們不想在非標準解決方案中實現RPC方案,例如在nginx中。 因此,KittenHouse具有透過UDP接收日誌的能力。

有關 VKontakte 架構和工作的常見問題解答

如果日誌的傳送者和接收者在同一台機器上工作,則在本機主機內遺失 UDP 封包的可能性非常低。 作為在第三方解決方案中實現RPC的需要和可靠性之間的折衷,我們簡單地使用UDP發送。 稍後我們將回到這個方案。

監控

我們有兩種類型的日誌:管理員在其伺服器上收集的日誌和開發人員透過程式碼編寫的日誌。 它們對應於兩種類型的指標: 系統和產品.

系統指標

它適用於我們所有的伺服器 網絡數據,它收集統計數據並將其發送到 石墨碳。 因此,ClickHouse 被用作儲存系統,而不是 Whisper 等。 如果需要,可以直接從ClickHouse讀取,或使用 格拉法納 用於指標、圖表和報告。 作為開發人員,我們有足夠的權限來存取 Netdata 和 Grafana。

產品指標

為了方便,我們寫了很多東西。 例如,有一組普通函數可讓您將 Counts、UniqueCounts 值寫入統計數據,然後將其傳送到更遠的地方。

statlogsCountEvent   ( ‘stat_name’,            $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid,    $key1, $key2, …)
statlogsValuetEvent  ( ‘stat_name’, $value, $key1, $key2, …)

$stats = statlogsStatData($params)

隨後,我們可以使用排序和分組過濾器,並從統計中執行我們想要的所有操作 - 建立圖表、配置看門狗。

我們寫得很 許多指標 每天的事件數量為 600 億到 1 兆個。 然而,我們想保留它們 至少幾年了解指標的趨勢。 把它們放在一起是一個我們尚未解決的大問題。 我會告訴你過去幾年它是如何運作的。

我們有編寫這些指標的函數 到本地記憶體緩存以減少條目數量。 曾在短時間內在本地推出 統計守護程式 收集所有記錄。 接下來,惡魔將指標合併到兩層伺服器中 日誌收集器,它匯總了我們一堆機器的統計數據,這樣它們後面的層就不會消失。

有關 VKontakte 架構和工作的常見問題解答

如果有必要,我們可以直接寫入日誌收集器。

有關 VKontakte 架構和工作的常見問題解答

但是繞過 stas-daemom 直接從程式碼寫入收集器是一個可擴展性很差的解決方案,因為它增加了收集器的負載。 只有當由於某種原因我們無法在電腦上啟動 memcache stats-daemon,或者它崩潰了而我們直接使用時,該解決方案才適用。

接下來,日誌收集器將統計資料合併到 喵DB - 這是我們的資料庫,它也可以儲存指標。

有關 VKontakte 架構和工作的常見問題解答

然後我們可以從程式碼中進行二進位“near-SQL”選擇。

有關 VKontakte 架構和工作的常見問題解答

實驗

2018 年夏天,我們舉辦了一次內部黑客馬拉松,想到了嘗試用可以在 ClickHouse 中儲存指標的東西來替換圖中的紅色部分。 我們在 ClickHouse 上有日誌 - 為什麼不嘗試一下呢?

有關 VKontakte 架構和工作的常見問題解答

我們有一個透過 KittenHouse 寫入日誌的方案。

有關 VKontakte 架構和工作的常見問題解答

我們決定 在圖表中新增另一個“*House”,它將按照我們的程式碼透過 UDP 寫入的格式準確接收指標。 然後這個 *House 將它們變成插入物,例如 KittenHouse 可以理解的原木。 他可以完美地將這些日誌傳遞給ClickHouse,ClickHouse應該可以讀取它們。

有關 VKontakte 架構和工作的常見問題解答

帶有memcache、stats-daemon 和logs-collectors 資料庫的方案被替換為這個方案。

有關 VKontakte 架構和工作的常見問題解答

帶有memcache、stats-daemon 和logs-collectors 資料庫的方案被替換為這個方案。

  • 這裡有一個來自程式碼的調度,它是在 StatsHouse 本地編寫的。
  • StatsHouse 將已轉換為 SQL 插入的 UDP 指標批量寫入 KittenHouse。
  • KittenHouse 將它們傳送到 ClickHouse。
  • 如果我們想讀取它們,那麼我們可以繞過 StatsHouse 來讀取它們 - 使用常規 SQL 直接從 ClickHouse 讀取它們。

還是 實驗,但我們喜歡它的結果。 如果我們解決了該方案的問題,那麼也許我們會完全轉向它。 就我個人而言,我希望如此。

駕駛 不節省鐵。 需要更少的伺服器,不需要本地統計守護程序和日誌收集器,但 ClickHouse 需要比目前方案更大的伺服器。 需要更少的伺服器,但它們必須更昂貴、更強大.

部署

首先,我們來看看PHP的部署。 我們正在發展於 混帳: 使用 GitLab и 團隊城市 用於部署。 開發分支合併到主分支,從主分支合併到測試分支,然後從測試分支合併到生產分支。

在部署之前,將取得目前生產分支和前一個生產分支,並在其中考慮差異檔案 - 變更:建立、刪除、變更。 此變更記錄在特殊的 Copyfast 引擎的 binlog 中,該引擎可以快速將變更複製到我們的整個伺服器群組。 這裡用的不是直接複製,而是 八卦複製,當一台伺服器將變更發送到其最近的鄰居時,將變更傳送到其鄰居時,依此類推。 這使您可以在數十秒內更新整個佇列的程式碼。 當更改到達本機副本時,它將這些補丁套用到其本地副本 本機檔案系統。 回滾也是按照同樣的方案進行。

我們也大量部署了 kPHP,而且它也有自己的開發 混帳 根據上圖。 從此 HTTP 伺服器二進位文件,那麼我們就無法產生 diff - 發布的二進位檔案有數百 MB。 因此,這裡還有另一個選擇——版本被寫入 二進位日誌快速複製。 隨著每次構建,它都會增加,並且在回滾期間它也會增加。 版本 複製到伺服器。 本地 copyfast 看到新版本已進入 binlog,並透過相同的八卦複製,他們為自己獲取最新版本的二進位文件,而不會讓我們的主伺服器感到疲倦,而是小心地在網路上分散負載。 接下來是什麼 優雅地重新啟動 對於新版本。

對於我們的引擎來說,本質上也是二進制的,該方案非常相似:

  • git master 分支;
  • 二進位輸入 德布。;
  • 該版本被寫入binlog copyfast;
  • 複製到伺服器;
  • 伺服器取出一個新的.dep;
  • dpkg-i;
  • 優雅地重新啟動到新版本。

不同之處在於我們的二進位檔案打包在檔案中 德布。,當抽出它們時 dpkg-i 被放置在系統上。 為什麼 kPHP 部署為二進位文件,而引擎部署為 dpkg? 事情就是這樣發生的。 它有效 - 不要碰它。

相關鏈接:

阿列克謝·阿庫洛維奇 (Alexey Akulovich) 是作為程序委員會的一員,幫助幫助 PHP 俄羅斯 17月XNUMX日將成為近期PHP開發者最盛大的盛會。 看看我們有一台多酷的電腦,什麼 揚聲器 (其中兩個正在開發 PHP 核心!) - 如果您編寫 PHP,這似乎是您不能錯過的東西。

來源: www.habr.com

添加評論