工程師如何對待開源---一個老工程師的肺腑之言

語言: CN / TW / HK

工程師如何對待開源

本文是筆者作為一個在知名科技企業內從事開源相關工作超過20年的工程師,親身經歷或者親眼目睹很多工程師對待開源軟體的優秀實踐,也看到了很多Bad Cases,所以想把自己的一些心得體會寫在這裡,供工程師進行參考,希望能幫助工程師更好的成長。

概述

作為一個在科技企業內部進行技術工作的工程師,工作任務就是用技術手段支援和實現公司所關注的商業目標。 實際工作過程中,需要主動或者被動的使用和維護大量的開源軟體。 據統計,每個工程師在企業內部進行研發和運維等工作的時候,每年會接觸到上千款開源軟體, 如果是以JavaJavaSciprt為主要程式開發語言的工程師,則接觸到的開源軟體數量更多,在萬級別甚至十萬級別。 (資料來源:《2020 State of the Software Supply Chain》由Sonatype釋出)

那麼如何選擇開源軟體? 這麼多開源軟體中,如何根據個人需求和業務需要來選擇合適的開源專案來進行投入,是需要綜合考慮的。

選擇了開源軟體之後又如何進行定製和長期維護? 這也是一個很大的問題。因為在企業內部開發軟體,跟個人開發軟體不一樣的是,維護一個計算機軟體系統的成本遠遠大於開發該系統或軟體的成本。選擇開源軟體之後,如何從長期的視角進行定製和修改,後續的長期維護如何進行,才能做到高效和節省成本,業內有很多很好的經驗,也有不少不太成功的案例成為教訓。

最後回到個人,工程師的成長是在不斷的學習和實踐中進行的。如何來利用開源來提升自己能力,擴大自己的眼界,提高自己技術口碑和業內影響力,對於工程師本人也是非常重要的。

本文將從如下三個部分來分別闡述:

  1. 工程師如何選擇開源軟體
  2. 工程師如何定製和維護開源軟體
  3. 工程師個人成長如何利用開源

1. 如何選擇開源軟體

首先要明確對開源軟體的態度,在現階段是不可能離開對開源軟體的使用的。 使用開源軟體有各種各樣的風險,包括開源合規、安全、效率的問題。 簡化為一句:在企業內部使用開源軟體,需要遵守該企業對開源軟體的內部規定,包括如何引入和如何維護,以便達到高效、安全、合規的使用。

回到具體如何選擇特定的開源軟體的問題上,有如下幾個緯度可以進行參考。

  1. 根據需求
  2. 根據技術發展趨勢
  3. 根據軟體採納週期的不同階段
  4. 根據開源軟體的成熟度情況
  5. 根據專案的質量指標
  6. 根據專案的治理模式

1.1 根據需求來選擇開源軟體

選擇開源軟體,首先要明確需求,即選擇這個開源軟體的目的究竟是什麼。 工程師選擇一個開源軟體,究竟是它用來做什麼的,是用來進行個人學習的; 還是用來滿足ToB客戶的需求的;還是用來滿足內部服務開發的需求的。 這三個不同的目的下,選擇開源軟體的導向完全不一樣。 (注意:後兩個場景是需要先考慮企業開源合規的需求的,參見第三章)

先說說選擇開源軟體來進行個人學習,那麼需要看看個人學習的具體目的究竟是什麼。 是想學習一種比較流行的技術來完善個人的技術知識結構擴大個人技術視野;還是想看看相應的開源技術專案的具體實現,來作為內部專案技術開發的參考;還是想為了下一份工作進行有針對性的技術準備。不同的目的會導致不同的選擇。針對前者,顯然是什麼技術最流行選什麼,自己缺什麼選什麼;針對第二種目的,一般是對該技術領域的知名開源軟體或者創新性軟體進行有針對性的選擇,即某個特性是我當前需要的,或者是我當前專案實現不好的,我需要看看別人是如何實現的。最後一種,顯然是按照下一份工作的職位需要和技術棧要求進行準備,並根據技術棧要求的門檻高低進行選擇。但是注意,從個人需求出發選擇開源軟體,一般都需要寫個小專案練練手,比如一個Demo程式或者一個測試服務,因為不用考慮後續的長期維護,所以儘可以按照個人的想法和個人研發習慣進行各種練習,不用遵循企業內部的開發流程和質量要求,也不用考慮該開源軟體的穩定性和社群成熟度等情況,只需要盡情的學習和參考程式碼就好了。

然後看下一個需求,選擇開源軟體進行研發的軟體是需要提供給客戶的,往往可能還是以私有云的方式進行交付。基於此類需求來選擇開源軟體,注意作好平衡,即客戶的需求和企業自身技術規劃或產品的長期規劃需要。以私有云方式進入客戶的IDC環境,是需要跟客戶開發和執行環境的上下游專案進行整合的。這時候要看客戶的需要,可能某些客戶對開源軟體有特定的要求,例如要求使用HDFS而且是某個特定版本。對這類指定軟體名字和指定版本的要求,有可能是因為客戶當前比較熟悉這個版本,也有可能是因為之前其他軟硬體供應商提供的軟體和版本,指定的目的是方便整合和後續的使用與維護。如果這種需求是符合企業專案或者產品的長期發展需求的,則是可以完全滿足的。如果甲方非常強勢,除了滿足他的要求之外沒有別的辦法,那就選擇客戶所指定的軟體和版本好了。但是如果跟自身專案或產品的長期發展需求不一致,而且具體專案或者版本是可以跟甲方進行協商的,那麼需要跟客戶協商出一個雙方都能接受的結果出來,即選擇特定的開源軟體和版本既要做到客戶滿意並買單,又要做到自身的交付成本可控,還要做到符合自身專案或者產品的長期發展需要。例如客戶使用Java的某個老版本,但是企業的toB交付的軟體要求使用Java的較高版本。那麼需要跟客戶協商,要麼切換到企業希望的版本上,還需要幫助客戶完成已有系統的升級工作;要麼只能降低自身軟體的Java版本需求,可能還需要對某些自身程式碼進行修改,還可能對軟體中的某些依賴元件進行修改。這個場景下是帶有很多客觀約束條件下的選擇,是需要跟客戶,自身的產品經理和架構師一起協商的。

