缺人,技術受限,但我們重構了支撐100W用戶的架構!


​故事的開始

 

我剛到掘金的時候,有2個Android工程師,2個iOS工程師,3個前端工程師。沒了。這就是開發力量的全部人員配置。這個人員配置支撐了掘金的前20萬註冊用戶的服務。

 

而掘金則是用一個ServerLess平臺搭建起來的, 我們的服務器端實現全是node.js,然後提供接口給所有客戶端調用。

 

平臺代替我們完成了基礎的數據庫驅動, 數據模型的存儲, 基礎的ACL機制, 內容存儲等底層抽象, 用戶相關功能(整套用戶中心解決方案)等。使用它的SDK就可以搭建一個小規模應用了。掘金正是這樣快速誕生的。

 

但很快, 我們就遇到了一些問題。

 

故障。是的, 而且是系統性故障, 由於ServerLess平臺集成度非常高, 導致系統性故障的情況下我們連操作面板都打不開, 如果是自己搭建的系統, 起碼也可以有個降級。但完全依賴一整個系統的話, 在系統性故障面前除了等待平臺方修復以外我們束手無策。

 

性價比問題。大家都知道按量收費在請求量小的情況下是十分划算的, 而請求量超過一定程度反而固定量收費的模式會更經濟。這與手機流量套餐是一個道理。我們業務的請求量已經越過了按量收費的經濟區間。成本開始大幅上升了。

 

平臺架構完全的黑盒。除了我們使用的SDK,我們對平臺的架構和基礎設施一無所知。最外側能操作的邊界是自己的域名, 內側能操作的邊界是自己業務邏輯的代碼。除此之外, 是完全的黑盒。ServerLess 雖然賣點是開發者完全不需要關注這些, 但實際工業實踐中 ServerLess 並不是萬能的。

 

架構腐敗。代碼複雜度已經膨脹到了現有人手無法處理的情況。ServerLess 的理念是講究儘可能輕巧的, 但現有架構的耦合成都相當高, 平時甚至需要經常單獨安排時間來重構代碼。看似重構是爲了讓代碼組織更流暢, 但其實這是發生了更深層次的問題。

 

代碼需要經常重構直接說明了這麼幾個問題:

 

  • 現有架構設計跟新的需求衝突很大, 每次修改都與原有架構不兼容;

  • 現有架構開發複雜度高, 需要對當前的佈局和模式有相當深入的理解才能進行修改, 否則架構就會腐敗;

  • 現有架構擴展橫向複雜度(代碼量則增加)會導致縱向複雜度(組織代碼的代碼)劇烈增加, 而初創公司業務發展又是十分迅速的, 導致代碼量增長非常快, 這就會被迫瘋狂重構。 這也是爲什麼大型項目需要設計模式的原因。 大家需要一個統一的方式來管理業務橫向複雜度的增長, 而管理本身會增長業務的縱向複雜度。 通俗點來講就是代碼量太大需要適當的設計模式來組織代碼, 而代碼越多, 需要的設計模式和組織代碼也越複雜, 即縱向複雜度增加了。 橫向複雜度可以通過人手來解決, 而縱向複雜度則需要富有經驗的,熟悉當前架構的,並對接下來業務衍化有一定預知能力的工程師來解決。

 

我給出的能很好的處理縱向複雜度的這三個必要條件是經過經驗和思考得出的結論:

 

  • 富有經驗的,代表能看懂當前架構;

  • 熟悉當前架構的,代表能迅速針對當前架構做出修改;

  • 並對接下來業務衍化有一定預知能力的,則代表能對當前架構做出正確方向的修改。

 

基於這些原因和思考,我打算徹底改變舊有架構,爲掘金設計一個面向100萬註冊用戶服務的架構。

 

剛有這種想法的時候我也是很謹慎的的,相信各位多少在職業生涯中都經歷過重構項目。重構有多大風險就有多大。

 

但支撐我更換架構的最主要動力是無論做不做出改變都會面臨同樣的風險的局勢。如果做出改變,可能會有技術失敗的風險,時間和成本的消耗導致公司無法繼續運營下去。如果不做出改變,雖然當前沒問題,但在不遠的未來一定會陷入無盡的故障修復, 疲於維護, 業務無法前進的地步。

 

