Categories
程式開發

微服務開發的10個最佳實踐


在文章《微服務架構概覽》中,我詳細討論了微服務架構以及在現代軟件開發中使用它的優缺點。那麼,什麼是微服務架構呢?我給出的定義是:

微服務架構是將軟件系統分解成可獨立部署的自治模塊,這些模塊通過輕量級的、語言無關的方式進行通信,共同實現業務目標。

軟件系統是複雜的。由於人腦只能處理一定程度內的複雜性,大型軟件系統的高複雜性導致了許多問題。大型複雜的軟件系統難於開發、增強、維護、現代化和規模化。多年來,為解決軟件系統的複雜性做過許多嘗試。在上世紀70年代,David ParnasEdsger W. Dijkstra引入了模塊化軟件開發,以解決軟件系統的複雜性。在上世紀90年代,引入了分層軟件架構來處理業務應用程序的複雜性。自本世紀初以來,面向服務的架構(Service Oriented Architecture, SOA)成為開發複雜業務應用程序的主流。微服務架構是處理現代軟件應用複雜性的最新方法。此時,大家可能會提出一個問題:為什麼我們突然需要一個新的軟件開發方法?簡而言之,與軟件開發相關的整個生態系統在過去十年中發生了巨大的變化。如今,軟件使用敏捷方法開發,使用CI/CD在容器+雲上部署,在NoSQL數據庫上持久化,在現代瀏覽器或智能手機上展現,機器通過高速網絡連接。由於這些因素的出現,在2012年誕生了微服務架構。

微服務還是單體架構

主要有兩類人對微服務和單體架構持相反的觀點。對於一群人來說,微服務架構完全是關於貨物崇拜或炒作驅動的開發,這只是癡迷於技術的開發人員的遊樂場。對於另一群人來說,微服務架構是“一個管控所有的架構”,它可以消除軟件系統的任何復雜性。在我看來,微服務和單體架構是互補的。如果從長遠來看,這個應用程序仍然會較小,則單體架構是適合的方法。另一方面,對於大型而復雜的應用程序或有潛力變得大型而復雜的應用程序,微服務架構是正確的解決方案。現代軟件開發是如此龐大,以至於微服務架構和單體架構將像SQL和NoSQL一樣的方式共存。

微服務的最佳實踐

正確設計微服務架構非常具有挑戰性和困難。與單體架構為所有問題提供一個解決方案相反,微服務架構為不同的問題提供不同的解決方案。如果選擇了錯誤的解決方案,那麼微服務架構就是一個注定要爆炸的定時炸彈。一個設計糟糕的微服務架構比一個單體架構還要糟糕。為微服務架構定義一組最佳實踐也很有挑戰性。我曾在一些會議上看到一些著名的、受人尊敬的軟件工程師提出了微服務架構的最佳實踐,但這些實踐卻適得其反。

在這裡,我提出了一些最佳實踐,這些實踐將有助於開發有效的微服務應用程序,在這些應用程序中,目標項目應該存在超過6個月的時間,並且團隊規模從中等到大型(6 +開發人員)。另外,還有一些關於微服務架構最佳實踐的文章,例如Martin Fowler()的《微服務架構的特徵( Characteristics of a Microservice Architecture)》、Chris Richardson的《微服務模式( Microservices Patterns )》、以及 Tony Mauro的《在Netflix採用微服務:架構設計的教訓(Microservices at Netflix: Lessons for Architectural Design)》。也有一些很棒的演講,例如Stefan Tilkov 的《微服務模式和反模式( Microservices Patterns and Antipatterns )》,David Schmitz的《微服務嚴重失敗的10個技巧(10 Tips for failing badly at Microservices)》,Sam Newman 的《微服務原理( Principles of Microservices )》。

1.領域驅動設計:

開發微服務的首要挑戰是將大型、複雜的應用程序分割成小型、自主、獨立的可部署模塊。如果微服務沒有以正確的方式進行分割,將會出現緊耦合的微服務,這些微服務將具有單體架構的所有缺點,並具有分佈式單體架構的所有復雜性。幸運的是,已經有一個解決方案可以在這方面提供很大的幫助。 Eric Evans當時是一名軟件工程顧問,他在不同公司的業務應用程序中遇到了關於軟件複雜性的反復出現的問題,於是在2004年出版的《領域驅動設計:處理軟件核心的複雜性》一書中總結了他的寶貴見解。該書概述了三個核心概念:

  • 軟件開發團隊應該與業務部門或領域專家密切合作。
  • 架構師/開發人員和領域專家應該首先進行戰略設計:找到有界的上下文和相關的核心域以及普遍存在的語言、子域、上下文映射。
  • 然後,架構師/開發人員應該進行戰術設計,將核心域分解為細粒度的構建塊:實體、值對象、聚合、聚合根

領域驅動設計的詳細討論超出了這篇文章的討論範圍,但是你應該讀一下起初的DDD書籍Eric Evans的《領域驅動設計:處理複雜的軟件(藍皮書)(Domain Driven Design: Tackling Complexity in the Heart of Software (Blue Book))》,或者更現代一點兒的DDD書籍 Vaughn Vernon的《實現領域驅動設計(紅皮書)(Implementing Domain Driven Design(Red Book))》。如果將一個大型系統劃分為核心域和子域,然後將核心域和子域映射到一個或多個微服務,那麼我們將得到理想的松耦合微服務。

2.每個微服務一個數據庫

在將復雜的應用程序拆分為多個微服務模塊之後,下一個挑戰出現了,如何處理數據庫?我們是否應該在微服務之間共享數據庫?這個問題的答案是一把雙刃劍。一方面,在微服務之間共享數據庫將導致微服務之間的強耦合,這與微服務架構的目標正好相反。即使是數據庫中的一個小更改也需要團隊之間的協調同步。此外,在一個服務中管理數據庫的事務和鎖就已經足夠具有挑戰性了。而在多個分佈式微服務之間管理事務和鎖更是一項艱鉅的任務。另一方面,如果每個微服務都有自己的數據庫或私有表,那麼在微服務之間交換數據就打開了挑戰的潘多拉盒子。因此,許多著名的軟件工程師都提倡在微服務之間共享數據庫,將其作為一種實用的解決方案。然而,在我看來,微服務是關於可持續和長期的軟件開發的。因此,每個微服務都應該有自己的數據庫(或者私有表)。

3.微前端

不幸的是,大多數的後端開發人員對前端開發有一種過時的看法,認為前端開發很簡單。由於大多數軟件架構師都是後端開發人員,他們很少關注前端,而前端在架構設計中往往被忽視。通常在微服務項目中,後端與它們的數據庫被很好地模塊化,但只有一個整體前端。在最好的情況下,他們考慮用最熱門的單頁面應用(React、 Angular、Vue)其中之一來開發獨體前端。這種方法的主要問題是,前端的單體架構和後端單體架構一樣糟糕,正如我前一篇文章所述。另外,當前端因為瀏覽器的變化而需要更新時,它就需要一個大的更新(這就是為什麼那麼多公司仍然使用過時的Angular 1框架的原因)。網絡是簡單的,但非常強大,並天生提供了穿透力。開發基於單頁面應用的微前端有很多方法:使用iFrame、Web組件或借助於(Angular/React)元素。

4.持續交付

微服務架構的關鍵賣點之一是每個微服務都可以獨立部署。如果你有一個系統,例如100個微服務,並且只需要更改一個微服務,那麼你可以只更新一個微服務,而不需要修改其他99個微服務。但是,在沒有自動化的情況下獨立部署100個微服務(DevOps、CI/CD)是一項艱鉅的任務。要充分利用微服務的這一特性,需要CI/CD和DevOps。使用沒有CI/CD、DevOps的微服務架構,自動化就像購買最新的保時捷,然後用手剎去駕駛它。難怪微服務專家Martin Fowler將CI/CD 列為使用微服務架構的三個先決條件之一。

5.可觀察性

微服務架構的主要缺點之一是,軟件開髮變得簡單,而犧牲了運維。使用單體架構,監視應用程序要簡單得多。但是許多微服務在容器上運行,整個系統的可觀察性變得非常重要和復雜。甚至日誌記錄也變得很複雜,要將來自許多容器/機器的日誌聚集到一個中心位置。幸運的是,市場上已經有很多企業級的解決方案了。例如,ELK/Splunk提供微服務的日誌記錄。 Prometheus/App Dynamics提供工業級監控。微服務世界中另一個非常重要的可觀察性工具是Tracing。通常,對一個微服務的一個API請求會導致對其他微服務的幾個級聯調用。要分析微服務系統的延遲,需要測量每個微服務的延遲。 Zipkin/Jaeger為微服務提供了出色的跟踪支持。

