天天看點

阿裡畢玄:我在系統設計上犯過的14個錯

<b></b>

下為全文:

在上篇《架構師畫像》的文章中提到了自己在系統設計上犯過的一些錯,覺得還挺有意義的,這篇文章就來回顧下自己近八年來所做的一些系統設計,看看犯的一些比較大的血淋淋的錯誤(很多都是推倒重來),這八年來主要做了三個基礎技術産品,三個橫跨三年的大的技術項目(其中有兩個還在進行中),發現大的錯誤基本集中在前面幾年,從這個點看起來能比較自豪的說在最近的幾年在系統設計的掌控上确實比以前成熟了很多。

<b>第1個錯</b>

在設計服務架構時,我期望服務架構對使用者完全不侵入,于是做了一個在外部放一個.xml檔案來描述spring裡的哪些bean釋出為服務的設計,這個版本釋出後,第一個小白鼠的使用者勉強在用,但覺得用的很别扭,不過還是忍着用下去了,到了釋出的時候,發現出現了兩個問題,一是這個xml檔案研發也不知道放哪好,是以到了釋出的時候都不知道去哪拿這個xml檔案。

這個設計的關鍵錯誤就在于在設計時沒考慮過這個設計方式對研發階段、運維階段的影響,後來糾正這個錯誤的方法是去掉了這個xml檔案,改為寫了一個spring factorybean,使用者在spring的bean配置檔案中配置下就可以。

是以對于一個架構師來說,設計時在全面性上要充分考慮。

<b>第2個錯</b>

服務架構在核心應用上線時,出現了前端web應用負載高,處理線程數不夠用的現象,當時處理這個故障的方式是復原了服務架構的上線,這個故障排查了比較長的時間後,查到的原因是服務架構用的jboss remoting在通信時預設時間是60s,導緻一些處理速度慢的請求占據了前端web應用的處理線程池。

上面這裡故障的原因簡單來說是分布式調用中逾時時間太長的問題,但更深層次來思考,問題是犯在了設計服務架構時的技術選型,在選擇jboss-remoting時沒有充分的掌握它的運作細節,這個設計的錯誤導緻的是後來決定放棄jboss-remoting,改為基于mina重寫了服務架構的通信部分,這裡造成了服務架構的可用版本釋出推遲了兩個多月。

是以對于一個架構師來說,在技術選型上對技術細節是要有很強的掌控力的。

<b>第3個錯</b>

在服務架構大概演進到第4個版本時,通信協定上需要做一些改造,突然發現一個問題是以前的通信協定上是沒有版本号的,于是悲催的隻能在代碼上做一個很龌蹉的處理來判斷是新版本還是老版本。

這個設計的錯誤非常明顯,這個其實隻要在最早設計通信協定時參考下現有的很多的通信協定就可以避免了,是以這個錯誤糾正也非常簡單,就是參考一些經典的協定重新設計了下。

是以對于一個架構師來說,知識面的廣是非常重要的,或者是在設計時對未來有一定的考慮也是非常重要的。

說到協定,就順帶說下,當時在設計通信協定和選擇序列化/反序列化上沒充分考慮到将來多語言的問題,導緻了後來在多語言場景非常的被動,這也是由于設計時前瞻性的缺失,所謂的前瞻性不是說一定要一開始就把未來可能會出現的問題就解掉,而是應該留下不需要整個改造就可以解掉的方法,這點對于架構師來說也是非常重要的。

<b>第4個錯</b>

在服務架構切換為mina的版本上線後,釋出服務的應用重新開機時出現一個問題,就是發現重新開機後叢集中的機器負載嚴重不均,排查發現是由于這個版本采用是服務的調用方會通過硬體負載均衡去建立到服務釋出方的連接配接,而且是單個的長連接配接,由于是通過硬體負載均衡建連,意味着服務調用方其實看到的都是同一個位址,這也就導緻了當服務釋出方重新開機時,服務調用方重連就會集中的連到存活的機器上,連接配接還是長連,是以就導緻了負載的不均衡現象。

這個設計的錯誤主要在于沒有考慮生産環境中走硬體負載均衡後,這種單個長連接配接方式帶來的問題,這個錯誤呢還真不太好糾正,當時臨時用的一個方法是服務調用方的連接配接每發送了1w個請求後,就把連接配接自動斷開重建,最終的解決方法是去掉了負載均衡裝置這個中間點。

是以對于一個架構師來說,設計時的全面性要非常的好,我現在一般更多采用的方式是推演上線後的狀況,一般來說在腦海裡過一遍會比較容易考慮到這些問題。

<b>第5個錯</b>

服務架構在做了一年多以後,某個版本中出現了一個嚴重bug,然後我們就希望能通知到用了這個版本的應用緊急更新,在這個時候悲催的發現一個問題是我們壓根就不知道生産環境中哪些應用和機器部署了這個版本,當時隻好用一個臨時的掃全網機器的方法來解決。