但唯一能確定的是,如果掘金在短時間都無法增長到100萬註冊用戶,那後續發展也沒有什麼意義。只有快速增長,未來纔有存活的機會。因此只有做出改變這一條路。

 

好,既然決定去做了,那就要儘可能快的去實施。不過就在這個時候, 我遇到了新的問題。人手不夠。

 

是的,工程師的數量補充不上來。雖然我已經盡力降低招聘的工程師的要求了,但是在面試中可能10個面試者裏能簡單實現鏈表的人連一個都沒有。更別提招聘到具有一定水平, 豐富經驗的工程師了。初創公司沒什麼名氣的話, 本身就不是很吸引人, 想要招聘到大廠工程師不是給到大廠工資就能解決的問題, 初創公司去大廠挖人很可能都是去了就Double的。而我們沒有這麼多預算。而通常招聘的話, 根據我的經驗一般在頭部公司, 初級職位的話2周內都能招聘到理想的候選人。但擺在掘金面前的是3周都不一定有滿意的候選者。

 

這使我不禁做出了一個假設, 會不會掘金可能在很長一段時間都面臨這種工程師整體水平無法提升的情況?

 

在我寫這篇文章的時候, 距離做出這個猜想已經過去了4年。事實證明, 這個猜想是正確的。初創公司如果不花大價錢, 那就無法穩定獲得和維持優秀的工程師。這是得到的血的教訓。

 

那麼, 回到當時, 在工程師水平不高, 人手不夠的情況下, 如何構建一個能支撐100W用戶,能迅速推進新業務,成本又不是特別高的系統? 在繼續之前, 我們需要一些理論基礎, 這些理論和思考, 可能還處於非常膚淺的程度, 不過從實踐經驗上說, 是成功的, 導出了我的答案。因此與大家分享一下, 希望拋磚引玉。

 

枯燥的概念環節

 

服務大小的概念  

 

服務的大小是個很有意思的話題。我們爲什麼不把所有代碼組織到一個服務? 該如何設計服務的大小?

 

我們不如先來看服務大小的區分。服務大小並不是指一個服務代碼量的大小, 而是指組成一個業務的服務之間的組織方式。根據組織方式我們可以嘗試對服務大小進行分類。

 

我們先假設一個固定大小的業務, 比如 Wordpress, 他是一個CMS類型的完整的業務。

 

那麼顯而易見, 服務大小的上邊界就是這個 Wordpress 了。我們可以先簡單定義, 整個業務只依靠進程內部通信 (無任何網絡通信) 並將整個業務組織成一個 repo, 即爲服務大小中的最大模式。

 

接下來我們將 Wordpress 無限拆分, 拆分到每個 function 都構成一個服務 (因爲再細分已經毫無意義, 把一個大小, 功能適當的 function 再拆開只會降低性能徒增複雜度)。那麼以 function 爲服務最小單位的 repo 組成的業務就是服務中的最小模式了。

 

爲了討論的簡便, 我們直接把最小服務模式叫 Picoservice。最大模式叫 Polyservice。Polyservice 的話我們剛纔說的 Wordpress 就是個很好的例子, 現實中 Polyservice 有很多, 傳統業務可能都是這樣組織代碼的, 相信大家也都見到過。

 

但每個 function 都拆分成服務的業務可不多見, 我粗略統計了下 Wordpress 大概有一萬個 function。可以想象將 Wordpress 拆分成 Picoservice 最後組織成業務是多麼可怕的事情。現實中只有 FAAS 平臺上運行的業務可能是以單個 function 的形式組成並運行的。

 

依據服務大小的定義, 我們可以把現有的服務類型按照大小進行排序:

 

Picoservice <= FAAS(or ServerLess) < Microservice < Monoservice <= Polyservice

 

注意服務大小跟服務部署規模沒有關係, 無論是 Picoservice 還是 Polyservice, 只要設計得當都可以多機部署以提升性能。

 