最後,如果場景是為了滿足內部服務的需求,即選擇開源軟體來搭建的服務是給內部業務或者終端使用者來使用的,常見於國內各大網際網路公司的網際網路服務系統和各種手機上的App。這時候專案的開發和維護方有較大的自主權,跟toB的交付業務完全不一樣。此時選擇開源軟體,就一定要綜合考慮開發和維護成本,還要考慮使用該服務的業務所處的階段。

1)如果提供的服務是給創新業務使用的,創新業務一般都是試錯業務,隨時需要根據市場情況的變化和當前執行的狀態進行調整,很可能三個月後這個專案沒了,即被取消了。這種情況下糙快猛的開發方式是比較合適的,不用太多考慮系統的可維護性和可擴充套件性,就用研發團隊最熟悉的軟體技術棧,然後用底層技術支撐團隊比如基礎架構團隊提供的成熟而且經過驗證後的底層基礎技術平臺就可以,最重要是儘快把系統搭建出來,然後隨著產品進行快速的迭代。這個時候需要儘量降低現有研發運維團隊的學習成本和開發成本,不用太多考慮可維護成本,因為需要糙快猛的把系統堆出來,驗證產品需求和商業模式是最重要的,時間最重要。如果發現有市場機會,就快速跟進,站穩腳跟之後可以採用省時間但是費資源的方式(俗稱堆機器)來進行擴充套件,或者採用邊開飛機邊換引擎的模式進行重寫都是比較划算的。對於處於創業階段的企業或者專案來說,速度勝過一切。

2)但是如果選擇開源軟體搭建出來的計算機軟體系統或者服務,是需要長期維護的,比如是給公司內成熟業務使用的,或者是針對公司內成熟平臺的缺點進行系統升級並要替代原有產品的,那麼在滿足業務需求的前提下,考慮系統的可維護性變成最重要的事情。選擇對應的開源軟體,它是否成熟,是否穩定;二次開發是否友好;運維成本是否比較合算即比較省機器和頻寬;運維操作是否方便,例如常見的擴容和縮容操作是否可以高效、自動、無損的完成;Upstream到上游開源社群是否容易等等,這些都成為需要重點考慮的事情。這種情況下,開發一個系統的成本,可能只佔整個系統生命週期內的成本的1/10不到。所以在滿足需求的前提下,重點考慮可維護性。

1.2 根據技術發展趨勢來選擇開源軟體

如上圖所示,現代計算機軟體或者服務的研發,是一個不斷執行的迴圈和迭代過程。從市場分析開始,然後進入到創意階段,再到編碼階段,最後到上線階段完成應用的部署和生效,上線之後根據得到的資料反饋,繼續進行分析。這個迴圈迭代的過程,顯然對於一個身處競爭激烈的行業的企業來說,迭代的速度越快越好,同時也需要具備快速彈性、低成本伸縮的能力,即產品方向對了,那麼趕緊進行系統擴容,承接快速增長的流量,做到快速增長;如果產品方向不對,需要趕緊縮容,把相關硬體和人力資源節省出來,投入到新的試錯方向上去。身處同一個行業內的企業,如果企業A能以更低的成本,更快的速度的進行各種產品和策略的迭代,顯然它是能比迭代速度慢,成本高的企業B是有更好的競爭優勢的。

現在的開源軟體數量非常多,幾乎每一個分類下面都有很多的開源專案。針對某一個具體的需求,如何進行選擇?一個建議是根據技術趨勢進行選擇。即現在的計算機系統迭代的方式是Agile(敏捷) + Scale(擴充套件)。顯然,能夠支援計算機系統進行快速迭代,並能夠很方便進行低成本彈性伸縮的開源軟體是值得進行長期投入的。而對一個新的開源軟體的學習和使用,學習者是希望該軟體的學習門檻越低越好。一個流行的開源軟體,內部實現可以儘可能的複雜,但是對於使用者來說一定是需要使用者友好的。不然即使創新度再好,易用性不好,只有極客才能學習和掌握,創新的鴻溝是很難跨越的。

