停下來,歇口氣,造輪子

上周四至今,我大概有 50-70% 的時間在造一個輪子,一個叫 merlin 的工具。

事情的起源是這樣的 —— 我們內部的一個重要服務,要升級到 elixir 1.5。之前這個服務的 ansible 部署代碼大概是這樣的:在目標機器上 clone 代碼,編譯,生成一個符合 systemd 的 release,更新 systemd 配置,重啟服務。那位說:如果一個 cluster 里有幾十台機器,每台都這麼 build,費時費力,中途出問題的幾率也增大很多啊 —— 為什麼不直接在 CI 工具,比如 travis 里完成 build,生成一個 tarball,目標機器直接獲取這個 tarball 部署呢?

這有些歷史原因。我們的服務在編譯期做很多事情,需要訪問 db 等資源,由於我們使用第三方 CI 工具,不方便把 db 哪怕是 readonly 的 credentials 暴露給它,無奈採用了這麼個不合 erlang / elixir 時宜的部署方案。

我們生產環境的 erlang / elixir 跑在 19.x / 1.4 上。如果貿然升級,對線上服務影響很大;provision 新的 cluster,部署後切換流量然後再把老的 cluster 幹掉雖然可行,但是每次版本升級都要這麼走一遭,不太好。再加上目前的在目標機器上編譯的部署方式越來越沉重,一次部署動輒幾分鐘,效率太低,所以我動了做一個簡單的構建系統的心思。

思路很簡單:我們是 github 的重度用戶,那麼將整個場景和 github release 連接起來:提供一個處理 new release event 的 webhook,當 github 上某個 repo 生成一個新的 release,webhook 會收到這個 event(裡面有 repo 名字,tag 等信息),我們將其稍作處理後便塞到 AWS SQS 里,然後有一個定期的任務從 SQS 里拉出消息,按照消息中的 repo 和 tag 把代碼 checkout,build 之,然後將生成的 tarball 作為 asset 上傳到這個 release 里。一切完成後,將這條消息從 SQS 里刪除。這是整個 build 的流程。要部署的時候,只要跟部署腳本指明要部署的 release,我們可以從 release assets 中拿到這個 tarball,解壓部署即可。

這樣的好處是:構建系統在我們自己的 VPC 中,可以從 vault 中獲取資料庫的 credentials;同時,我們只需要在構建系統里搭載合適的 erlang / elixir 版本,然後通過 include ERTS 的方式構建,可以讓目標機上完全不用關心 erlang VM 的版本。

那位說,為何不在 webhook 里把消息插入 erlang 內部的一個 queue,而是要引入外部的 SQS 呢?這是因為 build 耗時很長,和系統/工具鏈的很多環節打交道,中間有各種可能,如果處理消息的 process 掛掉,消息跟著也隨風而逝。這會使得我們失去處理完這個 build 的機會,這樣不好;然而要處理好這些邊邊角角的情況,做持久化,需要額外寫不少代碼。而 SQS 保證消息不會丟失,dequeue 後消息只是隱藏起來,在 visibility timeout 內對其他人不可見,所以處理失敗也不怕,visibility timeout 一過,又可以重新處理;只有所有處理結束,我們顯式刪除消息,消息才會真正從 queue 中拿走。另外,SQS 對這樣消息體量的應用,幾乎是免費,何樂而不為?

這個任務我處理近一周,寫下 650 行代碼,發布不下十個 release,已經在線上為內部的三個 elixir repo 提供服務,目前一切運行良好。

下圖是整個系統的設計稿:

下圖是一個 release 發布後,還未編譯的 github 截圖:

已經編譯成功後的 github 截圖:

這個系統如此簡單,我們只需用 plug 寫幾個 API,然後有一個定期運行的 GenServer 處理消息,spawn process 進行 build 即可。不用太多介紹,相信你也能很快寫出。

這篇文章我著重要說的是:你要時不時的,停下來,歇口氣,從頭開始造輪子。你會有意想不到的驚喜和收穫。

我們程序員大部分的工作時間,說好聽一點是在創作(make stories),說難聽一些是做些瑣碎的零工(do chores)。我們需要做這些零工來獲得收入養家糊口,但這些日復一日的零工卻配不上我們的成長。如果總在「打零工」,即便是以 996 的節奏工作,也是蒙眼狂奔,或者說「老鼠賽跑」。

過去的幾個月,在北京和舊金山,我面試過不少程序員。每每我拋出一個試題,要應聘者選自己最熟悉的語言寫個解決方案時,幾乎所有人在我話音將落未落之際,就迫不及待地打開編輯器,或生成 skeleton 代碼,或開始 google 某個函數的使用方法,或寫下第一行代碼 —— 卻鮮有人在白板上,草稿紙上理一理思路,清一清頭緒,畫一畫自己的解決方案。這就是平日里被動養成的打零工的心態 —— 每個任務就像流水線上的計件工作一樣,誰也不需要太多的思考,甚至連問題都不太用問,順著已有的行為模式和思維習慣做下去就可以。

在公司里做事,一份代碼維護地越久,在生產環境存活地越長,大家行事的風格就越謹慎。這有很多原因交織在一起:後來者沒太讀懂前任的邏輯,不敢輕易修改;已經運作數年的框架,功能不斷堆疊,已經像托勒密的地心說模型,不堪重負,卻無人有膽識推倒重建或者另起爐灶,因為行走江湖靠的是一個「穩」字;技能棧被鎖死,想有所突破,卻無從下手;更有甚者,被公司太過優越的開發環境和工具鏈所馴化,久而久之,就像養尊處優慣了的八旗子弟,突然被扔到荒郊野嶺,已然騎不上馬,跑不動道。

想想看,你上一次從零到一把一個全新的項目從一個藍圖起,一行行代碼壘起來,最終部署到生產環境或者用戶手邊,是什麼時候?如果超過半年,那麼,你可能需要停下來好好歇口氣,認認真真從頭造個輪子。

你會發現,之前在一個大項目里填補功能,寫起代碼來左右逢源,勢如破竹;如今一個空白的項目下,數據結構的選擇和定義都讓人抓破頭皮。

等基本功能準備停當,可以部署,你又發現,原來從零開始寫一個 ansible 的部署腳本,和平日里的 copy paste 有雲泥之差。哪些東西該抽象到 role 里,需要設置什麼樣的 hooks,別看平日里讀別人的腳本遊刃有餘,真要自己老老實實去寫,才發現,別人家的代碼畢竟是別人家的代碼。

CI/CD 的設置扒半層皮,服務的監控又是另外半層。很多平日里不用操心的事情,一下變得重要起來。「已有的系統」就像是一座監獄,把你和野蠻的自由世界隔離開來。

在從零到一的過程中,你戰戰兢兢,汗出如漿。之前被同僚們奉上的「宇宙中心編程小王子」原來只是穿了層皇帝的新衣,禁不起一捅。

殫精竭慮之下,輪子終於被建造完畢,痛定思痛,你重新捧起原先已快要爛熟於胸的代碼。寫過之後再讀,原來那些冷冰冰的代碼,變得豐滿紅潤起來,你腦海里充滿的無數個問號,此刻開始一一對號入座。是的,你的工作欺騙了你:每日的零工讓你莫名滿足,你真的只是不知道自己不知道而已。

所以,時不時的,停下來,歇口氣,把自己逼到牆角里,造點輪子。


推薦閱讀:

從微服務聊起
不要嘗試同步代碼和設計文檔
超實用極簡客戶端Failover方案
知不知上——控制調查範圍
為什麼「耦合」概念該要摒棄

TAG:软件架构 | 软件设计 | 软件开发 |