而爲什麼 FAAS 這種接近於每個 function 都要單拆分成服務的模式會出現呢? 很大的原因在於, 它可以降低每次編寫成本。如果程序足夠小, 目標足夠單一, 那麼每次編寫或修改的成本就會足夠的低。這種思想類似unix, 編寫目標和功能單一的程序, 並用管道將程序組織起來完成更復雜的工作。另外一點則是, 現在的硬件性能足夠強大了, 可以這麼揮霍, 犧牲一部分性能來換取便捷性。

 

微服務則是這種思想的以業務爲單元的架構模式。所以我覺得微服務也可以叫做 BAAS (業務即服務, Business as a Service)。

 

那麼同理, 現在很流行中臺這個概念, 中臺其實就可以看作將微服務中共通的基礎部分抽象出來, 將這部分形成Monoservice, 以提升性能和管理水平(即由專門部門來維護提供較高的可用性和符合預期的一致性)。

 

服務依賴模式的概念  

 

引出了服務大小的概念, 我們就可以開始討論服務之間的依賴模式。

 

首先我們知道, 是不是 Polyservice 與是否滿足低耦合高內聚沒有必然關係。那麼如果是接近 Picoservice 的情況呢? 當然我們不會真的每個 function 都弄個接口來跑, 但我們假設我們的服務要拆分, 該怎麼去拆分呢? 很顯然, 我們需要儘可能在容易斷開的地方進行切割。容易斷開的地方比如業務的自然邏輯, 業務的子功能, 外部依賴, CPU內存等資源密集型邏輯的邊界, 複用的多的邏輯等等。

 

我們同樣以理想模型開始入手。這裏我們討論的都是純粹的數據耦合(Data Coupling)。

 

1)串行ABC模式 (ABC Serial Pattern)

 

即:

 

 

A->B->C

 

這樣的業務進行拆分的話, 只要不將 A, C 放到一起, 就不算失誤, 因爲 A, C function 毫不相干, 放在一起就成了 偶然內聚性(Coincidental Cohesion) 毫無內聚可言。

 

2)相互 AB 模式 (AB Mutual Pattern)

 

 

A <-> B

 

兩節點的話, 決定是否拆分都可以, 沒有別的條件的話就不需要過多考慮。

 

3)樹形ABC模式 (ABC Tree Pattern)

 

即這樣的模式,ABC-Tree-Pattern:

 

 

 A

↓ ↓

B C

 

ABC-Turn-Tree-Pattern:

 

 

B C

↓ ↓

 A

 

對於這兩種模式, 同樣保證只要不行成 偶然內聚性(Coincidental Cohesion) 即把 B, C 放到一起, 就不算失誤。

 

4)環形ABCD模式 (ABCD Loop Pattern)

 

即:

 

 

A -> B

↑    ↓

D <- C

 

這種模式通常在回調函數中常見。一般適合大端放到一起以提升性能。

 

這4種最小模式就是組成業務調用的最常見模式了, 接下來我們來看真實世界的程序的調用模式:

 

 

我們可以看到程序的調用圖 (Call Graph), 基本都是這幾種模式的組合。一個大的業務的話, 其實也是類似這樣的樹形的調用模式(如圖, 微服務的Kiali調用圖)。

 

 

那麼, 我們根據上面的基礎模式, 知道了不適合拆分成什麼狀態。但我們對適合拆分成什麼狀態還沒有頭緒, 這時候我們就要引入一些其他參考條件了。

 

5)自然業務的最小單元

 

我們都知道需求產生業務, 沒需求就不用寫代碼了。那麼業務的最小單元是什麼呢?

 

我們定義, 只包含一個功能的操作的集合即爲最小業務單元(Minimal Business Unit)。比如文章業務(包含創建文章, 讀取文章, 更新文章, 刪除文章等操作)。而其中的操作即爲最小操作單元(Minimal Operating Unit)。

 

一般來講, 按照業務拆分的最小極限即按照最小業務單元進行拆分。而如果爲了性能, 可以在局部提升到按照最小操作單元拆分。甚至爲了極限性能, 操作單元內部還要進行拆分。比如微博的數據流的生產, 大V和普通用戶的模式是不一樣的。

 

