簡介: 現在,幾乎全部的事情都離不開軟件,當你開車時,腳踩上油門,其實是車載計算機經過力度感應等計算輸出功率,最終來控制油門,你從未想過這會是某個工程師的代碼。html
做者 | 張羽辰(同昭)阿里雲交付專家前端
導讀:現在,幾乎全部的事情都離不開軟件,當你開車時,腳踩上油門,其實是車載計算機經過力度感應等計算輸出功率,最終來控制油門,你從未想過這會是某個工程師的代碼。
面向對象編程?函數式?模塊化設計?微服務?這些詞彙貌似都和架構這個 buzzword 有點關係,的確咱們這個領域充滿了不少難以理解的詞彙,這些詞彙從英語翻譯到中文已經喪失了部分上下文,再隨着上下文的改變使得意義完全扭曲,好比:引擎、框架、架構、應用、系統……誠然你們都或多或少對這些詞語達成共識,在工做中使用這些詞彙進行溝通,某時就是指「咱們都懂的那個東西」,可是在我深刻的想聊聊架構或者說軟件架構時,的確不得不問本身這個問題,咱們究竟是談論什麼?程序員
事實上,架構這個詞根據上下文所肯定的範圍較爲固定,建築學上的架構指代房屋結構、總體設計、組合構成等,而這些 high-level 設計每每並不須要全面瞭解底層,就像使用 RestTemplate 進行 WebService 調用時,咱們也不關心 socket 是在四層鏈接的同樣,由於細節被隱藏了。golang
可是,建築學上的架構與軟件架構卻又極大的不一樣之處,問題出如今「軟件」這個詞上,按照 software 的詞解,ware 是指產品同樣的東西,而 soft 則強調易變,這是與 hardware 所對應的。咱們但願「軟件」可以進行快速的修改,應該可以快速響應甲方或者客戶的需求,因此軟件架構必然不像建築架構同樣,建築一經建成,修改的成本極高,而軟件應該走對應的方向,發揮易於修改的特色。web
「如今的大多數軟件很是像埃及金字塔,在彼此之間堆建了成千上萬的磚塊,缺少結構完整性,只是靠蠻力和成千上萬的奴隸完成。」 —— Alan Kay。筆者認爲,雖然這句話表達的意思我很贊同,但實際上,金字塔做爲帝王的陵墓,是有着完整的設計邏輯,而且隨着好幾座金字塔的迭代的,以及逐漸完備的施工管理,後期金字塔是很是傑出的建築表明,並做爲地球上最高的人造建築持續了好幾千年。關於金字塔是否由奴隸建造仍是存有爭議。(圖片來自 Isabella Jusková @ Unsplash)。docker
做爲工程師,咱們一方面關注軟件產品的能力和行爲,這每每是一個項目的起點,另外一方面咱們須要關注軟件的架構設計,由於咱們但願設計有着彈性、易於維護、高性能、高可用的系統,更但願系統可以不斷演進,而不是在將來被推倒重作。因此,回正咱們的視野,當咱們決心要設計一個好的架構時,咱們須要明確,架構每每決定的是軟件的非功能性需求。這些非功能性需求有:數據庫
這裏引用 Robert C·Martin(Uncle Bob)的原語,「軟件產品是有兩方面的價值,一方面是實現功能的價值,另外一方面是架構的價值,而架構的價值可能更重要一些,由於它表明着軟件 soft 的特性。」編程
本書例子過少,並且缺少現有流行框架的重構或者改進建議,有點形而上,可是在方法論層面筆者仍是認爲值得一讀。Robert C·Martin 對數據庫(特指 RDBMS)的態度很值得討論,首先他認爲數據庫是一種細節,在架構中應該與業務解耦,他強調業務代碼與數據庫的無關性。同時在咱們的代碼進行計算時,表格每每不是理想的數據結構,好比有些場景會使用樹、DAG 等等。能夠回想一下,當你須要把一個樹存入數據庫時,你該如何實現?
根據咱們以前的討論,後端系統採用微服務是不會影響到其功能上的價值,本質上微服務化和單體應用的差異並不會表達在功能上,不少微服務進展不順利的同窗會常常說到:這東西用單體寫早就完事兒!的確是這樣,這側面也印證了微服務只是一種軟件架構,而不是別的神奇的東西,並非某個業務需求必需要使用微服務完成,咱們看中微服務,也是看中了架構方面的優點,即那些非功能性需求。也有人使用 pattern 來描述它,也有人說和 SOA 基本上是一個東西,只是粒度不一樣,因此咱們一開始就別相信這個世界有靈丹妙藥,也別指望有個什麼技術可以瞬間替代 Oracle。後端
做爲開發「企業級後端應用」的同窗,咱們常常會面臨不少非業務需求上的苦惱:有時咱們須要同時支持移動端、移動 web、桌面端三種客戶端;有時候咱們須要支持不一樣的協議好比 JSON 或 XML;有時咱們又須要使用不一樣的中間件傳遞消息;或者在研發時,咱們知道有一個地方寫的很差,咱們想在將來補課重構;咱們想嘗試最新的技術可是代價太高;系統沒法擴容,或者成本極高;系統過於複雜沒法在本地運行致使極低的效率……這些苦惱纔是採用微服務的主要驅動力,回到咱們對軟件架構的討論之中,咱們但願的是經過足夠鬆耦合的獨立服務,來下降組件之間變化的成本,也就是說今天更新發送通知的功能,並不會影響到用戶查看購物車,也不會讓研發人員半天改完,再等三天才能上線。設計模式
可是世界上沒有免費的午飯,雖然咱們知道微服務有不少很好的特性,好比組件即服務、鬆耦合、獨立部署、面向業務、高維護性、高擴展性等等,這裏並不想展開討論它的好處,咱們先考慮投入成本。假設咱們每一個同窗都完整的學習了微服務的全部知識,對市面上的框架、產品很是熟悉,摩拳擦掌準備開始,在拆解完幾個服務後,咱們會發現,沒有足夠的自動化手段,靠手動的方式進行測試、編譯、部署、監控,這是顯而易見的會下降體驗,若是沒有優化好的部署策略,全部的服務都在某個發佈日上線,那更是一種災難。
隨着規模的擴大,單體應用的代碼改動成本會愈來愈大。不少時候咱們微服務的架構實踐是存在誤區的,咱們總認爲流量通過某個 gateway 後直達某個服務,確忽視了服務之間調用的場景,理想的微服務架構應該是一張網,每一個節點都是獨立的、自治的服務。
一些以前使用單體很容易作到的場景,在分佈式的環境下會更加困難。好比咱們能夠經過 RDBMS 提供的數據庫事務來支撐一致性,可是若是訂單服務和價格服務分離,勢必要進行分佈式事務來保證一致性(每每是最終一致性),而分佈式事務的成本和難度就不用贅述了。在單體環境下,咱們能夠很輕鬆的使用切面進行權限驗證,而在微服務的場景中,服務之間相互調用是難以控制的。
拆分服務或者服務邊界劃分是另外一件很難作到的事情,最吃香的理論也許是根據 DDD 去進行劃分,自然的領域或者子域(domain)貌似都能對應一個服務,由於足夠的界限上下文(bounded context)可以保持服務的獨立性,使其細節被隱藏在界限以內,聽起來是個不錯的主意。可是現實卻十分殘酷,使用 DDD 生搬硬套去進行軟件開發的例子不在少數,成功例子也難以複製。
雖然我在實踐中也常用業務領域去進行服務劃分,可是我並不認爲這是 DDD 的作法,沒有必要規定有多少個 domain 就有多少服務,也不須要規定 sub domain 可否獨立服務。與其進行頂層設計一攬子的解決方案,我更相信演進的力量,若是你真的須要拆分一個服務,足夠的基礎設施與自動化工具應該容許你低成本的去作,而不是一開始就畫好全部的架構圖。這就跟全部的改革同樣,革命派每每不是一步功成,而是逐漸的積累的。因此使用微服務,當你可以負擔的起(only you can afford it),也表示你能負擔的失敗同樣,技術世界不存在一蹴而就,all in 很是危險。
衛報網站(Guardian)的微服務改造就是一個很好的例子,網站核心依舊是一個巨大的單體,可是新功能經過微服務實現,這些微服務調用單體所提供的 API 來完成功能。對於經常出現的市場活動(好比某個體育比賽的專用板塊),這種方式可以快速實現活動頁面與功能,完成業務需求,並在活動結束後刪除或丟棄。我以前參與項目中,也經過等量替換與重構,慢慢絞殺(Strangler Pattern)掉一個巨大的陳舊的 JBoss 應用。
PlayStation 首席設計師 Mark Cerny 在今年的 PS5 新主機的技術分享中提到,遊戲主機須要平衡好演進與革命(balance the evolution and revolution),咱們不想丟掉多年來開發者的積累,在複用過去的成功經驗時,咱們也但願你們可以使用更先進的技術。
看起來,在 Java 世界中,Spring Cloud 貌似是微服務的最優解了,甚至在不少同窗的簡歷上,Spring Cloud 幾乎能夠和微服務劃等號了,不止一次的有人告訴我說:公司的技術棧不是 Java,因此搞不了微服務很難受,並非我沒有學習精神和冒險精神云云。很遺憾,對於軟件架構來講,跟可沒有規定編程語言,設計模式不是也出了不少版本嗎?歸根結底仍是 Spring Cloud 的全家桶策略更吸引人,什麼事兒都不如加上幾個 jar 就能擁有的神奇次時代架構更有吸引力。
不能否認,我在學習 Spring Cloud 的時候也驚歎其完整性,幾乎常見的微服務需求都有足夠完整的解決方案,而大多數方案是作在應用層,具備良好的適配性,好比 eurake 的註冊發現、zuul 網關與路由、config service、hystrix circuit breaker 等等,經過統一的編程範式(基於 annotation 的注入與配置),足夠豐富的功能選擇(經常使用功能甚至都有兩種選擇),以及較好的集成方式。前有 Netflix 的成功經歷,後隨着微服務的浪潮,再加上足夠龐大的 Java 社區,能夠說是王道中的王道。但並不是 Spring Cloud 沒有弱點,反倒這些功能設計與隨後的容器化浪潮產生了分歧,至今融合 Spring Cloud 與 Kubernetes 都是熱門話題,這裏咱們展開說說它的不足或者限制(limitation)。
這多是最大的問題,基本只能使用 Java 做爲研發語言,這一點在國內也備受爭議,由於不管是做爲架構師仍是入門的程序員,都須要嘗試新的技術棧來進行儲備或是採用新的功能,並且好比使用自制的 client 去實現 ribbon 的負載均衡也是很難的,可是若是不用 Java,作到這一點也很難,不是說 Java 語言不夠優秀,而是咱們對將來應該有更多的選擇,對於一個技術公司來講編程語言應該不會成爲限制,試問這個時代誰不想學習一點 golang 或者 rust 或者 scala 呢?其餘服務好比 SSO、Config Service 也過於總體,若是想進行某項適配,則必須進行大量的修改(還好是開源的)。咱們很擔憂這種狀況都會隨着框架的老去而面臨推到重來的境界,Ruby on Rails 可能就是前車可鑑吧。侵入性是另外一個問題,還記得咱們在討論軟件架構時所提倡的實踐規則嗎?儘可能不要讓頂層設計依賴底層的框架或者某種細節,可是滿屏幕的 annotation 與 jar 的直接引用,無疑告訴咱們想去掉它們仍是很是難的。
對於雲原生,不管是 CNCF 仍是 Pivotal (VMWare)都在強調容器化、微服務、面向雲環境等,CNCF 圍繞 Kubernetes 開始發展壯大,也隨着這種先進的容器編排技術的流行人們漸漸發現它和 Spring Cloud 在功能上仍是存在不少重疊,雖然 k8s 與 IaaS 沒有重疊,可是如今還有多少廠商再推純 IaaS 呢?既然有功能重疊,就有取捨,考慮到 Spring Cloud 的全家桶屬性,這個分歧處理一直都不能很好的解決。
不管是 Config 、Eureka 都是聚合的單點,及時它們有集羣的方式達到近乎 100% 的可靠性,但在邏輯架構上,全部的微服務都依賴它們,這些集中式的資源的耦合是很是強的,它們會一直存在在你的生產環境之中,直到最後一個使用它們的系統下線。咱們在架構中須要避免使用共享的實例與資源,一個應用不會由於不能寫日誌而崩潰,也不該該由於本地沒有 eureka 而沒法啓動。
誠然,在進程以內解決服務註冊發現、負載均衡是很好的,它表明了最好了平臺無關性,但平臺的其餘能力也很難享受的到了。綁定 k8s 貌似是個更好的選擇,由於相對於 Spring Cloud 它更靈活,也能作到不會被基礎的雲平臺綁定,但也更難以掌握與運維。固然我也不是認爲 K8s 必須做爲微服務的選擇,做爲容器的編排平臺,它能夠作更多的事情(好比跑數據庫、中間件等),運行微服務應用只是其中之一。
2020年已通過了一半,從技術上來講,Serverless 已經進入成熟期,Kubernetes 也更加成熟,已經成爲事實的標準。可是不少時候咱們的方法論與架構設計是跟不上的技術發展的,不少同窗可能還在經歷每週的發佈日,不少同窗還沒辦法改進團隊內老舊的技術,不少同窗的 Jenkins 仍是停留在打包的階段,不少同窗機器上仍是沒有安裝 docker,不少時候並非框架或者平臺的問題,而是方法論還停留在過去。
應用程序也應該踐行開閉原則,對擴展開放使得咱們在將來有更多的選擇。微服務是一個很好的機會能讓咱們真正的演進架構而不須要付出過多的代價,當咱們須要組件化系統時,組件的關鍵特性正是可獨立替換或升級,咱們能夠不影響其餘部分去進行替換和重構,這樣的成本是顯然低於拋棄舊的巨型框架而重寫的。有着正確的態度和工具,咱們能夠更快、更頻繁的控制變動,咱們能夠激進的選擇新的技術棧,也能夠合併兩個耦合過緊的服務,隨着服務的不斷聚合、抽出,你會發現系統的邏輯架構會愈來愈清楚,再進行修改就會信心倍增了。咱們能夠針對每一個服務使用不一樣的存儲技術,咱們可使用 OSS 處理文件,而不是繼續往 Oracle 裏面塞圖片和視頻。
這個開幕雷擊雖然槽點滿滿,但並無下降社區對 Istio 的信心,反卻是漸漸發現此次的大改動使 Istio 變得有點好用了,能夠在生產中採用而不須要付出太多代價了。固然,漂亮話永遠好說。
2017 年的時候 Service Mesh 仍是一個襁褓中的概念,如今已經成爲了微服務領域的將來之選,但遺憾的是目前只有 Istio 足夠成熟能表明這項技術,固然我也有幸實踐過相似的 Sidecar 來進行反向代理、日誌收集、性能監控、健康檢查等功能,可是距離 Mesh 的願景仍是有大的差距。
今天並不想展開 Service Mesh 或者 Istio 的優點,進程以外的解決方案可以確保系統的靈活性,而流量控制、服務治理、端對端的傳輸安全、限流、發現註冊等等,咱們但願工程師可以聚焦業務,實現架構的靈活性,聚焦真正的價值,而剩下的進行配置就好。因此我想這也是 Serverless 被無限看好的緣由,既然咱們想 delegate 對基礎設施的控制,那爲何還須要關心容器呢?Istio + k8s 的方案已經足夠好,至少咱們團隊正在認真學習,在向客戶提供最佳實踐以前,咱們依舊有不少問題須要解決,下面列出一些表明性的:
使用系統度量、參數等觸發彈性伸縮是常見的需求,這裏咱們是使用雲監控?仍是本身搭一套?我一直傾向數據與用途解耦,使用 pub sub 模式解決問題,好比 CPU 太高可能會觸發多個行爲:觸發警報,觸發彈性規則,展現在 dashboard 上。咱們能夠增長 pod 來分擔服務的壓力,或者由於某個 pod 異常退出後,啓動新的 pod 來完成自恢復,這系列動做也是須要咱們本身解決的。
日誌、警報等可觀測性的問題,這一方面的實踐較多,惟一比較擔憂的是 ARMS 或者 Newrelic 這種 APM 功能目前沒在目標平臺上實踐過,咱們但願可以清晰的看到每一個服務的實時性能,目前這一部分還缺少考慮與設計。
Istio 的流量控制能力是很是強大的,如何對服務採起降級、限流這種常見的治理操做,也是須要總結出實踐經驗的。避免串流錯誤(cascade failure)在微服務領域也很常見,也須要避免故障蔓延。
藍綠、灰度、金絲雀,這些多樣的部署方式也須要落地,咱們可能會寫一些 deployment util 之類的小腳本,部署的方式須要和 CICD 打通。一個部署工具,一個配置文件,一個 CICD 組成將來單個應用的部署方式。
基於屬性的權限控制以及 OPA 的可定製性咱們是很是看好的,並且 sidecar 能夠在進程以外解決權限驗證問題,的確值得嘗試,恰好也學習下 golang 用於定製 OPA。
分佈式事務是微服務實踐中的大坑,我曾經也寫過相似的文章,項目經驗中更多的是將事務放在單一的服務內,再加上我本身也沒機會寫過真正的網店系統。補償事件也是經常使用的最終一致性方案,總之放在一塊兒驗證。
基於 K8s 實現應用的高可用應該不難,容器的天生優點就是易於啓動與管理,若是隨機殺掉 pod 不會影響系統可用性,這就算是實現了相似於 SLB + ECS + ASG 的能力,真正危險的是其餘 非業務型 pod,好比 istio 的各類 supervisor。
實踐微服務,做爲架構師所考慮的東西遠遠大於只是實現業務,可是一旦鋪平道路,下來的研發與迭代將會更加順利。
若是你也對雲原生架構有濃厚的興趣,歡迎 加入釘釘交流羣 與咱們交流!
《深刻淺出 Kubernetes》一書共聚集 12 篇技術文章,幫助你一次搞懂 6 個核心原理,吃透基礎理論,一次學會 6 個典型問題的華麗操做!
<關注阿里巴巴雲原生公衆號,回覆 排查 便可下載電子書>
張羽辰(同昭)阿里雲交付專家,阿有着近十年研發經驗,是一名軟件工程師、架構師、諮詢師,從 2016 年開始採用容器化、微服務、Serverless 等技術進行雲時代的應用開發。同時也關注在分佈式應用中的安全治理問題,整理《微服務安全手冊》,對數據、應用、身份安全都有必定得研究。