從運維視角來看何爲好的軟件架構 易於部署 易於擴容 易於監控 易於恢復

這是在滴滴內部混沌說meetup上分享的內容《DEV和SRE的換位思考》的上篇,歡迎交流探討

轉載自我自己的公衆號:https://mp.weixin.qq.com/s/23TomQBc6VkpnRDxjHFNJA

從軟件的整個生命週期來看,寫完第一個版本上到線上,只是萬里長征走完了第一步,很多研發人員就覺得OK了,大功告成,然後就想着去幹個別的新鮮的項目,這個做法顯然是不推薦的,如果時間允許應該把這個項目繼續優化,從運維角度來提升可運維性,提升穩定性和容災能力,這可是一個架構師必須掌握的技能,寫一些零零散散的業務邏輯真的不如好好提升架構能力。當然,廣泛的領域知識也是很重要的。領導對你的訴求肯定是既要又要還要,哈哈阿門。

從運維視角來看何爲好的軟件架構,一句話概括:就是不需要運維操心的,或者儘量少操心的,總結起來有四個易於:易於部署、易於擴容、易於監控、易於恢復。

易於部署

更少的環境依賴

依賴環境就意味着依賴運維的初始化,依賴別人的前置工作,但是別人是可信賴的麼?這是一個典型的風險點。

最典型的減少依賴的方式就是減少系統依賴和庫依賴,比如Google的所有C/C++程序都使用靜態編譯,就是爲了減少依賴,減少動態庫的版本衝突,對於agent類,這點體會尤爲明顯,因爲agent通常要部署到很多機器上,不同的線上機器環境各異,什麼情況都可能出現,少個包,少個命令,太正常不過了

解決環境依賴的典型利器就是Docker Image,容器技術其實出現的很早,爲啥到了Docker才火起來呢,就是歸功於Image這個機制上來了,沒有Image,Build、Ship、Run則無從談起。除了Docker Image,另一個解決依賴的手段是Buildpack機制,在Heroku和CloudFoundry上可以看到,Buildpack其實就是把用戶代碼編譯之後,和依賴一起打包,比如Java Web程序,Buildpack會先把源碼編譯打包成War,然後和Tomcat、JDK一起,打成一個包,稱爲Droplet,生產環境部署的時候就是直接去分發Droplet即可。

自動化的配置

自動化配置,是減少心智負擔的典型手段,如果無法做到自動化配置,儘量讓所有實例的配置相同,最煩的就是一個機器一個配置...

比如自動探測機器的運行環境,自動配置線程數,根據內存自動設置JVM參數等,都是典型的自動化配置的方式,當然,如果應用運行在容器環境,就要注意了,容器的隔離性比較差,獲取到的配置信息實際是所在宿主的配置,如果以此爲依據設置一些參數,比如JVM內存相關的參數,很可能會造成OOM

另外就是不同的環境不同的配置問題,如果有中心化配置自然比較好,如果沒有,最典型的做法就是把不同的環境配置分別準備一個配置文件,然後根據機器的一些信息自動判斷當前的環境,如果當前是在生產環境,就自動應用生產環境的配置文件,如果當前是測試環境,就自動應用測試環境的配置文件。那怎麼判斷當前所在環境呢?比如我們可以讓機器名做的規範一些,生產環境的機器都要在機器名裏帶有prod字樣,而測試環境的機器,都要在機器名裏帶有test字樣,以此,便可非常方便的區分了。

最後一個典型的配置就是關聯關係配置,比如a模塊要調用b模塊的接口,首先就要知道b模塊部署在哪些機器上,即對應的ip:port是什麼,我們稱爲endpoint,a模塊如果要把b模塊的endpoint列表寫死在配置文件裏,b模塊要擴縮容就比較麻煩了,需要通知a模塊去修改配置併發起a模塊的變更,崩潰...

典型解法有兩個,一個是名字服務註冊中心,即b模塊通過心跳的方式向註冊中心彙報自身的endpoint,然後a模塊再去註冊中心獲取b的endpoint列表,如果b的某個實例掛了,就不會心跳了,a模塊從註冊中心獲取到的endpoint列表,就會自動踢掉掛掉的實例。另一個就是加一個轉發層,比如lvs或者nginx,這個比較容易理解不再贅述。