例如Docker的出現之後,以極快的速度風靡全球,非常多的工程師喜歡上了Docker。就是因為Docker的特性,在傳統的容器系統之上增加了新特性,包括把應用程式和底層依賴庫封裝為一個容器映象,容器映象有版本,而且可以通過集中的映象倉庫進行儲存和大批量分發。Docker首先解決了長期困擾工程師的開發、測試、上線環境標準化的問題,能夠支援開發者進行快速的迭代。同時使用了統一的映象倉庫來進行映象的分發,而且底層採用了輕量級虛擬機器即容器的技術,可以非常快的被拉起,所以採用Docker的系統可以很方便的進行彈性擴充套件。同時,因為把應用App封裝在一個映象裡面,可以在邏輯上根據Domain Model的設計原則進行更好的抽象和複用。顯然,這樣的技術是值得每一個開發計算機系統的工程師學習和掌握的。因為他能帶來極大的方便。相反,在Docker產生之前,雖然Control Group(簡稱cgroup + Namespace的技術早就已經出現,並早就整合在Linux核心中,Googleborg相關的論文早就已經發表,但是一般的技術研發團隊不是很容易就能駕馭容器並把容器系統在公司內部大規模進行部署的。印象中borg論文出現後,國內只有BAT級別的網際網路公司,才有一小撮精英研發團隊來研發和使用容器管理系統,例如百度負責Matrix系統研發的團隊,阿里負責Pounch系統研發的團隊,騰訊也有一個小團隊負責容器系統的研究。但是除了那一小部分團隊,更多的工程師因為相對較難的學習難度而沒有把容器大批的用起來。而Docker這種技術,就是非常好的順應了敏捷和彈性擴充套件的技術趨勢,而且提供了非常好的使用者易用性,然後一出場就被非常多的工程師迅速使用上了,而且成為市場的預設標準。

這些順應潮流的開源軟體是值得選擇和投入的。

另外一個例子是SparkSpark的出現解決了MapReduce在分散式計算過程中因為需要頻繁進行IO操作導致的效能比較低下的問題,同時在易用性上有較大的提升,所以才取代了MapReduce在分散式計算領域內的主流地位。

1.3 根據開源軟體採納週期的不同階段進行選擇

軟體作為智力活動的產物,他有他的生命週期,一般用軟體的技術採納曲線表示。

開源軟體也是軟體的一種,也都是遵循軟體的技術採納規律的。如下圖所示:

一個開源軟體從建立到衰亡一般會經過5個階段。 從創新期(Innovators,佔比2.5%),到早期採納期(Early Adopters,佔比13.5%),然後跨越鴻溝(chasm),進入到早期大眾期(Early Majority,佔比34%),再進入後期大眾期(Late Majority,佔比34%),最後進入衰退期(Laggards,佔比16%)。絕大部分的開源創新專案,沒有能成功的跨域鴻溝,即從早期採納階段進入到早期大眾階段,就消亡掉了。 所以,如果是選擇一個需要長期使用並維護的開源專案,選擇處於早期大眾或者後期大眾狀態的專案是比較理智和科學的。

當然如果只是個人想學習一個新的東西,可以看看處於創新者狀態的開源專案,或者看看處於早期採納者狀態的專案。

注意不管是從長期研發系統的角度,還是從個人學習的角度,都不要再去看處於衰退期(Laggards)的專案了。 例如現階段即2022年,是不用再去選擇MesosDocker Swarm之類的專案了。自從Kubernetes成為容器排程技術分類的預設標準,這兩個專案就已經處於衰退期,他們的母公司都已經放棄了。這個階段如果還投入較多精力來開發和維護,除非真的是非常強勢的甲方要求,把錢砸在工程師面前逼的不得不用才會選擇。

同學們可能會問,從哪裡可以看到這些技術採納度曲線?

InfoQgartnerthoughtworks每年都會更新他們各自的技術採納度曲線並公佈出來, 大家可以在網上搜索一下,看看他們各自的技術採納圖是什麼,然後結合一些業內的經驗,得出自己的判斷。

例如 https://con.infoq.cn/conference/technology-selection?tab=bigdata

從這裡能看到2022InfoQBigData領域各種流行技術的判斷。 

從上圖可以看出,HudiClickhouseDelta Lake等開源軟體還處於創新者的階段,即在工業界採納還比較少,對於想學習新專案的同學是可以重點關注的。但是現在這些開源軟體還不適合應用在需要長期維護的成熟應用場景裡面。

注意這些知名科技媒體的技術採納曲線是每年都在更新的,在進行參考的時候別忘了注意一下發表的時間。

1.4 根據開源軟體的成熟度情況選擇開源軟體

還有一點,即根據開源軟體本身的成熟度來選擇開源。 即從這個開源軟體是否定期釋出,是否處於一個多方維護的狀態(即使一個公司的戰略發生了變化不再繼續維護了,還有其他的公司在長期支援),是否文件比較齊全等多個維度來進行成熟度的評估。

對於開源軟體的成熟度模型,開源社群有很多度量開源專案的成熟度模型,其中Apache開源軟體基金會的專案成熟度模型是比較有名的。

可以參考這裡: https://community.apache.org/apache-way/apache-project-maturity-model.html

按照這個Apache開源軟體基金會制定的開源專案成熟度模型,他把一個開源專案的評估緯度,分為7個維度:

  • Code(程式碼)
  • License and Copyright(軟體許可證和版權)
  • Release(釋出)
  • Quality(質量)
  • Community(社群)
  • Consensus Building(共識共建)
  • Independence(獨立性)

每個緯度又有幾個考察項。例如針對Independence(獨立性),又有兩個考察項,其一是看這個專案是否獨立於任何公司或者組織的影響,其二是看貢獻者在社群內活動是代表他們個人,還是作為公司或者組織的代表出現在社群並進行活動的。

Apache基金會Top Level的專案即頂級專案,在畢業階段都會從這些維度進行綜合的判斷。只有各方面都達標的專案,才會被允許從Apache基金會的孵化狀態中畢業而成為成為Top Level的專案。這也是逼著個人比較喜歡Apache頂級專案的原因。

另外,OpenSSF專案的Criticality評分(參見https://github.com/ossf/criticality_score)也是一個不錯的參考指標,它會度量一個專案的社群貢獻者數量、提交頻度、發版頻度、被依賴的數量等指標,來判斷一個開源軟體在開源生態中的重要程度。這裡就不詳細展開了,有興趣的同學可以參考它的資料,個人認為是一個值得參考的方向,但是這個評分還處於早期階段,距離理想狀態還比較遠。

1.5 根據專案的質量指標來進行選擇

很明顯,有些開源軟體的程式碼質量是比其他開源軟體的質量好。 有的時候需要從專案的質量情況來選擇開源軟體。

這個時候,我們需要檢視一些被業內廣泛證明比較有效的指標。

其中MTTU是被知名開源供應鏈軟體供應商SonaType所推薦的指標。 它在它著名的供應鏈年度報告裡面提到MTTU 參見https://www.sonatype.com/resources/state-of-the-software-supply-chain-2021

MTTUMean Time to Update):即開源軟體更新它依賴庫的版本的平均時間。舉個例子來說,某開源軟體A依賴於開源庫B,假設A的當前版本是1.0,依賴B的版本是1.1。某天開源庫B的版本從1.1升級到了1.2,然後一段時間之後,開源軟體A也釋出了新版本1.1,其中把對B的依賴版本從1.1升級到了1.2。這個時間間隔,即從開源版本B的版本升級到1.2的時間點距離開源軟體A的新版本1.1的釋出時間,稱之為Time to Update,反映出來的是開源軟體A的研發團隊,根據依賴庫的更新週期,同步更新它的依賴版本的能力。Mean Time to Update是指這個軟體的平均升級時間。數值越低表明質量越好,表明該軟體的負責人在很快速的升級各種依賴庫的版本,在及時修復各種依賴庫引起的安全漏洞問題。

SonaType的統計,業內開源軟體的更新升級時間MTTU越來越短。 據它的統計,在Maven中心倉庫上的Java類開源軟體,2011年平均的MTTU371天,2014年平均的MTTU302天,2018年平均的MTTU158天,而2021年平均的MTTU時間是28天。能看出來,隨著開源軟體庫更新頻率的加快,使用它們的軟體也隨著加快了更新版本的速度,MTTU相對10年前,時間縮短到原來的10/1以下。

當然MTTU只有專案質量的一個間接緯度。 歷史上是否爆出重要高危安全漏洞,修復響應是否快速及時,等等也是作為開源專案質量評價的重要維度。

某些大廠的安全部門,會不斷評估開源軟體的安全情況,把某些屢屢發生高危安全漏洞,但是修復不及時的開源軟體設定為不安全軟體,列入到內部的開源軟體黑名單中對內公示,並要求各個業務研發團隊不再使用這些軟體,實在因為研發和人力問題不能遷移到新的軟體系統的情況也需要把這些老服務遷移到一個相對封閉的網路環境中,減少風險可能造成的損失。這個時候,顯然應該需要遵守公司的安全規定,不再使用黑名單上的開源軟體。

1.6 從開源軟體所屬於的開源社群治理模式角度來考慮。

還有一個維度,即從這個開源專案的社群治理模式來考慮,適用於需要長期進行開發和維護的專案。

社群治理模式(Governance Model)主要是指該專案或者社群是如何做決定的以及由誰來做決定。 具體表現為: 是所有人都可以做貢獻嗎還是少數幾個? 決定是通過投票的方式產生的,還是通過權威?計劃和討論是否可見?

常見的開源社群和開源專案的治理模式有如下三種:

  1. 單一公司主導:特點是軟體的設計、開發和釋出都由一個公司來控制,也不接受外部貢獻。開發計劃和版本計劃不對外公開,相關討論也不對外公開,版本釋出時候才對外公開原始碼。例如GoogleAndroid系統。
  2. 獨裁者主導(有個專有名詞“Benevolent Dictatorship”,翻譯為仁慈的獨裁者):特點是由一個人來控制專案的發展,他有強大的影響力和領導力,一般都是該專案的創始人。例如Linux KernelLinus Torvalds來負責,Python之前由Guido Van Rossum來主導。
  3. 董事會主導:特點是有一撥人構成專案的董事會來決定專案的重大事項。例如Apache軟體基金會的專案由該專案的PMC決定,CNCF的基金會的決策是CNCF董事會來負責(很多技術決定授權給了CNCF董事會下的技術監督委員會)。

個人意見和經驗,根據該開源軟體背後的開源社群的治理方式來進行選擇優先順序的排序如下:

  1. 優先選擇Apache畢業專案(因為這些專案的智慧財產權情況清晰,而且至少有三方在長期維護)
  2. 次優選擇Linux基金會等其他開源基金會的重點專案(因為Linux基金會的運營能力很強,每個重點專案後面往往都有一個或者多個大公司在支援)
  3. 小心選擇一個公司主導的開源專案(因為該企業的開源戰略隨時可能會調整,很有可能不再持續支援該專案,例如Facebook就是一個棄坑很多的公司)
  4. 儘量不選擇個人開源的專案(個人開源更加隨意,風險尤其高,但是不排除某些已經有很高知名度,並且跑出長期維護模式的專案,例如知名開源作者尤雨溪(Evan You)所負責的Vue.js開源軟體)。

這是個人推薦的選擇同類開源軟體專案的優先順序順序,僅僅代表個人觀點,歡迎討論。

2. 如何定製和維護

把一個開源軟體引入到企業內部後並用來進行開發和長期維護,就出現瞭如何定製和維護的問題了。 首先要明確,開源軟體引入到企業內部之後是需要定製的。 因為如下幾個理由:

  1. 開源軟體往往都是適用於通用場景,考慮的情況比較多,需要支援各種各樣的使用場景。但是引入到企業內部之後,往往只需要針對企業特定的場景。所以針對這些特定場景進行優化,例如對全部功能進行剪裁,去掉跟本場景無關的特性,針對特定場景進行效能調優和引數優化等,往往能取得更好的效能,例如可以抗更多的流量,節省機器成本的效果是驚人的。這也是常見的定製方法。
  2. 開源軟體進入企業內部要經過開發並長期運營,是需要滿足該企業的各種內部的服務運維規範的。例如業務上線,是需要有完整的日誌和監控,比如需要提供服務健康檢查介面,還需要有流量排程等容錯處理。這些都是需要進行定製修改的。
  3. 開源軟體還需要對接企業內部的上下游系統,例如如果該軟體的正確執行需要依賴底層的分散式儲存和分散式計算系統來完成基本功能,是需要對接企業內部已有的儲存系統或者計算系統的;企業內部的底層虛擬機器系統或者容器排程系統,往往有部分修改和優化,對接起來也是需要進行修改的;所以這個時候需要進行定製修改。
  4. 特殊場景下的需求定製,在企業應用場景下使用該開源軟體往往會遇到特定的問題,可能會碰到Bug,這些都需要Bugfix和新增特性來支援。

2.1 如何對開源軟體進行定製和修改?

對此,筆者建議有幾個基本原則: 不動開源軟體的核心程式碼,儘量使用該開源軟體已有的外掛機制;或者在外圍改;定期升級到開源社群的穩定版本。

很多開源軟體在設計之初,就留下了不少擴充套件機制,方便後續開發者進行功能擴充套件和特性增加。例如幾個最著名的開源軟體Visual Studio CodeFirefox Browser就提供了Extension機制,很多開發者根據自身需求開發對應的外掛,並把外掛提交到官方支援的外掛市場裡面。普通使用者在安裝完成主要程式後,還可以瀏覽外掛市場,尋找和選擇自己需要的外掛進行安裝。 另外像Kubernetes,也在多個地方提供了擴充套件機制,例如核心排程器哪裡提供了定製化的scheduler,可供開發個性化的排程策略;底層的儲存和網路都提供了很多的外掛機制;最值得稱道的是它提供了CRDCustom Resource Definition)的機制,允許開發者定義新的資源型別,並複用Kubernetes成熟的宣告式API和排程機制,進行很方便的操作和運維。 所以,儘量使用該開源專案已有的外掛或擴充套件機制來增加特性。