這個問題後來糾正的方法是在服務釋出和調用者在連接配接我們的一個點時,順帶把用的服務架構的版本号帶上,于是就可以很簡單的知道全網的服務架構目前在運作的版本号了。

是以對于一個架構師來說,設計時的全面性是非常重要的,推演能起到很大的幫助作用。

<b>第6個錯</b>

服務架構這種基礎類型的産品,在釋出時會碰到個很大的問題,就是需要通知到使用者去釋出,導緻了整個釋出周期會相當的長,當時做了一個決定,投入資源去實作完全動态化的釋出,就是不需要重新開機,等到做的時候才發現這完全就是個超級大坑,最終這件事在投入兩個人做了接近半年後,才終于決定放棄,而且最終來看其實更新的問題也沒那麼大。

這個問題最大的錯誤在于對細節把握不力,而且決策太慢。

是以對于一個架構師來說,技術細節的掌控非常重要,同時決策力也是非常重要的。

<b>第7個錯</b>

服務釋出方經常會碰到一個問題,就是一個服務裡的某些方法是比較耗資源的,另外的一些可能是不太耗資源,但對業務非常重要的方法,有些場景下會出現由于耗資源的方法被請求的多了些導緻不太耗資源的方法受影響,這種場景下如果要去拆成多個服務,會導緻開發階段還是挺痛苦的,是以服務架構這邊決定提供一個按方法做七層路由的功能,服務的釋出方可以在一個地方編寫一個規則檔案,這個規則檔案允許按照方法将生産環境的機器劃分為不同組,這樣當服務調用方調用時就可以做到不同方法調用到不同的機器。

這個功能對有些場景來說用的很爽,但随着時間的演進和人員的更換,能維護那個檔案的人越來越少了,也成為了問題。

這個功能到現在為止我自己其實覺得也是一直處于争議中,我也不知道到底是好還是不好…

是以對于一個架構師來說,設計時的全面性是非常重要的。

<b>第8個錯</b>

服務架構在用的越來越廣後,碰到了一個比較突出的問題,服務架構依賴的jar版本和應用依賴的jar版本沖突,服務架構作為一個通用技術産品,基本上沒辦法為了一個應用改變服務架構自己依賴的jar版本,這個問題到底怎麼去解,當時思考了比較久。

可能是由于我以前osgi這塊背景的原因,在設計上我做了一個決定,引入osgi,将服務架構的一堆jar處于一個獨立的classloader,和應用本身的分開,這樣就可以避免掉jar沖突的問題,在我做了引入osgi這個決定後,團隊的1個資深的同學就去做了,結果是折騰了近兩個月整個比對osgi的maven開發環境都沒完全搭好,後來我自己決定進去搞這件事,即使是我對osgi比較熟,也折騰了差不多1個多月才把整個開發的環境,工程的結構,以及之前的代碼基本遷移為osgi結構,這件事當時折騰好上線後,效果看起來是不錯的,達到了預期。

但這件事後來随着加入服務架構的新的研發人員越來越多,發現多數的新人都在學習osgi模式的開發這件事上投入了不少的時間,就是比較難适應,是以後來有其他業務問是不是要引入osgi的時候,我基本都會建議不要引入,主要的原因是osgi模式對大家熟悉的開發模式、排查問題的沖擊,除非是明确需要classloader隔離、動态化這兩個點。

讓我重新做一個決策的話,我會去掉對osgi的引入,自己做一個簡單的classloader隔離政策來解決jar版本沖突的問題,保持大家都很熟悉的開發模式。

<b>第9個錯</b>

服務架構在用的非常廣了後,團隊經常會被一個問題困擾和折騰,就是業務經常會碰到調用服務出錯或逾時的現象,這種情況通常會讓服務架構這邊的研發來幫助排查,這個現象之是以查起來會比較複雜,是因為服務調用通常是多層的關系,并不是簡單的a–&gt;b的問題,很多時候都會出現a–&gt;b–&gt;c–&gt;d或者更多層的調用,逾時或者出錯都有可能是在其中某個環節,是以排查起來非常麻煩。

在這個問題越來越麻煩後,這個時候才想起在09年左右團隊裡有同學看過g家的一篇叫dapper的論文,并且做了一個類似的東西,隻是當時上線後我們一直想不明白這東西拿來做什麼,到了排查問題這個暴露的越來越嚴重後,終于逐漸想起這東西貌似可以對排查問題會産生很大的幫助。

到了這個階段才開始做這件事後,碰到的主要不是技術問題,而是怎麼把新版本更新上去的問題,這個折騰了挺長時間,然後上線後又發現了一個新的問題是,即使服務架構具備了trace能力,但服務裡又會調外部的例如資料庫、緩存等,那些地方如果有問題也會看不到,排查起來還是麻煩,于是這件事要真正展現效果就必須讓trace完全貫穿所有系統,為了做成這件事,n個團隊付出了好幾年的代價。