易於擴容

儘量做到無狀態水平擴展

無狀態水平擴展,是最容易運維的服務形態,一般web服務都是這個類型,容量不夠機器來湊,無需擔心容量上限,因爲架構上無狀態可以通過加機器提升服務容量,代碼性能爛一點問題也不大...

負載均衡接入層在某實例掛掉時會自動摘除異常實例,服務不會受損或受損較輕,如果追求極致,在做服務發佈變更的時候,可以先摘掉部分實例的流量,然後去升級,升級完了再加回去,以此避免服務發佈時實例重啓造成的短暫請求失敗。

有狀態則自動路由流量

比如不同的實例有不同的狀態,有些請求只能交給leader處理,那可以引入選主機制,再slave節點收到請求之後轉發給leader節點,或簡單的將leader信息返回,讓客戶端去重新請求leader

本地數據通常是造成有狀態的根因,數據如有分片機制,擴容的時候儘量減少數據遷移,比如在中心存放數據key和數據location的對應關係,如果用算法來計算數據key對應的location,比如一致性哈希或者ceph的crush算法,則擴容會導致數據位置發生變更,導致數據遷移,遷移則要能控制遷移速度,避免影響正常請求的I/O,搞過ceph的對這一點應該印象深刻

儘量自動註冊進系統

需要避免擴容了某個組件,還要去修改其他組件(接入層除外)的配置方能生效,這個對運維是不小的心智負擔,複雜就帶來了風險。

易於監控

關鍵指標透出

每個模塊都要透出能夠反映自身是否健康的指標數據,比如Google的所有服務模塊,都會起一個http server,開一個/varz接口,訪問這個接口,就會返回該模塊的監控指標數據。當然,這個規範比較強,如無法提供/varz接口的方式,也可以內嵌監控埋點的sdk,或者直接打印日誌。

何爲關鍵指標?比如MQ服務的消息堆積量,比如支付模塊的微信/支付寶支付成功率,比如對象存儲的接口延遲。只有進程是否存活、端口是否在監聽這樣的監控指標遠遠不夠。

日誌規範全面

談到監控不得不談日誌,最常見的業務監控方式就是日誌,所以日誌得全面,級別得用對,日誌級別定義請參考《日誌級別規範》。公司需要制定統一的日誌規範,便於統一的自動化工具處理(比如日誌收集程序),日誌格式要能夠輕易用正則表達式抽取指標數據,便於後續監控策略配置。

易於恢復

自動容災

典型的比如系統自動檢測到某個機器掛掉,或者某個盤不可用,或者某個實例異常,自動處理;如果是無狀態服務,前端自會有負載均衡器(比如lvs、nginx、haproxy)做這個事情,當然,實例掛掉的時候,client很容易出現請求失敗,此時一定要做重試,而server端的接口則一定要做到冪等,負責client也不敢重試啦。

另外就是能夠退化處理,比如原本從redis讀取數據,速度快,redis掛了可以退化到mysql讀取。

能夠自保

比如流量過載,能夠限流熔斷,那要做限流首先得知道模塊能抗多大的量,這就要求模塊在上線之前有個壓測數據,根據壓測數據來配置限流閾值,提前壓測得知某個模塊的流量上限這個要求可以考慮放到運維准入標準裏。

能夠降級,比如原本網站有100個功能,扛不住的時候可以關掉部分邊緣功能,減少系統負載,保證主流程暢通。

外掛自愈

適用於某些故障有固化的處理腳本,並且公司有故障自愈平臺,可以用監控告警觸發這個腳本。在我司這種外掛自愈的任務每週跑幾千個,可以大幅釋放運維人力。

舉個簡單例子,我們可以配置各硬盤報警,使用率80%時觸發P3告警,不配置接收人,只配置自愈觸發邏輯,讓自愈腳本去清理硬盤無用日誌,使用率85%時觸發P2告警,配置接收人,正常來講永遠不會觸發,因爲P3的策略會自動化處理,讓硬盤使用率降下來,但是如果自愈系統出現問題,就需要這個P2的告警策略來兜底了。

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