了解最新公司動態及行業資訊
內網車禍一直是互聯網公司盡量避免的事情,也是服務端程序員最關心的問題之一。 但是,如果我們統計一下各種內網事故的原因,我們會發現一個推論:內網事故70%的原因是在運維領域,而只有30%左右的事故原因是由于BUG造成的。程序本身造成的。 這里所說的“運維領域激勵”包括配置錯誤、現網運行錯誤、網絡或其他硬件環境發生變化、硬件故障等。 在盛行“運維”與“開發”分離的時代,雖然這是運維的錯,但這個錯已經扛了好六年了,一直沒有本質的進步。 說明這一技術問題不能僅僅通過單純的“區分責任”的管理方式來解決。 例如,當您需要重新部署一個包含數百個進程并分為數十種不同類型的服務的系統時,您可能需要小心處理數十個或數百個配置和操作命令。 這種配置和命令有很多相似之處,有些地方需要仔細辨別優缺點。 另外這個配置里面有很多相互關系,大家一定要理解的很清楚,并且對這個配置和命令的執行順序有嚴格的要求。 部署所有這些就像操作帶有數百個按鈕的機器面板。 最可怕的是,其中任何一個出錯,系統都可能立即引發“外網車禍”,或者在未來不可預知的時間引發“外網車禍”。 這導致老板半夜三點把你從床上拖到筆記本電腦前來處理這個爛攤子。 其實我說的這種情況并不是每個項目都會發生,我們確實在很多項目中都不同程度地陷入了類似的陷阱。 不知道我們是不是已經被復雜的httpd征服了。 conf,所以很多程序員都非常喜歡配置文件。 “一切都應該是可配置的!” 不僅成為了我們的口號,也成為了無數復雜的配置項和花哨的工具命令。 ——但這種事情,在實際的商業運作中,卻成了無數的定時炸彈。
【程序員愛配置文件】
手動測試已成為當今開發的標準程序之一。 尤其是在敏捷開發方法盛行之后,最重要的實踐之一就是手工測試。 我們知道,通常我們用于測試的環境往往與真實環境不同。 例如,我們在做功能測試時,運行被測程序的顯存和硬盤可能比實際運行環境要小很多。 如果我們的程序因為硬件或者IP地址等其他軟件的不同而需要配置運行,那么我們的每一個測試都可能需要手動操作。 不能說是“自動化”。 再加上人為失誤,更容易導致測試結果出現嚴重錯誤。 測試工作不僅環境不同會造成運維操作,自測也需要多個環境。 比如很多系統有多個分支同時開發,或者有內部功能測試、外部邀請用戶測試、公開測試等多個測試環境。 假設我們的軟件每次部署都需要大量的人工操作,面對多種環境,頻繁發布新版本進行測試,那么部署工作一定是非常繁重的,而這種繁瑣的工作本來是可以盡可能避免的。
【一個漂亮的CI閉環,往往毀于復雜的部署流程】
快速發展仍然是現代軟件公司追求的目標。 由于不斷變化的需求和市場,軟件產品和應用系統也被迫每晚更新其功能。 說到服務器端軟件,我們在開發過程中往往需要和很多其他程序一起開發調試。 最典型的就是與客戶端軟件進行交互。 假設每一個參與項目的程序員都集中連接到一個開發服務器進行調試,必然會產生各種相互影響。 而這些跨機開發環境往往只有一些命令行界面,效率不如圖形界面的IDE軟件。 假設我們開發的程序,尤其是服務端軟件,可以直接在開發的工作機上運行和調試,這樣不僅響應速度更快,而且在多個程序同時運行時也可以方便調試時間,這對于經常因“聯調”而頭疼的工程師來說,是一種非常有效的提高工作效率的措施。 但是上面提到的這些方法都需要我們的服務端程序,可以很方便的部署到各種環境中。 另一方面,有些服務器系統結構比較復雜,需要啟動很多進程,很多配置文件可以配對啟動,所以你肯定懶得部署很多套,而是擠在一個環境里為了發展。
分布式系統運維友好性難點
避免資源泄漏。 我們知道服務器端程序需要常年運行,我們非常擔心資源系列,比如顯存漏洞、文件句柄漏洞、網絡連接相關漏洞等等。 所以很多時候,我們愿意在服務器一啟動的時候就“占用”或者“分配”所有需要的資源,然后不管后續請求進來多少,做什么事情,都完全沒有必要了。 “分布”,從而避免一切“泄漏”。 但是這種方式也大大增加了程序運維的復雜度。 首先,我們無法明確硬編碼一個程序運行的硬件資源。 相反,我們設計諸如配置文件和命令行參數之類的東西來根據運行環境來確定可能使用的硬件資源。 比如我們會在配置文件中設計一個“網絡契約緩沖區大小”配置項,根據服務器的顯存大小進行配置。 此外,程序中的功能可能非常復雜。 如果要將使用的顯存、文件等所有資源都變成配置項,那么配置文件也必須極其復雜。 如果我們期望運維人員理解這個配置文件,最好還是讓開發人員自己運維,因為開發人員有時并沒有想清楚這些資源的合理分配——原因是過于依賴這些“預申請”表格被用來拖延解決疑難問題。 避免資源漏洞是一個重要的問題,但簡單地將資源申請變成配置文件也會帶來另一場災難。 可怕的是,這些配置文件災難將支撐多進程協作系統的幾何倍數增長。 這些運維復雜度在一個系統剛剛上線的時候其實是可以接受的。 然而,隨著系統逐漸變得龐大和復雜,運維工作的難度猶如冷水煮烏龜。 無法清理。 經過3-5年的運行,一些系統已經發展到沒有人可以從頭部署一個新環境的地步。
快速排除故障。 今天的商業應用系統往往不是一個非常簡單的功能體,而是包含大量相關或不相關的功能。 我們最擔心的是,這種聯動功能,在同時處理上千個網絡請求時,如果某部分功能代碼出現bug,導致整個系統不可用。 所以我們往往更喜歡構建某種隔離系統,比如在不同的進程中運行不同功能的代碼。 這樣,借助操作系統工具,就可以很快發現這些有問題的代碼。 但是如果真的要將一個系統的多個功能分離到不同的進程中運行,首先會遇到的就是進程間通信的問題。 這個問題是現代分布式系統的核心問題之一,無數的開源軟件項目都在試圖解決這個問題。 但是不管是使用開源軟件還是自己寫代碼解決,這樣都會增加系統的進程數。 特別是,我們喜歡按功能來定義代碼和流程,也就是說,在運維一個系統的時候,我們需要面對大量“不同類型”的流程。 而且我們定義的功能越詳細,流程的種類就越多,需要運維的流程也就越復雜。 在管理這種進程時,不僅需要配置后面提到的一些性能參數,還需要配置海量的進程間關系。 而這種進程間的關系會隨著業務的變化而變化。 對于這些沒有具體接觸開發需求的運維人員來說,簡直就是噩夢。 事實上,一些程序員開始在通信公司工作服務器運維,因此他們非常習慣按流程定義功能,按通信級別組織系統,而隨著業務系統越來越復雜,這些工作習慣帶來了很多麻煩——每周,可能需要向系統添加新進程,或者調整各個進程的通信關系。 不同的行業需要不同的技術方案,這是理性工程師的看法。
負載均衡。 現代服務器端系統基本上是分布式系統。 即由多個服務器、多個進程組合??起來提供服務的系統。 為了使該系統穩定工作,最常見的措施是避免過載。 為避免多個進程出現一定的過載,需要進行負載均衡。 為避免同類所有進程過載,需要過載保護。 分布式系統中最常見的配置任務之一是配置每種類型的進程啟動的數量以及每個進程的過載保護閾值。 但是在一個有幾千個進程,幾百臺服務器的系統中,要準確填寫這個配置其實是非常困難的。 特別是,此類服務器的性能并不像提供商所說的那樣一致。 如果需要在集群中增加一些服務器,或者改變(搬遷)個別服務器上的服務,這就更加危險了,因為稍有不慎就可能導致原有的工作系統出現故障。 但是,就像業務需求在不斷變化一樣,運維環境也在不斷變化。 比如搬遷IDC,就是最常見的“折??騰”。 我們可以編寫很多運維管理的工具,試圖將這些工作“自動化”。 然而,業務需求不斷“折騰”,在一些“開發運維分離”的團隊中,開發人員并不是很關心運維工具的開發,因為他們已經被市場和業務人員逼迫不斷加班,只求功能盡快上線。 由于負載均衡的需要,由此產生的大量服務器端軟件和內部工作量與集群中龐大的服務器數量有關,因此是服務器運維和開發困難的最直接體現端系統。
如何開發一個運維友好的服務端系統
為了讓服務器端系統運行良好,我們或許應該采取一些開發措施服務器運維,而不是簡單地依靠所謂的“運維”甚至更不可靠的“管理”方式來減少錯誤和故障。
第一個可以作為參考的想法是“構建具有性能靈活性的系統”。 所以,性能彈性,最簡單的說就是我們的服務器進程不需要復雜的配置文件,不需要運維操作,就可以運行在各種性能環境中。 除了監控機器的IP地址、內存大小等最簡單的自配置功能外,更重要的是我們對資源管理思想的提升。 因為一個系統要解決的問題可能比較復雜,需要用到的資源也會很復雜。 比如我們需要用顯存來緩沖沒收的網絡包,還需要用顯存來存儲用戶會話數據等等。 如果我們只是提出配置這么一塊顯存,各種顯存容量的配置就會有很多。 但是,我們可以通過構建業務表示來簡化這些資源模型。 例如,對于一個在線交互系統,我們可以將資源管理的單位定義為“會話”——每個會話代表一個“并發”服務,每個會話使用多少資源是我們可以設計的,然后我們去關注管理“會話”總數以避免資源泄漏。 實際上,這些“會話”在不同的業務系統中可能有不同的概念和作用。 幸運的是,我們還可以利用面向對象的思想,將此類會話及其相關數據用類和對象進行封裝。 這樣,我們在規劃性能的時候,就不用在程序中四處尋找用到“資源”的機器配置,而只需要抓取一個關鍵變量即可。 更重要的是,我們可以對“”等關鍵指標采用“池化”的管理策略,將這些對象的使用變成需要“申請/返回”的機制,從而摒棄“分配”一個的做法大量資源是根據實際需要分配資源。 由于“池”的限制,當資源達到上限時,拒絕進一步的服務請求,解決一些過載問題,同時避免資源漏洞。 保護的問題。 而且,在某些環境下,我們還可以讓這個“資源池”更加智能化和彈性化。 例如,當請求壓力接近上限閾值時,我們可以開始一些擴容或者上報工作,而不是簡單的拒絕服務。 或者我們可以定期查詢“已申請”資源的處理情況。 如果發現資源占用時間過長,我們可以清除那些服務請求,這樣對于自恢復服務有一定的靈活性。 如果構建具有“資源彈性”的系統能力,這樣的進程可以以最小的配置實現自我管理和運行。 從根本上降低了運維工作的復雜度,同時也增加了環境變化對系統的影響。 同時,良好表征的功能代碼對于代碼的維護和開發也非常有利,可以說是一舉多得。
第二個想法是“在功能容器下運行”。 在某個項目實踐中,我看到了某個系統,它的每一個流程都包含了整個系統的所有功能代碼。 通過啟動時的命令行參數,可以指定這個進程需要提供哪些功能。 就運維的便利性而言,這個系統遠比需要配置部署各種功能來送包的系統簡單。 而且該服務器系統還可以以單進程全功能的形式進行開發和人工測試,在開發效率上具有顯著優勢。 在JSP/技術的使用中,我們經常會把不同的部署部署到不同的容器(如/Resin等)中運行,而沒有完全配置各種容器。 現在仍然有一些系統使用/JS/Lua等腳本語言來編寫主要的業務功能。 系統中的流程部署,只要腳本容器(引擎)完成,基本上就是復制腳本文件。 . 在容器技術的支持下,我們不僅可以簡化部署的工作,還可以獲得一些“熱更新”的用處。 對于基于硬件和流量的運維工作,運維人員可以集中精力管理“容器”。 例如,它是一個高度手動的 容器。 用戶甚至根本不需要安裝和部署任何軟件。 他們可以直接上傳PHP腳本或類文件來開始提供服務。 服務器系統運行在容器下,也可以借助容器指定的一些通信規范進行一些手動運維,比如手動擴容、縮容、容災——容器可以自我發現運行狀態集群并添加新的運行資源,移除故障(如訪問超時)的運行資源。 這也是所謂的 SOA 概念的最常見實現。 從另一個角度來說,如果我們有容器的支持,我們在配置進程的時候就可以簡化整個集群中各種關系的配置,因為我們只需要告訴容器如何加入一個目標集群,其他的事情都允許容器與其他集群成員協商配置。 容器不僅提出了統一的功能代碼開發環境約束,還規范了運維工作。 這對于需要經常改變服務內容,不斷改變運行環境的項目來說是非常有價值的。 在WEB開發領域,容器的概念早已深入人心,因此這類系統應用廣泛,運維工作可以專業順利的進行,并且在領域沒有網絡游戲這樣的“行業標準”,功能容器的概念仍然沒有被很多人接受。 很多人還在埋怨自己為什么要給自己戴上這個“枷鎖”,殊不知自由總是在束縛下行走。
最后說一下各種運維工具,不管是Chef還是各種非通用的運維部署系統。 如果你只使用操作系統提供的能力,你希望統一管理所有的系統。 難的。 而如果我們在開發的時候充分考慮到系統的運維需求,那么我們可能只實現一些簡單的約束,就可以大大提高運維工作。 我想這也是所謂受歡迎的原因。 (來源:漢大)