針對某些開源軟體的修改和定製,並不太適合使用它的擴充套件機制,或者它本身沒有提供可用的擴充套件機制。這個時候的修改,儘量在原始碼核心的外圍進行修改,而不要去動它的核心程式碼。因為開源軟體是隨著開源社群的進展不停的迭代的,開源社群的發展會不斷帶來更多更好的特性。如果對核心程式碼進行了修改,而當需要升級到比較新的開源版本的時候,就會非常的痛苦。因為有大量的內部Patch需要進行合併,而且需要各種測試,會導致升級成本過高而無法跟社群的主要版本進行同步,最後因為部分核心工程師的離職或者轉崗,那部分的修改沒人能持續維護下去,導致整個系統無法維護和升級,最後導致整個系統被廢棄或者被推倒重來,這會導致大量的人力成本。筆者在多家網際網路大廠工作過很多年,看到了太多這種專案,太多本來針對開源專案的修改,是非常有必要的,但是因為改動了核心程式碼,導致想升級到開源社群的較新版本成本太高,最後導致系統無人能維護,只好推倒重來的例子了。

舉個例子吧,筆者在某大廠內部看到有兩個技術團隊在維護Redis叢集,當時使用的版本都是Redis 2.x版本。因為沒有太多叢集功能,對大規模的業務支援不好,所以這兩個團隊都對Redis2.X版本進行了修改。其中團隊A的改法是在外圍改,即在Redis之上封裝了一層,用來進行流量排程,Failover處理等功能;團隊B就改的狠一些,直接改Redis的核心程式碼,把叢集功能相關的程式碼直接加了進去,甚至在某些區域性測試場景下,效能更好一下。短時間內,兩個團隊都能滿足業務線的需求。但是Redis開源社群在不停的迭代,不斷加入更多更好的需求,當Redis出到3.x的時候,兩個團隊都想升級到比較新的版本,因為使用Redis的業務方也希望使用3.x的版本。但是升級成本明顯不同,團隊A很快把相關功能移植到了3.x之上,很快把Redis版本升級了上去;團隊B呢,因為對核心的改動太大,移植成本和測試成本都太高,所以遲遲不能對2.x版本的服務進行升級。等到社群4.x版本出來,團隊B的核心工程師離職之後,該Redis叢集已經沒有人能夠持續維護並滿足客戶的新版本需求,只好推倒重來,從社群的4.X版本直接建叢集,自身的系統遷移化了很長時間,也給客戶帶來了很多成本。