是以對于一個架構師來說,設計時的全面性、前瞻性非常重要,例如trace這個的重要性,如果在最初就考慮到,那麼在一開始就可以留好口子埋好伏筆,後面再要做完整就不會太複雜。

<b>第10個錯</b>

服務的釋出方有些時候會碰到一個現象是,服務還沒完全ready,就被調用了;還有第二個現象是服務釋出方出現問題時,要保留現場排查問題,但服務又一直在被調用,這種情況下就沒有辦法很好的完全保留現場來慢慢排查問題了。

這兩個現象會出現的原因是服務架構的設計是通過啟動後和某個中心建立連接配接,心跳成功後其他調用方就可以調用到,心跳失敗後就不會被調到,這樣看起來很自動化,但事實上會導緻的另外一個問題是外部控制上下線這件事的能力就很弱。

這個設計的錯誤主要還是在設計時考慮的不夠全面。

是以對于一個架構師來說,設計時的全面性非常重要。

<b>第11個錯</b>

在某年我和幾個小夥伴決定改變當時用xen的模式,換成用一種輕量級的“虛拟機”方式來做,進而提升單機跑的應用數量的密度,在做這件事時,我們決定自己做一個輕量級的類虛拟機的方案,當時決定的做法是在一個機器上直接跑程序,然後碰到一堆的問題,例如從運維體系上來講,希望ssh到“機器”、獨立的ip、看到自己的系統名額等等,為了解決這些問題,用了n多的黑科技,搞得很悲催,更悲催的是當時覺得這個問題不多,于是用一些機器跑了這個模式,結果最後發現這裡面要黑科技解決的問題實在太多了,後來突然有個小夥伴提出我們試用lxc吧,才發現我們之前用黑科技解的很多問題都沒了,哎,然後就是決定切換到這個模式,結果就是線上的那堆機器重來。

這個設計的主要錯誤在于知識面不夠廣,導緻做了個不正确的決定,而且推倒重來。

是以對于一個架構師來說,知識面的廣非常重要,在技術選型這點上非常明顯。

<b>第12個錯</b>

還是上面這個技術産品,這個東西有一個需求是磁盤空間的限額,并且要支援磁盤空間一定程度的超賣,當時的做法是用image的方式來占磁盤空間限額,這個方式跑了一段時間覺得沒什麼問題,于是就更大程度的鋪開了,但鋪開跑了一段時間後,出現了一個問題,就是經常出現實體機磁盤空間不足的報警,而且删掉了lxc容器裡的檔案也還是不行,因為image方式隻要占用了就會一直占着這個大小,隻會擴大不會縮小。

當時對這個問題極度的頭疼,隻能是删掉檔案後,重建image,但這個會有個要求是實體機上有足夠的空間,即使有足夠的空間,這個操作也是很折騰人的,因為得先停掉容器,cp檔案到新建立的容器,這個如果東西多的話,還是要耗掉一定時間的。

後來覺得這個模式實在是沒法玩,于是尋找新的解決方法,來滿足磁盤空間限額,允許超賣的這兩需求,最後我們也是折騰了比較長一段時間後終于找到了更靠譜的解決方案。

這個設計的主要錯誤還是在選擇技術方案時沒考慮清楚,對細節掌握不夠,考慮的面不夠全,導緻了後面為了換掉image這個方案,用了極大的代價,我印象中是一堆的人熬了多次通宵來解決。

是以對于一個架構師來說,知識面的廣、對技術細節的掌控和設計的全面性都非常重要。

<b>第13個錯</b>

仍然是上面的這個技術産品,在運作的過程中,突然碰到了一個虛拟機中線程數建立太多,導緻其他的虛拟機也建立不了線程的現象(不是因為實體資源不夠的問題),排查發現是由于盡管lxc支援各個容器裡跑相同名字的賬号,但相同名字的賬号的uid是相同的,而max processes是限制在uid上的,是以當一個虛拟機建立的線程數超過時,就同樣影響到了其他相同賬号的容器。

這個問題我覺得一定程度也可以算是設計問題,設計的時候确實由于對細節掌握的不夠,考慮的不全導緻忽略了這個點。

是以對于一個架構師來說,對技術細節的掌控和設計的全面性都非常重要。

<b>第14個錯</b>

在三年前做一個非常大的項目時,項目即将到上線時間時,突然發現一個問題是,有一個關鍵的點遺漏掉了,隻好趕緊臨時讨論方案決定怎麼做,這個的改動動作是非常大的,于是項目的上線時間隻能推遲,我記得那個時候緊急周末加班等搞這件事,最後帶着比較高的風險上了。

這個問題主要原因是在做整體設計時遺漏掉了這個關鍵點的考慮,當時倒不是完全忽略了這個點,而是在技術細節上判斷錯誤,導緻以為不太要做改動。

是以對于一個架構師來說,對技術細節的掌控是非常重要的,這裡要注意的是,其實不代表架構師自己要完全什麼都很懂,但架構師應該清楚在某個點上靠譜的人是誰。