我的實踐經驗是, 如果程序的一部分邏輯有以下一個或多個行爲, 就可以考慮拆分了:

 

  • 只被其他部分調用, 即處於調用圖的底部的邏輯 (通常可能是工具類或存儲類)

  • 被很多其他部分調用

  • 調用很多其他部分

  • 業務需求變化非常頻繁的部分

  • 使用者不同的部分

  • 請求耗時長的部分

  • 代碼量大顯著大於其他部分

 

用最小業務單元模式拆分  

 

我們通過服務大小的概念瞭解了, 服務越小越容易做出修改, 當然性能也會越低。而通過服務依賴模式的概念我們知道了, 該如何組織服務以讓我們的服務間依賴更簡單。

 

我們在上面講了中臺這個概念的出現, 它將微服務之間公用的部分抽取出來以提升性能。那麼我們自然可以想到, 有沒有與中臺相對的概念呢? 比如, 散臺(這裏將中臺的中理解爲集中的中, 而不是中間的中)? 又或者說, 有沒有一種, 不但容易修改, 而且修改不僅對自身影響較小, 對其他周邊影響也很小的模式?

 

好的, 我們來講一個服務大小中介於FAAS和微服務之間的模式。我叫它最小業務單元模式(Minimal Business Unit Pattern)。即業務單元爲模式的基礎單元, 它在實踐中的特殊之處在於, 鼓勵以複製的方式複用, 來降低不同業務對同一邏輯的複用程度。進而將業務的變化對業務之間的影響降低到最小。

 

比如一個業務被很多其他業務調用, 現在新業務需要再次調用這部分邏輯, 並且有很多與原來邏輯不一樣的地方。傳統的重構模式會在原來的代碼並在基礎上開發, 中臺模式會在這個邏輯足夠大的時候抽取出來集中化形成基礎設施, 而這個模式會直接寫一部分重複代碼連同新的業務拆分出去。

 