所以,對開源軟體原始碼的修改,都建議以Local Patch(本地補丁)的方式存在,一來便於進行維護和升級,二來也方便管理和統計。這種模式下,內部專案的編譯指令碼,一般都是把該開源軟體的某個原始碼包解開,然後通過patch命令把這些Local Patch一一打進去,之後再一起進行編譯和測試。而不是把Patch直接打到業務原始碼裡面,雖然在CI階段省了幾分鐘,但是後續的維護、升級、管理卻增加了相當的麻煩。

2.2 回饋社群,Upstream(回饋)到上游開源社群,減少維護成本

工程師在企業內部針對某個開源軟體的某個版本,進行功能特性的增加或者Bugfix之後,一般會以Local Patch(本地補丁)的方式存在在程式碼庫中。筆者建議工程師在解決完業務問題之後,儘量把這些Local Patch提交到該開源軟體所屬的上游開源社群裡面去,完成Upstream的過程。

Upstream有如下幾個好處:

  • 能獲得更好的程式碼

在企業內部針對某開源軟體增加特性尤其是Bugfix的補丁,往往因為時間緊急,更多采用的是“Hack”的方式,即為了快速上線解決問題,補丁修復的地方不一定很合理,程式碼補丁的邏輯可能會有漏洞,程式碼補丁可能對更多異常條件的處理不夠完善等等。這個時候,如果把這個Local Patch提回到該開源專案所屬的開源社群裡面,跟該開源社群的資深工程師(Module Reviewer/模組負責人)等進行深入的溝通交流之後,往往會根據他們的反饋,對程式碼補丁進行更好的完善,從而能得到更好的程式碼。

  • 能減輕維護成本

內部保留的Local Patch,在每次升級到開源軟體較新版本的時候,這些Patch都是需要進行評估,部分需要和入並測試的。當然希望這些Local Patch的數量越少越少。最好的辦法就是當該開源社群釋出新版本的時候就已經包含了這些Patch。包含的數量越多,企業內部需要評估、需要合併和測試的Local patch數量就越少,升級起來成本就越低。記得Fedora的釋出版本里面,每個版本都保留了不少針對核心和其他元件的Local Patch,紅帽的工程師也在不斷的把這些Local Patch貢獻並和入到上游開源專案社群裡面,這樣才能保持Fedora內部的local patch數量在一個比較低的水平,也保證了升級版本時候的成本是比較可控的。

  • 建立團隊技術品牌和僱主品牌,方便招聘,並提升工程師自豪感,

向上遊開源技術社群貢獻程式碼,Upstream這些Local patch,是可以獲得更好的社群口碑的。向這些技術社群表明該公司不僅只是開源軟體的消費者,同時也是貢獻者。

同時可以建立起較強的團隊技術品牌,表明該公司不僅僅業務比較不錯,技術團隊也是很有實力的,這樣方便對外招聘。

Upstream到上游開源社群,同時也有利於提升團隊的工程師自豪感和滿意度。

舉個例子, 小米公司在大量使用Apache HBase專案的時候,負責的研發工程師堅決執行Upstream的策略,不斷的把小米內部驗證過的Patch貢獻回到HBase社群,並和Hbase社群的同學們一起進行某些特性的討論和研發。小米同學在HBase社群的影響力越來越大,不斷產生了CommitterPMC,最後小米工程師張鐸成為該專案的PMC負責人即專案的PMC Chair。小米公司在大資料、雲端計算等領域的技術品牌,很大程度上來源於此專案相關的研發團隊。

3. 個人成長如何利用開源

工程師的成長,跟他從事的日常工作密切相關,也跟他的日常學習密切相關。 在這個過程中,如何利用開源軟體,來更好的幫助工程師進行成長,幫助工程師實現他們的職業理想或者技術理想, 這裡有一些建議。

3.1 開放和共享,眼界和心態

站住巨人的肩膀上才能站的更高。 開源世界裡面有的是各種各樣的軟體,面向各種場景,解決各種問題。 所以一定要保持一個開放的心態,即在做什麼技術相關的事情之前,先看看別人是怎麼做的。 要知道世界這麼大,工程師遇到的問題99.99%以上都是別人已經遇到的問題,別人是如何解決的,有什麼經驗可以學習, 尤其是可以看看別人開源出來的專案,看看他們的設計文件,看看他們是如何思考的;看看他們的原始碼,看看他們是如何實現的。 如果感興趣,還可以進一步跟他們直接交流。 一來可以少走很多彎路,避免很多不必要的重複工作,避免重複踩坑。 二來不用重複造輪子,可以把有限的時間放在更有價值的工作上去。 千萬不要坐井觀天,老子天下第一,多看看工業界和開源界,剛畢業去大廠的同學尤其需要注意。