6.統一技術棧

微服務架構告訴我們,對於一個微服務,採用最適合該微服務的編程語言和框架。這種說法不能照字面理解。有時,一個微服務可能需要一個新的技術棧,例如CPU密集/高性能任務,可能會選擇如c++ /Rust之類的編程語言。如果微服務與機器學習一起工作,那麼Python可能是更好的選擇。但是在沒有任何理由的情況下使用不同的編程語言/框架會導致過多的編程語言和框架而沒有任何實際的好處。思考這麼一個應用場景,一個微服務是使用Spring Boot + Kotlin+ React + MySQL開發的,另一個用的是JakartaEE + Java + Angular + PostgreSQL,下一個是Scala + Play Framework + VueJS + Oracle,則需要大量的工作去維持這些不同的編程語言、數據庫、框架,而沒有太多的收益。

7.異步通信

微服務架構中最具挑戰性的設計決策之一是服務之間如何通信和共享數據。當每個微服務都有自己的數據存儲時,這一點就更為重要了。通常,一個微服務可以單獨存在,但是它不能單獨實現所有的業務目標。所有微服務為了實現業務目標而在一起工作,為了在一起工作,它們需要交換數據或觸發其他微服務來執行任務。在微服務之間進行通信的最簡單和最常見的方式是通過同步的REST API,這是一種實用但臨時的解決方案。如果服務A同步調用服務B,服務B同步調用服務C,服務C同步調用服務D,那麼延遲就會增加。此外,由於微服務主要是分佈式系統,它們有可能會失敗。通常,同步微服務會導致級聯失敗,即一個服務的失敗會導致其他服務的失敗。微服務之間的同步通信也導致了微服務之間的緊密耦合。對於長期解決方案,微服務應該異步通信。微服務之間的異步通信有很多方式:通過消息隊列,例如Kafka,通過異步的REST (ATOM)或CQRS。

8.微服務優先

許多專家認為,對於新項目,最好從松耦合的單體架構開始,因為微服務架構需要大量的初始工作來設置運維。在他們看來,一旦項目變得足夠成熟,“漂亮的”設計就可以很容易地轉化為微服務。然而,在我看來,這種方法在大多數情況下都會失敗。實際上,實體內部的模塊將是緊耦合的,這將使其很難轉換成微服務。而且,一旦應用程序投入生產,在不破壞應用程序的情況下將其轉換為微服務將變得更加困難。因此,我的建議是,如果最終有使用微服務架構的計劃,那麼就從微服務開始。

9.基礎設施優於類庫

在微服務軟件開發的早期,Netflix主要使用Java編程來開發微服務。他們還開發了許多類庫(Netflix的OSS棧,包括Hystrix、Zuul)。許多公司通過Netflix跟進並開始使用Netflix OSS。後來,許多公司(包括Netflix)發現,Java實際上並不是開發微服務的語言,因為它體積龐大,而且冷啟動問題嚴重。 Netflix後來轉向Polyglot微服務範式,並決定不再進一步開發Netflix OSS,這導致了追隨者公司陷入困境。因此,與其大量投資於特定語言的類庫(如基於Java的Netflix OSS),不如使用框架(如服務網格、API網關)。

10.組織考慮

大約50年前(1967年),Melvin Conway觀察到公司的軟件架構受到組織結構的限制(Conway定律)。儘管這一觀察發現已有50年曆史,但麻省理工學院(MIT)和哈佛商學院(Harvard Business School)最近發現,這一法則在當今仍然有效。如果一個組織計劃開發微服務架構,那麼它應該使團隊規模更為恰當(兩個“美國”比薩團隊:7±2人)。此外,團隊應該是跨職能的,最好有前端/後端開發人員、運維工程師和測試人員。微服務架構只有在高層管理者也相應地改變他們的觀點和願景的情況下才能發揮作用。

作者介紹:

Md Kamaruzzaman,熱情的軟件架構師,終身學習者,熱心的讀者,偶爾也寫寫文章。

原文鏈接:

Effective Microservices: 10 Best Practices