VKontakte 創建的歷史可以在維基百科上找到;這是 Pavel 自己講述的。 看來大家都已經認識她了。 關於 HighLoad++ Pavel 網站的內部、架構和結構
阿列克謝·阿庫洛維奇 (
四年多來,我一直在處理與後端相關的各種任務。
- 上傳、儲存、處理、分發媒體:影片、直播、音訊、照片、文件。
- 基礎架構、平台、開發者監控、日誌、區域快取、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。
什麼是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,
pp 有一些問題。 每組一個 IP - 快取無效。 多台實體伺服器共用一個公用IP位址,無法控制請求將傳送到哪台伺服器。 因此,如果不同的用戶訪問同一個文件,那麼如果這些伺服器上有緩存,則該文件最終會出現在每個伺服器的快取中。 這是一個非常低效率的方案,但卻無能為力。
最後 - 我們無法分割內容,因為我們無法為該群組選擇特定的伺服器 - 他們有一個共同的 IP。 另外,由於一些內部原因,我們有 不可能在地區安裝此類伺服器。 他們只站在聖彼得堡。
對於太陽隊,我們改變了選拔制度。 現在我們有 選播路由:動態路由、任播、自我檢測守護程式。 每台伺服器都有自己的獨立 IP,但有一個公共子網路。 一切都以這樣的方式配置:如果一台伺服器發生故障,流量會自動分散到同一組的其他伺服器上。 現在可以選擇特定的伺服器, 無冗餘緩存,且可靠性沒有受到影響。
重量支撐。 現在我們可以根據需要安裝不同功率的機器,而且,一旦出現臨時問題,可以改變正在工作的「太陽」的重量,以減輕它們的負載,讓它們「休息」並重新開始工作。
按內容 id 分片。 關於分片的一個有趣的事情是:我們通常會對內容進行分片,以便不同的用戶透過同一個「sun」存取同一個文件,這樣他們就有一個共同的快取。
我們最近推出了「Clover」應用程式。 這是直播中的線上問答,主持人提出問題,用戶即時回答,選擇選項。 該應用程式有一個聊天功能,用戶可以在其中聊天。 可同時連接廣播 超過100萬人。 他們都會編寫發送給所有參與者的訊息,頭像會隨訊息一起出現。 如果十萬人來追求一個「太陽」中的一個化身,那麼它有時可以滾到雲端。
為了承受對同一文件的突發請求,我們針對某種類型的內容啟用了一種愚蠢的方案,將文件分佈在該地區所有可用的「太陽」上。
陽光從裡面來
nginx 上的反向代理,快取在 RAM 或快速 Optane/NVMe 磁碟上。 例子: http://sun4-2.userapi.com/c100500/path
— 指向“太陽”的鏈接,該鏈接位於第四個區域,第二個伺服器群組。 它關閉物理上位於伺服器 100500 上的路徑檔案。
緩存
我們在我們的架構方案中新增了一個節點—快取環境。
下面是佈局圖 區域緩存,大約有20個。 這些是緩存和“太陽”所在的地方,它們可以透過自身緩存流量。
這是多媒體內容的快取;這裡不儲存任何用戶資料——僅儲存音樂、影片、照片。
為了確定使用者所在的區域,我們 我們收集各地區公佈的 BGP 網路前綴。 在回退的情況下,如果我們無法透過前綴找到 IP,我們還必須解析 geoip 資料庫。 我們透過用戶的IP來確定區域。 在程式碼中,我們可以查看使用者的一個或多個區域 - 這些點是他在地理位置上最接近的點。
它是如何工作的呢?
我們按地區統計文件的受歡迎程度。 有多個用戶所在的區域快取和一個檔案標識符 - 我們採用這一對並在每次下載時增加評級。
與此同時,惡魔——區域中的服務——時不時地來到 API 中並說:“我是某某緩存,給我一份我所在區域中尚未出現的最流行文件的列表。 ” API 提供一堆按評級排序的文件,守護程序下載它們,將它們帶到區域並從那裡傳送文件。 這是 pu/pp 和 Sun 與快取之間的根本區別:它們立即透過自身提供文件,即使該文件不在快取中,並且快取首先將文件下載到自身,然後開始將其返回。
在這種情況下我們得到 內容更貼近用戶 並分散網路負載。 例如,僅在莫斯科快取中,我們在高峰時段分發的資料量就超過 1 Tbit/s。
但也有問題—— 快取伺服器不是橡膠。 對於超級流行的內容,有時沒有足夠的網路來容納單獨的伺服器。 我們的快取伺服器是 40-50 Gbit/s,但有些內容完全阻塞了這樣的通道。 我們正在努力實現該地區流行文件的多個副本的儲存。 我希望我們能在今年年底前實施它。
我們研究了總體架構。
- 接受請求的前端伺服器。
- 處理請求的後端。
- 由兩種類型的代理關閉的儲存。
- 區域緩存。
這張圖缺少什麼? 當然是我們儲存資料的資料庫。
資料庫或引擎
我們稱它們不是資料庫,而是引擎——引擎,因為我們實際上沒有普遍接受的意義上的資料庫。
這是必要措施。 發生這種情況是因為在 2008-2009 年,當 VK 流行度爆發式增長時,該專案完全運行在 MySQL 和 Memcache 上,並且出現了問題。 MySQL 喜歡崩潰和損壞文件,之後就無法恢復,而 Memcache 的效能逐漸下降,必須重新啟動。
事實證明,這個日益流行的項目有持久存儲,會損壞數據,還有緩存,會減慢速度。 在這種情況下,很難開發一個不斷成長的專案。 我們決定嘗試重寫該項目在我們自己的自行車上關注的關鍵問題。
解決成功了。 這樣做是有機會的,也是極為必要的,因為當時不存在其他擴展方式。 那時還沒有一堆資料庫,NoSQL 還不存在,只有 MySQL、Memcache、PostrgreSQL——僅此而已。
通用操作。 開發由我們的 C 開發團隊領導,一切都以一致的方式完成。 無論使用何種引擎,它們都具有大致相同的寫入磁碟的檔案格式、相同的啟動參數、以相同的方式處理訊號,並且在邊緣情況和問題的情況下表現大致相同。 隨著引擎的成長,管理員作業系統變得方便——沒有需要維護的動物園,每個新的第三方資料庫都需要重新學習如何操作,這使得快速、方便的增加成為可能他們的號碼。
引擎類型
該團隊編寫了相當多的引擎。 這裡只是其中的一些:朋友、提示、圖像、ipdb、信件、列表、日誌、memcached、meowdb、新聞、nostradamus、照片、播放列表、pmemcached、沙箱、搜索、存儲、喜歡、任務…
對於每個需要特定資料結構或處理非典型請求的任務,C 團隊都會編寫一個新引擎。 為什麼不。
我們有一個單獨的引擎 memcached的,它與普通的類似,但有很多好東西,並且不會減慢速度。 不是 ClickHouse,但它也可以工作。 單獨提供 記憶體快取 - 持久化記憶體緩存,它還可以將資料儲存在磁碟上,而且不適合儲存在RAM中,以免重新啟動時遺失資料。 對於各個任務有不同的引擎:佇列、清單、集合——我們的專案所需的一切。
集群
從程式碼的角度來看,無需將引擎或資料庫視為進程、實體或實例。 該程式碼專門適用於集群和引擎組 - 每個簇一種類型。 假設有一個 memcached 叢集 - 它只是一組機器。
程式碼根本不需要知道伺服器的實體位置、大小或數量。 他使用特定的標識符進入集群。
為此,您需要在程式碼和引擎之間添加一個實體 - 代理.
RPC代理
代理人 連接巴士,幾乎整個網站都在其上運行。 同時我們有 沒有服務發現 — 相反,該代理有一個配置,它知道所有集群和該集群的所有分片的位置。 這就是管理員所做的。
程式設計師根本不關心花費多少、在哪裡以及花費什麼——他們只關心集群。 這讓我們有很多。 當接收到請求時,代理會重定向請求,知道在哪裡 - 它自己決定這一點。
在這種情況下,代理程式是防止服務故障的保護點。 如果某個引擎變慢或崩潰,則代理程式會理解這一點並向客戶端做出相應的回應。 這允許您刪除超時 - 程式碼不會等待引擎回應,而是了解它不工作並且需要以某種不同的方式表現。 必須為資料庫並不總是有效的事實準備程式碼。
具體實現
有時我們仍然非常希望有某種非標準的解決方案作為引擎。 同時,決定不使用專門為我們的引擎創建的現成的 rpc-proxy,而是為該任務創建一個單獨的代理。
對於 MySQL(我們仍然到處都有),我們使用 db-proxy,對於 ClickHouse - 小貓屋.
它的工作原理一般是這樣的。 有一個特定的伺服器,它運行 kPHP、Go、Python - 一般來說,任何可以使用我們的 RPC 協定的程式碼。 該程式碼在 RPC 代理程式上本地運行 - 程式碼所在的每個伺服器都運行其自己的本機代理程式。 根據請求,代理知道要去哪裡。
如果一個引擎想要到另一個引擎,即使它是鄰居,也會透過代理,因為鄰居可能在另一個資料中心。 引擎不應依賴了解除自身以外的任何物體的位置——這是我們的標準解決方案。 但當然也有例外:)
所有引擎均按照 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 是一個文件,在其末尾添加了狀態或資料更改的事件。 在不同的解決方案中它的呼叫方式不同:二進位日誌,
為了防止引擎在重新啟動時多年重新讀取整個binlog,引擎會寫入 快照 - 目前狀態。 如果有必要,他們會先讀取它,然後再從 binlog 讀取完畢。 所有二進位日誌都根據 TL 方案以相同的二進位格式編寫,以便管理員可以使用他們的工具平等地管理它們。 不需要快照。 有一個通用標頭,指示誰的快照是 int、引擎的魔力以及哪個主體對任何人都不重要。 這是記錄快照的引擎的問題。
我將快速描述操作原理。 有一台運作引擎的伺服器。 他打開一個新的空二進制日誌進行寫入,並寫入一個更改事件。
在某個時刻,他要么決定自己拍攝快照,要么收到信號。 伺服器建立一個新文件,將其整個狀態寫入其中,將目前的 binlog 大小(偏移量)附加到文件末尾,然後繼續進一步寫入。 不會建立新的 binlog。
在某個時刻,當引擎重新啟動時,磁碟上將會同時存在 binlog 和快照。 引擎讀取整個快照並在某個點提升其狀態。
讀取建立快照時的位置以及 binlog 的大小。
讀取 binlog 的末尾以獲取當前狀態並繼續寫入更多事件。 這是一個簡單的方案;我們所有的引擎都按照它工作。
資料複製
因此,我們的資料複製 基於語句的 — 我們在 binlog 中寫入的不是任何頁面更改,而是 變更請求。 與網路上的非常相似,僅略有修改。
相同的方案不僅用於複製,還用於 建立備份。 我們有一個引擎──一個寫入binlog的寫入主機。 在管理員設定的任何其他地方,都會複製此二進位日誌,就是這樣 - 我們有一個備份。
如果需要的話 閱讀副本為了減少CPU的讀取負載,只需啟動讀取引擎,讀取binlog的末尾並在本地執行這些命令。
這裡的滯後非常小,並且可以找出副本落後主伺服器多少。
RPC代理程式中的資料分片
分片是如何運作的? 代理如何了解要傳送到哪個集群分片? 代碼並沒有說:“發送 15 個分片!” - 不,這是由代理完成的。
最簡單的方案是firstint — 請求中的第一個數字。
get(photo100_500) => 100 % N.
這是一個簡單的記憶體快取文字協定的範例,但是,當然,查詢可以是複雜的和結構化的。 此範例採用查詢中的第一個數字以及除以簇大小後的餘數。
當我們想要擁有單一實體的資料局部性時,這非常有用。 假設 100 是一個使用者或群組 ID,我們希望一個實體的所有資料都位於一個分片上以進行複雜查詢。
如果我們不關心請求如何在叢集中傳播,還有另一種選擇 - 散列整個分片.
hash(photo100_500) => 3539886280 % N
我們也獲得哈希值、除法的餘數和分片編號。
只有當我們準備好這樣一個事實時,這兩個選項才有效:當我們增加叢集的大小時,我們會將其拆分或增加數倍。 例如,我們有 16 個分片,我們還不夠,我們想要更多 - 我們可以安全地獲得 32 個分片而無需停機。 如果我們不想增加倍數,就會出現停機,因為我們無法在沒有損失的情況下準確地分割所有內容。 這些選項很有用,但並不總是有用。
如果我們需要新增或刪除任意數量的伺服器,我們使用 像 Ketama 一樣在環上進行一致的哈希處理。 但同時,我們完全失去了資料的局部性;我們必須將請求合併到集群,以便每個部分都返回自己的小回應,然後將回應合併到代理。
有非常具體的要求。 看起來像這樣:RPC代理接收請求,確定要去哪個群集並確定分片。 然後,要么有寫入主節點,要么,如果叢集有副本支持,則它會按需發送到副本。 代理完成這一切。
日誌
我們以多種方式寫入日誌。 最明顯和簡單的一個是 將日誌寫入記憶體緩存.
ring-buffer: prefix.idx = line
有一個關鍵前綴 - 日誌的名稱,一行,還有該日誌的大小 - 行數。 我們從 0 到行數減 1 之間取一個隨機數。memcache 中的鍵是與該隨機數連接的前綴。 我們將日誌行和當前時間保存到該值中。
當需要讀取日誌時,我們執行 多獲取 所有key,按時間排序,從而即時取得生產日誌。 當您需要即時偵錯生產中的某些內容時,可以使用該方案,而不會破壞任何內容,也不會停止或允許到其他機器的流量,但該日誌不會持續很長時間。
為了可靠地儲存日誌,我們有一個引擎 日誌引擎。 這正是它被創建並在大量集群中廣泛使用的原因。 據我所知,最大的叢集可儲存 600 TB 的打包日誌。
引擎很舊,有些集群已經有6-7年的歷史了。 它有一些問題我們正在努力解決,例如我們開始積極使用ClickHouse來儲存日誌。
ClickHouse中收集日誌
這張圖顯示了我們如何走進我們的引擎。
有一些程式碼透過 RPC 在本地發送到 RPC 代理,並且它了解去往引擎的位置。 如果我們想要在ClickHouse中寫入日誌,我們需要改變這個方案中的兩個部分:
- 用ClickHouse取代一些引擎;
- 將無法存取 ClickHouse 的 RPC 代理程式替換為可以透過 RPC 存取的解決方案。
引擎很簡單 - 我們將其替換為 ClickHouse 的伺服器或伺服器叢集。
為了去 ClickHouse,我們做了 小貓屋。 如果我們直接從KittenHouse轉到ClickHouse,它就應付不了了。 即使沒有請求,它也是由大量機器的 HTTP 連線累積起來的。 為了使該方案發揮作用,請在具有 ClickHouse 的伺服器上 引發本地反向代理,其編寫方式使其能夠承受所需的連接量。 它還可以相對可靠地在自身內部緩衝資料。
有時我們不想在非標準解決方案中實現RPC方案,例如在nginx中。 因此,KittenHouse具有透過UDP接收日誌的能力。
如果日誌的傳送者和接收者在同一台機器上工作,則在本機主機內遺失 UDP 封包的可能性非常低。 作為在第三方解決方案中實現RPC的需要和可靠性之間的折衷,我們簡單地使用UDP發送。 稍後我們將回到這個方案。
監控
我們有兩種類型的日誌:管理員在其伺服器上收集的日誌和開發人員透過程式碼編寫的日誌。 它們對應於兩種類型的指標: 系統和產品.
系統指標
它適用於我們所有的伺服器
產品指標
為了方便,我們寫了很多東西。 例如,有一組普通函數可讓您將 Counts、UniqueCounts 值寫入統計數據,然後將其傳送到更遠的地方。
statlogsCountEvent ( ‘stat_name’, $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid, $key1, $key2, …)
statlogsValuetEvent ( ‘stat_name’, $value, $key1, $key2, …)
$stats = statlogsStatData($params)
隨後,我們可以使用排序和分組過濾器,並從統計中執行我們想要的所有操作 - 建立圖表、配置看門狗。
我們寫得很 許多指標 每天的事件數量為 600 億到 1 兆個。 然而,我們想保留它們 至少幾年了解指標的趨勢。 把它們放在一起是一個我們尚未解決的大問題。 我會告訴你過去幾年它是如何運作的。
我們有編寫這些指標的函數 到本地記憶體緩存以減少條目數量。 曾在短時間內在本地推出 統計守護程式 收集所有記錄。 接下來,惡魔將指標合併到兩層伺服器中 日誌收集器,它匯總了我們一堆機器的統計數據,這樣它們後面的層就不會消失。
如果有必要,我們可以直接寫入日誌收集器。
但是繞過 stas-daemom 直接從程式碼寫入收集器是一個可擴展性很差的解決方案,因為它增加了收集器的負載。 只有當由於某種原因我們無法在電腦上啟動 memcache stats-daemon,或者它崩潰了而我們直接使用時,該解決方案才適用。
接下來,日誌收集器將統計資料合併到 喵DB - 這是我們的資料庫,它也可以儲存指標。
然後我們可以從程式碼中進行二進位“near-SQL”選擇。
實驗
2018 年夏天,我們舉辦了一次內部黑客馬拉松,想到了嘗試用可以在 ClickHouse 中儲存指標的東西來替換圖中的紅色部分。 我們在 ClickHouse 上有日誌 - 為什麼不嘗試一下呢?
我們有一個透過 KittenHouse 寫入日誌的方案。
我們決定 在圖表中新增另一個“*House”,它將按照我們的程式碼透過 UDP 寫入的格式準確接收指標。 然後這個 *House 將它們變成插入物,例如 KittenHouse 可以理解的原木。 他可以完美地將這些日誌傳遞給ClickHouse,ClickHouse應該可以讀取它們。
帶有memcache、stats-daemon 和logs-collectors 資料庫的方案被替換為這個方案。
帶有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? 事情就是這樣發生的。 它有效 - 不要碰它。
相關鏈接:
- 安東·基留甚金的報告
“Vkontakte 系統管理員。 如何?” 包含有關 Copyfast 和八卦的詳細資訊。 - 尤里·納斯雷迪諾夫的報告
“VK 如何將數萬台伺服器的資料插入 CLickHouse ” . - 我的報告
“使用 VKontakte 範例的不斷發展的專案的架構” ,但從發展的角度來看,不是硬體。
阿列克謝·阿庫洛維奇 (Alexey Akulovich) 是作為程序委員會的一員,幫助幫助
PHP 俄羅斯 17月XNUMX日將成為近期PHP開發者最盛大的盛會。 看看我們有一台多酷的電腦,什麼揚聲器 (其中兩個正在開發 PHP 核心!) - 如果您編寫 PHP,這似乎是您不能錯過的東西。
來源: www.habr.com