另外還需要共享的心態,學到了最好能共享出來, 讓別人也能參考,吸取經驗和教訓,從而達到共同提高的目的。

3.2 學習開源軟體的推薦步驟和方法費曼學習法

學習開源軟體有各種學習方法。 針對不同的學習目的,也需要根據自身情況(即對該領域的熟悉程度以及對相關開源專案的瞭解程度)等採用不同的更適合自身的學習方法。

筆者在這裡給大家推薦一個適合工程師學習一門全新的開源技術專案的方法:

  1. 先儘快入門,把這個開源軟體的Quick Start(快速入門)和Tutorial(入門教程)跑起來,先了解它的主要場景和關鍵特性。
  2. 然後看文件,注意系統的主要架構圖,瞭解整個系統的大致架構,建立起一個比較大的整體框架圖出來。
  3. 最後再結合自身的實際應用場景看相關細節,包括某個細節的文件和程式碼。

比如,如果想學習Kubernetes,先上它的官網,把它官網上提供的教程快速執行一遍 (https://kubernetes.io/docs/tutorials/kubernetes-basics/create-cluster/cluster-interactive/) 瞭解如何建立pod,如何訪問,如何更新,如何進行流量排程等等。 然後看它的架構圖,瞭解它的設計原則即宣告式程式設計,包括幾個核心元件Kube-apiserverkube-scheduler,kube-controller-manager, kubelet等的功能以及這些元件都是如何互動的; 最後再根據自身的業務場景需要,看看具體哪部分還需要更深入的瞭解。 例如需要加入自己的儲存方式,那麼看看相關的程式碼,參考其他友商的儲存方式的實現。

不建議一上來先捧著原始碼看,這麼看是沒有頭緒的,而且效率很低。 況且現在不少開源專案都Too Big了,而且迭代速度很快,很難有人能瞭解全部程式碼,而且從個人精力來說做不到,更別說也沒有必要。

注意學習一定要和應用結合起來,即要動手。紙上得來終覺淺,絕知此事要躬行古人云,誠不我欺,對於工程師來說尤其如此。 如果是想比較深入的瞭解一門新的技術,甚至有打算切換技術路線和職業賽道,那麼一定要更多的動手,要把這個開源軟體用起來,或者寫一點程式跑個Demo並執行在實驗環境裡面,最好解決一些身邊的實際問題。千萬別眼高手低,覺得一切都很簡單,但是真的要跑起來,用起來就千難萬難了。可以嘗試參加技術企業內的一些創新活動,例如hackthlon(黑客松)活動,把新學的技術用起來;或者寫一點點小工具,讓他跑起來,解決一點點實際問題。例如,如果要練習Python,寫一個爬蟲,每天去爬天氣預報網站上的資料,然後做一個簡單的查詢,可以獲得當前的天氣預報情況。在用中學,學以致用。

還有一個非常好用的學習方法就是費曼學習法。費曼學習法被認為是最有效、最強大的學習方法之一,親測管用。 步驟也很簡單,我把它簡化為如下三步。

  1. 先學習一門技術
  2. 把它講給普通人,讓他聽懂
  3. 如果聽眾沒有聽懂,回到第一步

通過這種方法,只有自己講解該項技術的用法和架構,並能讓普通工程師聽懂這個技術,才算是真的掌握了。

費曼學習法來源於諾貝爾物理獎獲得者理查德·費曼(Richard Feynman)。他是一位知名的理論物理學家,量子電動力學創始人之一,奈米科技之父,因其對量子電動物理學的貢獻1965年獲得諾貝爾物理學獎。他所提倡的學習方法,被稱之為費曼學習法。步驟雖然非常簡單,但是能把複雜的技術進行簡化,並讓普通工程師能聽懂的方式講出來,這需要對這門技術有深入的理解和掌握,還需要對一些專有名詞和概念進行類比、聯想來進行簡化。一般能做到這樣,就說明對這門技術已經達到了入門的程度,可以繼續進行後續的更深入的學習了。

另外參加業內一些著名課程的考試或者認證也是一種比較好的方式。例如對雲原生不熟悉的工程師,當他通過CKACertificated Kubernetes Adminitrator)認證Kubernetes管理員考試之後,這個認證可以驗證他具備一定的水平,已經建立起對Kubernetes常見操作和系統架構比較全面的瞭解了。

3.3 融入開源社群,終身個人口碑

