微服務架構的興起,很大程度歸功於容器化技術的成熟,Docker 的普及讓微服務佈署更簡便。容器的自給自足特性,使其在不同環境下都能保持一致性,降低佈署和擴充套件的複雜度。然而,資料函式倉管理仍是微服務架構中的一大挑戰,特別是處理遺留系統時,分享資料函式庫是常見但非最佳的方案。透過 API 閘道和反向代理,可以有效管理和協調大量的微服務,避免前端直接暴露 API,確保系統的靈活性。此外,組態管理工具如 Puppet、Chef 和 Ansible,對於管理日益增多的微服務至關重要。跨功能團隊的建立和 API 版本控制也是微服務架構成功的關鍵因素。
微服務系統架構與實務應用
微服務架構已成為現代軟體開發的主流趨勢,特別是在雲端運算和容器化技術的支援下。微服務的自給自足(self-sufficient)和不可變性(immutable)使得它們能夠在不同環境中保持一致性,大幅降低佈署和擴充套件的複雜度。
容器化的微服務
容器技術(如Docker)讓微服務的佈署變得更加簡單。容器內包含了執行應用程式所需的所有元件,包括執行時函式庫、應用伺服器、資料函式庫和應用程式碼等。這種自給自足的特性使得容器可以在不同的環境中執行而無需擔心相容性問題。
自給自足容器的組成元件
- 執行時函式庫(JDK、Python等)
- 應用伺服器(Tomcat、nginx等)
- 資料函式庫(最好是輕量級)
- 應用程式碼(JAR、WAR、靜態檔案等)
然而,完全自給自足的容器在擴充套件時可能會遇到問題,例如內嵌資料函式庫的同步或資料卷的分享。因此,一種常見的做法是將資料函式庫外接到單獨的容器中,透過代理服務進行連結。這種方式雖然增加了佈署的複雜度,但提供了更大的靈活性。
微服務與資料函式庫的管理
在處理遺留系統時,資料函式庫往往是最後才被重構的部分。這種情況下,一種常見的做法是分享資料函式庫,但確保每個服務只存取特定的schema或表群。其他需要該資料的服務則透過負責該資料的服務的API來存取。這種方式雖然不是最佳實踐,但在某些情況下是必要的。
代理微服務與API閘道
大型企業前端可能需要呼叫數十甚至數百個HTTP請求。代理微服務可以幫助聚合這些請求並傳回綜合結果。它們不應包含任何業務邏輯,只是簡單地將多個回應組合在一起。
反向代理的重要性
永遠不要直接暴露微服務API。如果沒有某種協調機制,消費者和微服務之間的依賴關係會變得非常大,從而喪失微服務架構原本應有的靈活性。輕量級伺服器如nginx、Apache Tomcat和HAProxy非常適合執行反向代理任務。
極簡主義方法
微服務應該只包含它們真正需要的套件、函式庫和框架。越小越好。這與單體應用的做法截然不同。對於Docker容器佈署,選擇像CoreOS這樣精簡的作業系統可以更好地利用資源。
組態管理的重要性
隨著微服務數量的增加,組態管理(CM)的需求也隨之增加。使用Puppet、Chef或Ansible等工具可以避免佈署和管理微服務時的混亂。
跨功能團隊
微服務的最佳實踐是由跨功能團隊負責從設計到佈署和維護的全生命週期。這樣可以確保每個微服務都由一個團隊全權負責,避免多個團隊之間的交接問題。
API版本控制
對任何API(包括微服務)都應實施版本控制。如果某個變更破壞了API格式,應將其作為單獨的版本釋出。對於公共API以及被其他內部服務使用的API,必須保持向後相容性,或至少給消費者足夠的時間來適應。
版本控制的最佳實踐
- 在變更API格式時建立新版本
- 維持向後相容性
- 給消費者足夠的時間來適應新版本
綜上所述,微服務架構提供了一種靈活且可擴充套件的系統設計方法,但同時也帶來了新的挑戰,如組態管理、API版本控制等。透過採用適當的技術和最佳實踐,可以充分發揮微服務架構的優勢。
微服務架構的實踐與深度解析
微服務的概念並非新興事物,其實早已存在於我們的技術領域中。一個典型的例子是 Unix/Linux 系統中的管道(pipe)使用方式。以下是一個經典的命令列例子:
ps aux | grep jav[a] | awk '{print $2}' | xargs kill
這個命令組合了四個程式,每個程式都專注於執行單一或少數功能。它們透過標準輸入(stdin)和標準輸出(stdout)相互連線,展現了微服務架構的核心思想:簡單、可組合、且具有明確介面。
內容解密:
ps aux:列出所有正在執行的行程。grep jav[a]:過濾出包含 “java” 的行程。awk '{print $2}':提取行程 ID(第二列)。xargs kill:終止對應的行程。
這個命令的強大之處在於,每個程式都設計得非常專注和簡單,但組合起來卻能完成複雜的操作。這正是微服務架構的精髓所在。
微服務的演進與關鍵要素
儘管微服務的概念早已存在,但其廣泛流行卻是近年的事。這背後有多個關鍵因素的成熟與結合,包括:
- 領域驅動設計(Domain-Driven Design, DDD)
- 持續交付(Continuous Delivery)
- 容器化技術(Containers)
- 小而自主的團隊(Small Autonomous Teams)
- 可擴充套件的系統(Scalable Systems)
這些要素結合在一起,才使得微服務架構真正發揮其強大威力。
微服務的核心價值
微服務架構透過將複雜系統分解為小型、自治的服務,每個服務專注於特定的業務範圍,並透過 API 交換資料。這種架構方式與物件導向程式設計(OOP)的初衷不謀而合。正如 Alan Kay 所說:
“The big idea is ‘messaging’. The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.”
此外,Robert C. Martin 也指出:
“Gather together those things that change for the same reason, and separate those things that change for different reasons.”
這些觀點都強調了模組間通訊設計的重要性,以及根據變更原因進行模組劃分的必要性。
微服務的實踐優勢
在實踐中,微服務架構允許我們:
- 選擇最合適的工具和語言來實作每個服務。
- 實作鬆耦合,提高不同團隊之間的獨立性。
- 加速測試和持續交付/佈署,得益於其分散式特性。
然而,微服務並非萬能解藥,它解決的是特定的問題,而不是所有應用程式的設計方式。
建立開發環境
在決定採用微服務架構後,我們的第一步是建立開發環境。接下來,本文將帶領讀者進行實際操作,建立一個「時尚」書店服務的開發環境,讓理論與實踐相結合。
使用 Vagrant 和 Docker 設定開發環境
開發環境往往是新加入專案的人員首先要面對的問題。雖然每個專案都不同,但花費整天設定環境,並在接下來的幾天裡試圖理解應用程式的工作原理並不少見。
例如,安裝 JDK、設定本地 JBoss 伺服器例項、進行所有組態以及後端應用程式所需的所有其他複雜操作,需要多少時間?此外,如果前端與後端分離,還需要花費時間做同樣的事情。對於一個擁有成千上萬甚至數百萬行程式碼、層層疊疊的龐大單體應用程式來說,要理解其內部工作原理需要多少時間?
開發環境的設定和簡化是容器和微服務可以大顯身手的地方。微服務按照定義就是小的。那麼,理解一千行(或更少)的程式碼需要多少時間?即使你從未在微服務中使用的語言上程式設計,也不應該花費太多時間來理解它做了什麼。
另一方面,容器技術,尤其是與 Vagrant 結合使用時,可以使開發環境的設定變得輕而易舉。設定過程不僅可以無痛且快速,而且結果可以盡可能接近生產環境。事實上,除了硬體之外,它可以是相同的。
在我們開始開發這樣的環境之前,讓我們討論一下我們正在構建的服務背後的技術。
結合微服務架構和容器技術
本文中使用的微服務(books-ms)與大多數微服務倡導者推薦的方式略有不同。
除了我們已經討論過的服務需要小、限制在明確定義的有界上下文等方面之外,重要的是要注意,大多數微服務僅為系統的後端部分而建立。微服務倡導者會將單體後端拆分成許多小的微服務,但通常會保留前端不變。這樣做的結果是整體架構具有單體前端,而後端則被拆分成微服務。為什麼會這樣呢?
我認為答案在於我們使用的技術。我們開發前端的方式並不是為了將其拆分成更小的部分。
伺服器端渲染正變成歷史。雖然企業可能不同意這種說法,並繼續推動伺服器端框架,將 Java 物件「神奇地」轉換為 HTML 和 JavaScript,但客戶端框架將繼續慢慢增加其受歡迎程度,將伺服器端頁面渲染送入歷史。這讓我們有了客戶端框架。
單頁應用程式是我們今天經常使用的。AngularJS、React、ExtJS、ember.js 等證明瞭它們是前端開發演進的下一步。然而,無論是不是單頁應用程式,大多數都推廣了前端架構的單體方法。
後端被拆分成微服務,而前端仍然是單體的,我們正在構建的服務並不能真正遵循每個服務都應該提供完整功能的理念。我們應該應用垂直分解,建立小的、鬆散耦合的應用程式。然而,在大多數情況下,我們在這些服務中缺少視覺部分。
所有前端功能(身份驗證、庫存、購物車等)都是單一應用程式的一部分,並透過 HTTP 與後端(大多數情況下)進行通訊,而後端則被拆分成微服務。這種方法與單一單體應用程式相比是一個很大的進步。透過保持後端服務的小型、鬆散耦合、設計用於單一目的且易於擴充套件,我們曾經在單體應用程式中遇到的一些問題得到了緩解。雖然沒有什麼是理想的,微服務也有其自身的問題集,但發現生產中的錯誤、測試、理解程式碼、更改框架甚至語言、隔離、責任等變得更容易處理。我們付出的代價是佈署,但這透過容器(Docker)和不可變伺服器的概念得到了顯著改善。
如果我們看到微服務在後端提供的好處,那麼將這些好處也應用到前端,並設計出不僅包含後端邏輯還包含應用程式可見部分的微服務,不是一步一步向前邁進嗎?如果開發人員或團隊能夠完全開發一個功能,並讓其他人只需將其匯入應用程式,那不是很有益嗎?如果我們能夠以這種方式開展業務,前端(無論是不是單頁應用程式)將簡化為一個支架,只負責路由和決定匯入哪些服務。
內容解密:
本段落主要闡述了微服務架構和容器技術的結合,以及如何將這種結合應用於前端和後端。文中提到了目前大多數微服務僅針對後端,而前端仍然是單體的,這種架構存在一定的侷限性。作者認為,將微服務的概念擴充套件到前端,可以帶來更多的益處,例如更完整的垂直分解和更靈活的功能開發。
網頁元件的出現
我不會深入介紹網頁元件的工作原理,因為本文的一個目標是盡可能做到語言無關。如果你對這個主題感興趣,可以進一步瞭解。
// 這裡是一個簡單的網頁元件範例
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = '<p>這是一個網頁元件</p>';
}
}
customElements.define('my-component', MyComponent);
內容解密:
本段落程式碼展示了一個簡單的網頁元件定義。使用 customElements.define 方法註冊了一個名為 my-component 的自定義元素。在這個範例中,我們建立了一個類別 MyComponent,它繼承自 HTMLElement,並在其 constructor 中初始化了一個 Shadow DOM,設定了內部的 HTML 結構。這種方式允許開發者建立可重複使用且封裝良好的 UI 元件。網頁元件技術使得前端也可以像後端一樣,被拆分成小的、可獨立開發和維護的部分,從而更好地符合微服務架構的思想。
使用 Vagrant 和 Docker 建立開發環境
在開始使用 books-ms 之前,我們需要了解這個服務包含了前端網頁元件和後端 API,且被封裝成一個單一的微服務。這種做法使得我們可以將所有功能集中在一處,並根據需要使用它。無論是呼叫服務 API 還是將網頁元件匯入網站,都能滿足不同使用者的需求。
技術背景
這個服務是使用 Scala 和 Spray 開發的,用於處理 API 請求和提供靜態前端檔案。前端元件則是使用 Polymer 開發的。整個開發過程採用了測試驅動開發(TDD)的方法,產生了單元測試和功能/整合測試。原始碼位於 vfarcic/books-ms GitHub 儲存函式庫中。
Vagrant 和 Docker 簡介
我們將使用 Vagrant 和 Docker 來建立開發環境。
Vagrant
Vagrant 是一個命令列工具,用於透過虛擬機器管理工具(如 VirtualBox 或 VMWare)建立和管理虛擬機器。它不是虛擬機器管理工具,而是一個提供一致介面的驅動程式。透過單一的 Vagrantfile,我們可以指定 Vagrant 需要的所有資訊,以建立所需的虛擬機器。由於只需要一個設定檔,因此可以將其與應用程式碼一起儲存在儲存函式庫中。Vagrant 非常輕量和可攜,可以建立可重複使用的環境,無論底層作業系統為何。
# Vagrantfile 示例
Vagrant.configure("2.2") do |config|
config.vm.box = "ubuntu/focal64"
config.vm.network "forwarded_port", guest: 80, host: 8080
end
內容解密:
Vagrant.configure("2.2")指定了 Vagrant 的設定版本。config.vm.box = "ubuntu/focal64"定義了要使用的虛擬機器映像。config.vm.network "forwarded_port"設定了埠轉發,將虛擬機器的 80 埠對映到主機的 8080 埠。
Docker
Docker 容器允許我們將軟體及其依賴項封裝成一個完整檔案系統。它們包含了軟體執行所需的一切,包括程式碼、執行環境、函式庫、資料函式庫和應用伺服器等。由於所有東西都被封裝在一起,因此容器在任何環境中都能保持一致的執行結果。
為什麼同時使用 Vagrant 和 Docker?
我們使用 Vagrant 建立一個具有 Ubuntu 作業系統的虛擬機器,以確保本文中的所有命令和工具都能在讀者的電腦上正常運作,無論其底層作業系統為何。Docker 則用於建立和管理容器。
環境設定
在接下來的章節中,我們將假設讀者已經在電腦上安裝了 Git 和 Vagrant。其他所需的工具將透過指令和指令碼提供。
# 安裝 Vagrant 和 Docker
sudo apt-get update
sudo apt-get install -y vagrant docker.io
內容解密:
sudo apt-get update更新了套件列表。sudo apt-get install -y vagrant docker.io安裝了 Vagrant 和 Docker。
使用 Vagrant 和 Docker 建立開發環境
在 Windows 系統上,請確保 Git 設定為「Checkout as-is」。在安裝過程中選擇圖 4-1 所示的第二或第三個選項即可實作這一點。同時,如果未安裝 SSH,請確保將 [PATH_TO_GIT]\bin 新增到系統的 PATH 環境變數中。
開發環境設定
首先,從 books-ms 的 GitHub 倉函式庫克隆程式碼。
git clone https://github.com/vfarcic/books-ms.git
cd books-ms
下載程式碼後,我們可以繼續建立開發環境。
Vagrant
建立 Vagrant 虛擬機器非常簡單。
vagrant plugin install vagrant-cachier
vagrant up dev
第一個命令不是必需的,但它可以透過快取套件來加速新虛擬機器的建立。第二個命令才是真正的工作,它啟動了名為 dev 的虛擬機器。第一次執行可能需要一些時間,因為需要下載基礎映像。後續建立根據相同映像(ubuntu/trusty64)的虛擬機器將會快很多。
Vagrantfile 設定解析
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.synced_folder ".", "/vagrant"
config.vm.provider "virtualbox" do |v|
v.memory = 2048
end
config.vm.define :dev do |dev|
dev.vm.network "private_network", ip: "10.100.199.200"
dev.vm.provision :shell, path: "bootstrap.sh"
dev.vm.provision :shell,
inline: 'PYTHONUNBUFFERED=1 ansible-playbook \
/vagrant/ansible/dev.yml -c local'
end
if Vagrant.has_plugin?("vagrant-cachier")
config.cache.scope = :box
end
end
內容解密:
config.vm.box = "ubuntu/trusty64":指定使用的基礎映像為 ubuntu/trusty64。config.vm.synced_folder ".", "/vagrant":將本機當前目錄與虛擬機器的 /vagrant 目錄同步。v.memory = 2048:為虛擬機器組態 2 GB 的 RAM。dev.vm.network "private_network", ip: "10.100.199.200":設定虛擬機器的私有網路 IP。dev.vm.provision :shell, path: "bootstrap.sh":執行 bootstrap.sh 指令碼進行初始化設定。dev.vm.provision :shell, inline: ...:執行 Ansible playbook(dev.yml)來安裝 Docker 和 Docker Compose。config.cache.scope = :box:啟用 vagrant-cachier 快取功能,加速虛擬機器的建立。
對於不熟悉 Ruby 的人來說,語法可能看起來有些陌生,但很快就會發現使用 Vagrant 定義虛擬機器非常簡單。我們的設定指定了使用 ubuntu/trusty64 映像,並將本機目錄與虛擬機器的 /vagrant 目錄同步。同時,虛擬機器被組態為使用 2 GB 的 RAM 和執行特定的 Ansible playbook。
登入虛擬機器並檢查安裝結果
完成 Vagrant 虛擬機器的啟動後,我們可以登入虛擬機器並檢查 Ansible、Docker 和 Docker Compose 是否正確安裝。
vagrant ssh dev
ansible --version
docker --version
docker-compose --version
cd /vagrant
ll
內容解密:
vagrant ssh dev:登入名為 dev 的虛擬機器。ansible --version、docker --version、docker-compose --version:檢查 Ansible、Docker 和 Docker Compose 的版本,確認它們已經正確安裝。cd /vagrant:進入虛擬機器中的 /vagrant 目錄,該目錄與本機的程式碼目錄同步。ll:列出 /vagrant 目錄的內容,可以看到與本機程式碼目錄相同的檔案。