這是一種反模式, 雖然它也是一種複用, 但直接違反了DRY(Don't repeat yourself)原則。帶來的壞處直接導致代碼量膨脹, 維護成本上升, 維護困難。但這次我們來看它會有什麼好處:

 

  • 首先它最大的好處是可以降低代碼修改引入Bug的機率。因爲舊代碼不變就不會引入新問題;

  • 其次可以以橫向複雜度的增長(代碼量的膨脹)替代代碼縱向複雜度(爲了組織代碼而產生的管理邏輯, 設計模式就是個最好的例子)的增長;

  • 再次它的成本很低, 在原有代碼基礎上修改不僅需要熟悉原有業務, 修改成合適的組織模式, 修改完畢後還需要測試原有業務(因爲不能保證修改後原有業務沒問題), 這些都是成本。而新業務只需要複製需要的邏輯並新增邏輯就行了;

  • 最後不會導致故障集中化, 這與生物學中遺傳變異的原理是一致的, DNA(可以看作邏輯)在複製過程中產生的變化, 可以在病毒(可以看作Bug)大範圍爆發的時候降低感染面積。同時複製後, 單個程序裏複製的部分掛掉不會影響到其他使用這份代碼的副本, 而在一個業務裏共享這部分代碼的程序, 一旦這部分代碼掛掉了, 則整個使用這部分代碼的業務都會掛掉, 最好的例子就是配置中心這個在大公司常見的業務, 一旦配置中心掛了, 所有業務都會掛掉。而把配置放到代碼裏, 雖然不是個好的實踐, 但卻不會面臨這種問題。

 

但是, 一定要搞清楚這種拆分反模式什麼時候適用:

 

  • 實施反模式的業務一定要拆分得足夠小, 這是反模式成立的前提, 接下來幾條都會依賴這一條規則;

  • 這種反模式適合實驗性新業務頻發爆發的情況。即產品經理在實驗他的新想法。這種時候, 與其在原有的業務上修改, 然後發現並沒有用戶喜歡這個新功能, 最後新增邏輯被扔在業務裏只會是個定時炸彈(無論是Bug還是安全隱患都會有)。不如直接寫個新的業務, 將需要的部分複製出來進行重複。這樣不需要的時候就可以直接下線。也不會影響到原有的業務。變更成本比在原有代碼上開發要低很多;

  • 種模式實施完畢後, 再次修改的情況不適合重構, 更適合重寫。只要業務夠小, 那麼重寫的成本就會無限低。重寫引入新Bug的機率也會相應下降;

  • 這種模式適合維護人員頻繁變更的情況。比如公司人手不夠, 又或者人員更迭頻繁, 那麼在原有代碼基礎上修改就需要理解原有代碼的組織模式, 而新增則直接新寫一個就可以了;

  • 實施反模式的另外目的在於消除業務間的調用, 如果實現完畢反模式仍然會調用源業務, 那麼就說明實施的不徹底。

 

是他先動手的

 

動手  

 

好的, 抽象的概念到此爲止。讓我們來看看上面這麼多都講了些什麼:

 

  • 我們先說了不同大小的服務的的特徵;

  • 然後我們又講了如果要將服務弄小的話, 該怎麼拆分, 什麼情況下適合拆;

  • 最後我們又講了一種拆分到非常小的, 每次修改成本非常低的最小業務單元模式。

 

好的, 我們通過對服務大小和服務間依賴模式的分析得出了一種每次編寫成本低, 對周邊影響低, 當然性能也低的模式 -- 最小業務單元模式。

 

再來看我們的目前的情況, 人手不夠(沒時間處理縱向複雜度), 人員水平不高(沒能力處理縱向複雜度)。所以很適合使用這種橫向複雜度暴漲但縱向複雜度不高的模式。

 

那麼性能問題怎麼解決? 對於總體性能問題, 這裏就是WEB業務的先天優勢了, 只要部署副本, 總體性能就會按照副本數量線性提升。而對於單次請求性能問題, 我們知道, 提升性能有兩種途徑, 做減法, 即減少邏輯自然性能就提升了。和 集中化, 即跟中臺一樣, 將性能敏感的部分集中化以提升性能。

 

最終, 我們終於得出瞭解決當前和未來一段時間人員問題的最終方案 -- 使用最小業務單元模式重新編寫所有業務, 並在性能不夠的核心部分使用傳統的集中化模式以提升性能。

 

阿基里斯按住了烏龜  

 

重構絕不是一蹴而就的, 一次性將業務停掉把新系統寫好然後一次性把流量都切換過去簡直是自殺行爲。有序的, 儘量不影響現有流量來進行切換, 成功的概率纔會增加。所以, 切換的流程正好與從0開始編寫一個大型業務相反, 先切換最上層的邊緣業務, 然後逐漸向內進行, 最後切換核心, 是比較好的實踐方式。

 

大家都知到阿基里斯和烏龜的故事, 而業務重構過程也類似這個感覺。在重構的過程中, 如何保證舊有業務代碼不再繼續膨脹是重構成功的關鍵之一, 否則舊代碼一直膨脹, 新業務永遠也沒有上線的機會。

 

這種情況很簡單, 把原有業務代碼變爲只讀就可以了。我與業務團隊制定了規則, 從一個時間點開始, 不再向舊有平臺新增代碼。新增業務一律直接從頭開始寫新的服務。

 

重構業務如同給魚換魚缸, 需要4樣東西參與整個過程, 魚缸(原有系統), 魚(用戶流量), 魚網(橋接器), 新魚缸(新系統)。既然不能繼續在原有系統上繼續增加代碼, 那麼我們該如何在重構的過程中, 讓新的系統訪問殘留在舊系統的功能和數據呢? 答案就是漁網(橋接器)了。

 

選老平臺與新平臺的橋接器是很複雜的, 需要在所有訪問流量的收束點(網關部分), 來依據規則轉發流量, 這樣才能做到改動最小, 覆蓋所有業務。而原有 ServerLess 平臺的SDK則是個很好的橋接器, 所有的客戶端都需要SDK才能跟服務端進行通信。

 

我們將它改造了一下, 新平臺按照最小業務單元模式拆分出來的業務既讀取它自身的數據庫, 也通過改造後的SDK讀取遺留在 ServerLess 平臺上的原有數據。同樣, 原有 ServerLess 上的業務需要訪問新平臺上的業務, 也通過改造後的SDK來轉發這部分流量到新的平臺。這樣每次遷移完畢一個業務, 只要在SDK裏面做出修改, 就能把流量導向新的平臺了。全部切換完畢後, 再單獨找時間移除SDK, 換成原生的URI調用即可。

 

這裏其實就是 ServerLess 平臺設計上的侷限之一, 自己的域名到 ServerLess 平臺是 CNAME 過去的, URI自己完全無法控制。而在自己建設的平臺, 存在web網關, 再次遷移只要在WEB網關上做修改即可, 業務是完全無感知的。而 ServerLess 平臺無法操作WEB網關, 因此只能做出這種hack。不得不說SDK是開源的給了我們遷移的機會。要是閉源的。只能要麼做風險極高的一次性遷移, 要麼在上面再適配一層自己的WEB網關才能達到同樣的效果。

 

顯而易見, SDK訪問兩個平臺會成爲性能瓶頸。所以我直接在新平臺實現了一個集中式的流量代理網關 api-proxy, 用 openresty 實現了流量轉發, 並內嵌了個用 lua-resty-lrucache 實現的內部緩存, 來解決數據耦合過程耗時的問題。這個代理網關能在 8core16G 的機器配置下實現接近80KQPS(平均請求耗時20ms, 緩存命中的情況下)的性能, 這就是我們在上面講過的, 在性能不夠的核心部分使用傳統的集中化模式以提升性能。

 

Ghost Dubbing  

 

數據庫的切換比較複雜, 對於文章、評論這種數據, 我們找用戶在線比較少的時間直接複製數據庫切換流量就可以了。但是在複製用戶數據上我們遇到了問題。用戶密碼是加密的, 而且我們不知道是如何加密的 (ServerLess平臺的內部功能, 具體實現未知)。

 

如何切換這部分數據呢? 我們做了這樣的設計。

 

未註冊用戶, 很簡單, 直接調用遷移移植後的新用戶中心接口註冊就可以了。

 

已註冊用戶, 用戶登錄時調用 api-proxy 模擬用戶登錄, 用戶一旦在舊平臺登錄成功, 證明用戶的用戶名和密碼是合法的, 直接將用戶輸入的這個密碼輸入新平臺的更新用戶密碼邏輯, 完成本地數據庫密碼的更新。一旦檢測到本地用戶存在密碼, 就不會在調用 api-proxy, 而是直接進入新的用戶中心完成用戶登錄過程。這樣我們就完成了在不知道原來加密方法的情況下, 重新將用戶密碼加密後結果存儲到新數據庫中的過程。

 

非活躍用戶, 一直調用 api-proxy 來驗證用戶登錄是完不成遷移的, 畢竟我們最後要下線 api-proxy。於是我們設定了一個2周的過渡期, 2周過後, 我們直接下線 api-proxy。非活躍用戶將直接進入密碼重置流程, 通過發送重置鏈接到用戶註冊郵箱完成密碼的更新。

 

部署模式和局部優化  

 

最後我們將一些不便於灰度切換的部分統一整理了下, 準備了一個特定的時間來進行整體切換。上線過程很簡單, 我們提前發了公告, 然後在 SDK 和 api-proxy 去掉了向 ServerLess 平臺的轉發邏輯, 直接全部切換到新環境中。由於測試進行的很充分, 基本沒遇到適配錯誤。但出現了性能問題。

 

切換後整體延遲顯著上升, 主站甚至在高峯期出現了打不開的情況。這就是之前說的由於拆分過細導致的性能問題了。不過解決方案也早就準備好了。我們服務的部署模式是單機整體部署的, 即線上每臺機器都有整站的所有服務。因此提升性能直接在公有云新開一臺機器, 將服務全都部署到上面, 最後在WEB反向代理添加新增的機器就可以了。

 

在沒有容器化平臺之前, 想要迅速簡單擴容, 單機整體部署是相當不錯的實踐。架構足夠簡單。每臺機器都是冪等的。不會出現某臺機器離線導致部分業務不可用的問題。但缺點也是顯著的。因爲單機的性能是有極限的。因此當業務非常多的時候, 就會面臨單機性能不夠的情況, 雖然增加機器數量可以緩解, 但如果存在CPU密集的業務, 那麼這種部署模式勢必會與別的業務共享主頻, 造成延遲上升。根據我們的實踐, 掘金有 100+ 微服務 repo。也就是說, 這些repo每臺機微服務器上都有。我們的測試數據極限大概在每核心 8 repo 左右。即一臺16核心的機器, 大概能承受我們分割粒度的 repo 128 個。

 

爲了減少接口延遲, 我們還在 api-proxy 上做了一些優化。由於服務是最小業務單元模式的。因此業務關聯數據較多的時候, 通常需要調用的底層存儲接口也會較多。這會嚴重影響性能。尤其是數據有上下文依賴的情況。用戶通知就是個例子, 比如某用戶接到了別人點贊他文章的通知, 那麼這個通知信息要顯示文章的摘要(讀取文章系統), 顯示點贊用戶(讀取用戶信息), 顯示點贊數量(點贊系統), 這就要調用3個接口。爲此 api-proxy 設計了一個全局的緩存。將這些數據提前用 builder 聚合到一起, 需要數據的系統直接讀取緩存, 並將其中不需要的字段過濾掉, 只保留需要的字段即可。這是一種典型的用空間換時間的思路。

 

結尾

 

掘金的重構和切換持續了接近6個月時間, 從2016-12到2017-05結束。重構之初有我和2個前端工程師, 2個後端工程師, 2個iOS工程師, 2個Android工程師。其中2016-12至2017-02這幾個月是並行進行的, 即同時有新業務開發和重構在進行。17年03月份工程師流失非常嚴重, 前後端只剩下我和另外一個前端工程師, iOS工程師全部離職, Android 工程師感情深厚留了下來。

 

所以這個月有大部分時間都在招聘新工程師, 並且在03月底招聘到了3個後端工程師。實際上, 每年發完年終獎都是工程師流失最嚴重的時刻, 但我們由於一些其他問題導致流失的太多了。說起來, 我們在工程師培養上可謂碩果累累。這些工程師在離職後都去了一線大廠。不得不說小公司從各方面上來講都太難了。這也是我不得不放下驕傲, 將架構儘可能變得簡單的原因。

 

2017-04至2017-05這兩個月停止了新業務的開發, 所有時間都在重構和遷移。在遷移的最後階段可以說所有團隊的壓力都是非常大的。產品團隊處於"沒有新功能就無法吸引用戶新增的滑稽又尷尬的情況", 而開發團隊則處於帕累託曲線的最尾部, 越是接近完成, 就會發現越多零碎的小細節在等待着還原和完善。這種時刻就是作爲領導發揮作用的時刻了。我最後直接砍掉了一些舊有業務, 先完成切換, 然後找時間再完成這部分遺留問題。實際最後到我離開掘金, 這部分業務也沒有被要求重新實現。

 

我聽過無數個類似 "如果當初我知道這麼難我絕對不會做" 的故事。在重構期間我自己甚至把每週的週六都無償付出用來推進進度(公司週六正常休息), 希望能儘早切換完畢。但切換完畢後, 評價工作成果成爲了難題。因爲短時間內都不會遇到性能瓶頸了, 我也沒有時間來寫這樣一篇大長文來從原理講述爲什麼要重構, 要怎樣重構, 重構的過程, 重構的收益。也許只有在舊有架構崩潰的那一刻, 用魔法瞬間修復, 才能顯現出摧枯拉朽的效果。但魔法是不存在的, 創業公司也沒時間用來抒發感慨。

 

強大的架構不如錢, 錢不是萬能的。因此強大的架構不是萬能的(手動滑稽)。架構沒有優劣之分, 只有是否適合團隊和業務之分。謹慎重構, 評估先行。

 

作者丨karminski牙醫 來源掘金開發者社區(ID:juejin_im) dbaplus社羣歡迎廣大技術人員投稿,投稿郵箱: [email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章