最後一點,對於工程師來說,參與和融入到開源社群裡面積極貢獻,將會獲得終身的口碑,並能結交到終身的朋友,是十分有利於工程師的長期發展的。 在此,鼓勵工程師,可以選擇自己感興趣的開源專案和社群,並通過交流和貢獻,不斷在社群裡面成長。即使以後因為工作關係或者其他種種原因,不繼續在這個開源專案和社群中活躍了,但是他的貢獻將一直被承認。Apache開源軟體基金會有一句很有名的座右銘:“Merit never expires”(參見http://theapacheway.com/merit-never-expires/),就是說工程師在Apache開源軟體基金會專案和社群做貢獻獲得的認可,是永遠不會過時的。曾經是提交者,永遠是提交者。

在開源社群裡進行協作,也是工程師進行社交的一種方式。在這裡,能認識終身的朋友,能和他們一起工作和交流,對於工程師的成長也是非常有效的。很多開源社群的大牛,在社群裡面也是非常友善的,尤其對待新人,對待比較junior但是貢獻慾望比較強烈的工程師,更是願意手把手的教的。在這些大牛工程師的幫助和指引下,新人的成長是非常快的,而且沒有企業/部門/工作專案等帶來的天花板。即新人可以在自己感興趣的開源專案和社群裡面,憑藉虛心的態度和貢獻慾望,不斷和社群內的資深工程師進行交流和學習,可以帶來技術能力的飛速發展的。

另外,對於現在的工程師來說,很難有終身僱傭的企業,工程師在企業裡面也就是工作一段時間,然後隨著各種主動或者被動的變化,崗位或者就職企業也會發生變化。但是在開源社群貢獻所獲的認可,和建立起來的個人品牌和技術口碑,是永遠隨著個人的,不會因為公司或企業的情況而發生變動。能看到很多一直活躍在開源社群的人,雖然職業發生了很多變動,但是他們在開源社群的認可和品牌一直存在。這也是很多工程師突破職業內卷,突破平臺限制的一種好辦法。

在開源社群長期做貢獻,是利人利己的好事,鼓勵每一位有想法,有行動的工程師,都能找到自己喜歡並投入的開源專案和社群,並且融入進去。

3.4 如何在開源社群做貢獻

在開源社群,尤其是哪些尊重精英治理的社群(例如Apache基金會的專案),做貢獻越多,得到的認可越多。 但是很多時候,作為一個新人,要去開源社群做貢獻,並不是抬抬手就能做的,是需要先了解一些社群規則,然後遵守規則才能夠慢慢融入的。

1. 貢獻什麼?

在做貢獻之前,我們需要了解,對開源社群的貢獻並不是僅僅侷限於程式碼貢獻,寫程式碼增加功能或者Bugfix是貢獻,完善文件和測試用例是貢獻,報告使用問題是貢獻,寫部落格介紹專案和推薦專案也都是貢獻,這些都是在開源社群內被廣泛認可的貢獻。

很多社群的技術大牛,進入開源社群做貢獻是從提交測試報告開始的。比如當年Mozilla社群最年輕的架構師Blake Ross17歲就成為Mozilla社群最高技術決策層之一,並和另外一位架構師創立了Firefox專案),他最初進入Mozilla社群,是作為實習生,從測試開始的。

“Scratch your own itch!” 這是在開源社群流行很廣的一句話,意思是說在開源社群做貢獻,是需要解決自己的問題的。即在實際工作中遇到了問題,然後嘗試去解決,最後把解決的結果以社群接受的方式貢獻到社群。一般的情況是有個Bug或者問題影響使用者的實際應用,或者想增加一個新的功能來滿足企業的自身場景,或者就是想學習一些新的技術。這種解決自身需求的貢獻,是比較長久的。而為了一些蠅頭小利,參與社群運營的一些活動獲取獎勵,對工程師來說只是for fun,這種貢獻也不是長久的。

所以,對於一個新人,進入到開源社群裡面,貢獻可以從一些簡單的問題開始,從解決自身的需要開始。 一個最簡單的例子,先看新手入門文件,照著文件描述的步驟一步步走下去,看看能否走通;如果走不通,可以報一個Bug出來;或者親身體驗需要增加一些額外的步驟才能走通,可以給這個新手入門文件提一個Patch,把這些補充步驟描述出來,這也是社群很歡迎的貢獻。

有些社群把一些簡單的Bug設定為“Good First Issue”,貢獻者可以挑選這些Issue來進行貢獻,來熟悉貢獻流程,並融入到社群裡面。

2. 瞭解現有社群情況,尊重社群的慣例和習慣

給開源社群做貢獻的第一步是先了解社群。

可以通過社群的網站、郵件列表、Wikigithub程式碼倉庫中的文件等資料,瞭解該開源社群的一些基本情況。

通過檢視關鍵文件(Contributing.md),瞭解這些專案的貢獻流程和推薦方法。

注意每個開源社群都有自己的慣例,比如他們有自己的Issue管理系統(有的可能用githubIssue,有的使用Bugzilla,有的使用Jira),然後提交Patch的流程和要求也不一樣。

例如歷史非常悠久的Apache HTTP Server專案,它對貢獻者的要求如下:

  1. Patch需要符合他們的Code Style
  2. 對程式碼質量也有一些例如執行緒安全的要求
  3. Patch需要針對當前的開發版本 –2.5.X來做比較
  4. Patch的格式使用diff -u file-old.c file.c 來生成
  5. 提交Patch的入口在bz.apache.org/bugzilla,建議加上“PatchAvailable”關鍵字
  6. 可以在mail list中發郵件來討論,郵件的title需要為[PATCH ]

注意他們採用的方式並不是github上流行的Fork/Pull Request模式,而是更古老的Bugzilla+Diff Patch的模式, 請尊重他們的工作習慣,使用他們要求的模式。(說句老實話,筆者20年前在Mozilla社群做貢獻的時候,工作方式也是採用Bugzilla + Diff Patch的方式。二十多年過去了,ApacheHTTP Server專案的工作模式並沒有發生大的變化。不過工作方式不影響貢獻,熟悉並習慣就好。)

有的開源社群,會提供一種遊戲化的貢獻流程,即讓開發者通過一系列簡單的新手任務來熟悉專案和貢獻流程。這種方式是對新人更加友好的,也是經過該社群的社群經理精心設計的。那麼對於貢獻者來說,別辜負了他們的良苦用心,走一遍自己覺得必要的任務,熟悉自己希望熟悉的任務和流程就好。

3. 態度需要“Be Polite and Respectful”,尊重社群的多樣性

開源社群裡面是充滿多樣性的。

開源社群內大部分的資深工程師對新人非常友好的,他們會很有耐心的教導新人,熟悉文件,熟悉貢獻流程等等(注意一般只有一次,別辜負了)。日常交流中,包括在郵件列表中,在IRC或者Slack頻道中,在Issue comments中,都比較nice。跟他們的溝通和協作比較容易。

但是注意,也有部分人相對態度不是特別好,如果遇上了,注意不用發生正面衝突。建議可以向社群更資深的一些工程師來求得幫助,而不用正面硬剛。不可能改變任何人,也不可能讓所有人都喜歡,完成必要的工作就好。

4. 如何快速找到負責程式碼ReviewModule Owner,完成貢獻

有的時候,按照社群貢獻流程的文件走下去,不管是提Issue或者報Bugs,發現模組負責人反饋很慢的時候,這個時候有一些技巧。

可以加入他們的IRC或者Slack頻道,找到對應的模組負責人,然後跟模組負責人進行禮貌和有建設性的對話。

跟他們建立良好的關係,並通過實際的貢獻,逐步建立起他們的信任。

注意,開源社群的運作是以信任為基礎的。能獲得模組負責人的信任,是非常有利於之後的工作開展的。

5. 提交大Patch需要注意步驟

