Golang 在十二讚的深度應用

santower發表於2018-04-23

我們是“十二贊”,一個致力於幫助電商賣家進入小程式的小團隊,我們的主頁是http://www.12zan.cn/。 在實際執行中,我們使用了大量由golang寫就的小工具,幾乎每一個工具程式碼量都超短,一般在200行左右就完成了一個獨立的功能,同時擔當了相當重要的角色;像代理伺服器,程式碼量一共500行多一點點,卻是我們的核心支柱,壓測時QPS也直追nginx,表現優異。

基於Docker的基礎結構

做為基礎架構,我介紹一下我們的機器架構。 我們的整個業務構建於阿里雲之上,有5臺server,每一對都有獨立的外網IP,同時也在同一個內網之中。在每一臺機器上都跑了一個我們自己用golang寫的守護程式,這個程式負責監聽一些業務重啟、新增域名等類似的指令並執行(這些指令最後都傳遞給了docker)。同時,每臺機器上都有一個consul程式,這些consul都join到了一起。另外,我們每一臺機器上,都用docker跑了一個nginx來做80埠的服務,同時跑了一個用golang自己寫的HTTP代理。nginx做做日誌啊基礎的功能之後就把請求丟給這個http代理 ,HTTP代理會到consul裡去查應該將請求轉發到哪個IP的哪個埠上。

400行Golang程式碼寫的HTTP Proxy

在架構選型的第一天,我們就決定,我們會服務化,會大量使用http 介面來提供服務,並使用自己的http proxy來分發請求、新增自有的一些業務邏輯比如API的許可權驗證等邏輯。我們限定,所有業務,域名都是*.app.12zan.net,比如我們要上一個聊天服務,請求的介面就會是chat.app.12zan.net,哪天再上個評論服務,請求的介面就是comment.app.12zan.net。 確定域名後我第一件事情,就是拿golang自己寫了一個非常簡單的基於consul的http proxy server;感謝Golang這完善的HTTP庫,我們只用了幾百行程式碼就完成了所有功能。每當有http請求過來時,這個proxy server就會根據HTTP請求中HTTP_HOST 欄位去consul去查,有哪些後端是用這個域名名稱來註冊服務的,並根據指定的演算法,取出一臺後端來,把這個HTTP請求Proxy過去。每個具體的業務,可能執行在我們5臺機器中的任何一臺之中的docker上,也可能是多個docker例項上。所以這裡有一個機制,選擇哪個實際的docker例項來服務這個請求的問題。我們現在支援隨機選取、按客戶端IP地址做hash之後選取、按URL做Hash選取、按負載選取幾種方式。

WEB服務的服務註冊

基於php+laralel和nodejs+koajs兩種場景,我們製作了自己的docker映象。這個映象除開可以將php+nginx和nodejs構建的web服務執行起來之外,還包含一個golang寫的consul客戶端。在docker容器裡,這個客戶端隨著php+nginx或是nodejs的web服務一起啟動,啟動之後會向宿主機的consul 程式註冊自己這個服務,註冊的時候會通知說,某某應用,在某某IP某某埠提供服務啦,如果前面有到**.app.12zan.net的請求你可以轉發給我;同時會每隔一秒上報自己的程式數、當前機器CPU佔用、記憶體佔用情況。也是一樣的簡單,幾百行golang程式碼,就鼓搗出了這個consul客戶端。為什麼使用golang呢?第一個原因當然是因為consul天生是golang陣營,第二個,是因為我們的docker容器種類較多,所以這個客戶端直接就是在Mac上跨平臺編譯出來的在linux64平臺上執行的,不管docker容器是python為基準的還是ruby為基準的,還是nodejs的,只要把這個二進位制檔案拷貝進去就能正確執行,不像別的語言需要解決依賴問題。

我們還開發了一個web console介面,在這裡,我們可以註冊app,也可以為app新增例項。註冊app時,我們要指定程式碼倉庫的地址(對了,我們的程式碼管理是用的golang寫的gogs),指定對外服務的域名,指定是nodejs應用還是php+laravel應用。新增應用之後,可以在這個應用下新建例項,讓系統在指定的IP上去跑這個例項。例項執行的過程實際就是下發一個通知到某個機器上,去執行一個docker例項啟動的過程。docker啟動的時候帶了一些環境變數,比如當前內網IP、docker監聽的埠、對外提供服務時是用何域名提供服務。

日誌和儲存

前面這種架構有一個問題,就是後端可能是在任何一臺機器上執行的,今天可能是A,明天可能是B,那我要是把檔案存在A上了是不是讓B來提供服務的時候就掛掉了?所以我們想了這麼一個辦法(也是因為窮。。。。),我們把所有的檔案都挪到阿里雲的OSS服務上。同時為了不管是Nodejs應用還是php應用 還是python寫的應用都能做到把使用者上傳的檔案或是系統生成的檔案存到oss上面,我們很省事地寫了一個ossUploader,編譯好的可執行檔案釋出,只需要執行它,傳進來本地路徑和oss上的目標路徑,就保證給你上傳到oss上去就完整,不需要再在nodejs、php、python、ruby、java各種平臺下都琢磨一遍oss的SDK。

對日誌的處理是一樣的, 所有的日誌檔案的內容,都會被一個golang寫的工具gtail監聽著(就像linux 的tail -f命令一樣),所有新產生的內容都會被gtail挪到oss上去儲存。當然,也是可執行檔案釋出的。

這個實現之後, 我們的實際業務就真正可以在5臺機器上之間任意騰挪了。

訊息廣播

得益於golang的一些開源倉庫,我們還做了一些好玩的東西。 比如,看到https://github.com/gorilla/websocket這個東東,我們忍不住擼了一個websocket server,或是說叫群聊伺服器更好一點。 接下來,我們看到有一個golang的庫叫go-mysql-elasticsearch,偽裝了一個mysql的slave,去MySQL的master機器上去讀binlog,讀到binlog以後就將MySQL裡的資料傳送給ElasticSearch去索引資料。 我們就結合了一個,把這兩個結合起來,修改了一下go-mysql-elastichsearch,讓它監聽到MySQL的資料變更之後,在WebSocket server的某個群聊裡推送出來,形成一個資料變更的廣播。 再接下來我們就可以用nodejs寫一個應用,連上這個websocket server,加入特定的某個群聊,就源源不斷地收聽到資料變更的訊息。這個nodejs端的程式碼就非常簡潔了,只需要不到100行程式碼可以做各種好玩的事情,比如監聽到使用者留言表有新增,可以發郵件讓運營馬上去稽核。還有比如說,每當訂單表有成交的時候,我們某個小小的nodejs應用因為監聽了資料庫訊息,第一時間就知道了,馬上就去追溯使用者來源,來計算返利;同時這個nodejs的程式碼更新是和訂單主邏輯完全不相關的,寫這個業務的開發人員只需要知道訂單表的結構,不需要了解訂單應用後臺程式碼。

相關文章