可能有的工程師反饋,我給某某開源社群提交一個非常好的特性,在我的公司內部工作環境內測試並驗證,效果非常好,效能表現非常出色。但是我把程式碼提交到上游開源社群的時候,發現社群並不看重這個特性,反而對我的Patch指指點點,挑出各種毛病。太麻煩了,太心累了,乾脆不貢獻了。

需要將心比心的想象一下,如果一個陌生人給你的專案提交一個很大的Patch,程式碼Review實施起來就很費勁,因為Patch比較大。雖然貢獻者說這個Patch很有用,實現了一股很厲害的功能,而且經過了他的驗證,但是他是否可靠,他是否能在社群裡面長期存在,他是否能夠及時修復他所提交的程式碼產生的問題,這些都是問號。所以在沒有建立起基本的信任之前(即提交了幾個小Patch並得到了和入),提交大的Patch是很費勁的。

另外,提交這個Patch的工程師往往並不瞭解這個開源社群的歷史,也許這個功能很早就在社群被討論過了,也許討論的結論是不需要做或者在別的地方來做。所以,不要盲目自信於自己的Patch,而是應該先跟社群的工程師先溝通這個場景和問題。

筆者建議貢獻的步驟如下:

  1. 如果判斷這個Patch比較大,那麼先在社群內討論問題,讓社群認可這個問題,同時也能獲得社群對這個問題的一些歷史資訊(如果有的話)
  2. 如果社群認可該問題,覺得現在應該要修復了,繼續討論解決思路
  3. 問題和思路已經被認可之後,並完成一點點設計之後,再討論具體的程式碼Patch
  4. Patch需要遵守社群的規範(CodeStyle、元件呼叫規範、測試規範、文件規範等)
  5. 做好心理準備,Patch可能需要反覆修改若干次才能最後被和入,可能需要把一個大的Patch拆成若干個小Patch,分批提交和入。必要的時候需要一定的妥協。

貢獻一個大的Patch,實現一個重要的功能,步驟雖然多,時間週期雖然長,但是完成之後,能得到社群的高度認可,往往是成為更高層級貢獻者的基礎。而且對於貢獻者個人來說,內心的滿足感和成就感也是非常足的。

6. 注意不要做以下的事情

  1. 提出一個Idea,希望別人來完成。

尤其是剛剛加入一個社群的時候,就提出社群需要做某些事情,但是自己不做,希望社群裡面的其他人來完成,這些意見往往是會被忽略的。有許多人,下車伊始,就哇喇哇喇地發議論,提意見,這也批評,那也指責,其實這種人十個有十個要失敗。這種人更是不受社群歡迎的。

提出一個問題,同時提供一個有建設性的解決辦法,而且自己要參加,可以邀請社群其他人來一起。這是比較推薦的做法。

2. 過於急切,缺乏耐心,而忽略了社群的慣例。

寧可速度慢一點,尤其是在社群對新人的信任感建立起來之前,要有耐心。筆者曾經見過一個剛進入開源社群的工程師,技術能力很強,但就是隻想快點把他的Patch和入進去。跟模組負責人的溝通的時候,態度雖然禮貌,但是對負責人給出的改進意見反應很敷衍。折騰過幾次之後,該貢獻者在該社群的口碑已經被損失殆盡,他相關的Bugfix和新功能開發進展很慢,他後來也黯然離開了該專案。

3. 不要碰紅線(即社群的行為規範所禁止的一些惡劣行為)

基本每個成熟的開源社群都有自己的行為規範(Code of Conduct),一般都會在該社群網站或者程式碼倉庫的顯著位置展示此檔案。

規範內容列舉出若干社群不歡迎的舉動,包括性別、種族、宗教等方面的歧視和冒犯。

注意不要有這些行為,可能有的行為在中國開源社群裡面並不被認為是大問題,但是在國際社群不一定是小事。

7. 在企業內部對上游社群做貢獻要注意合規問題

在企業內部給上游社群做貢獻,因為是把公司內部研發的結果公開出去,所以需要滿足公司內部的開源貢獻管理辦法。

每個公司對此的規定不太一致。例如谷歌鼓勵工程師貢獻到開源社群,但是要求工程師應該以google.com的郵件地址來進行貢獻,100行以下的貢獻不需要通過內部流程審批,但前提是專案沒有采用Google禁止的許可證(例如AGPLPublic DomainCC-BY-NC-*),此外還有一些硬性條件,參見Google OSPO的官網連結https://opensource.google/documentation/reference/patching 國內百度公司也是鼓勵工程師貢獻到開源社群,無論Patch大小都需要經過內部的電子流程,由該部門的技術總監進行審批,並交由百度的開源管理辦公室(OSPO)進行備案,以便後續開源辦公室的資料統計和對工程師的貢獻激勵提供資料支援等。

在企業內部給上游社群做貢獻的時候,往往會碰到該社群要求工程師簽署CLAContribution License Agreement,即貢獻許可協議)或者DCODeveloper Certificate of Origin,開發者原創申明)的事情。其中CLA又分為ICLAIndividual Contributor License Agreement)和CCLACoperation Contribution License Agreement,即企業級貢獻許可協議),其中ICLA是針對個人的,CCLA是針對整個企業的,即如果該企業簽署了CCLA之後,該企業內部的工程師再做貢獻就不用單獨簽署ICLA了。不簽署CLA的話,則不能提交PatchCLA條款的內容是貢獻者把自己的貢獻授權給社群來進行使用。此時請遵守公司內部的規定,相關的CLA條款可能需要經過公司內部的法務來進行Review。不過好在一些著名專案的CLA條款,例如Apache開源軟體基金會的專案都使用統一的CLA檔案,CNCF基金會的專案也是類似。這些著名專案的CLA條款,法務確認之後就沒有問題了。如果不是法務已經確認過的CLA,需要跟公司負責的法務進行諮詢,避免碰上一些對企業不利的CLA

總結

本文比較長,凝聚本人的很多心得和體會。

一直認為工程師是非常務實,非常努力的一幫人,是一群深深相信我們可以用程式碼來改變世界的人,是一群認為“Talk is cheapShow me the code”日拱一卒,功不唐捐的人。我一直認為開放、協作、務實是當代工程師最好的特性之一。

在開源世界裡學習、工作、分享,是工程師改變世界最好的途徑之一。