1. 引言
1.1 專案的背景及意義
在當今的微服務架構中,應用程式通常被拆分成多個獨立的服務,這些服務透過網路進行通訊。這種架構的優勢在於可以提高系統的可擴充套件性和靈活性,但也帶來了新的挑戰,比如:
-
服務間通訊的複雜性:不同服務之間需要進行可靠的通訊,處理失敗重試、負載均衡等問題。
-
故障的容錯處理:系統的複雜性給與運維及故障處理帶來更大的挑戰,如何快速處理故障解決線上問題,這是考驗一個企業基礎設施建設的重要關卡。
最初,開發者使用SDK來解決這些問題,透過在程式碼中整合各種庫和工具來實現服務治理。然而,隨著微服務架構的規模不斷擴大,這種方法逐漸顯現出侷限性:
-
程式碼侵入性:需要在每個服務的程式碼中整合和配置各種庫,增加了程式碼的複雜性和維護成本。
-
一致性問題:不同服務可能使用不同版本的庫,導致治理邏輯不一致,SDK的升級難度凸顯。
為了解決這些問題,服務網格(Service Mesh)應運而生。服務網格透過在服務間引入一個代理層(通常稱為Sidecar),將服務治理的邏輯從應用程式碼中分離出來,實現了更好的治理和管理。然而,服務網格的引入也帶來了額外的複雜性和效能開銷。
在這樣的背景下,我們透過Java位元組碼增強技術,在服務治理領域提供了一種創新的解決方案。它結合了SDK和服務網格的優點,提供了無侵入性的治理邏輯注入、靈活的擴充套件性和高效的效能最佳化。這種方法不僅簡化了微服務架構中的服務治理,還提升了系統的可觀測性和安全性,對於現代微服務環境具有重要意義。
1.2 專案概述
Joylive Agent 是一個基於位元組碼增強的框架,專注於多活和單元化場景下的流量治理。它提供了以下功能:多活流量排程、全鏈路灰度釋出、QPS和併發限制、標籤路由、負載均衡,熔斷降級,鑑權等流量治理策略。其特性包括微核心架構、強類隔離、業務零侵入等,使其在保持高效能的同時對業務程式碼影響最小,是面向Java領域的新一代Proxyless Service Mesh探索實現。
專案地址:https://github.com/jd-opensource/joylive-agent
重要的事情說三遍:求Star,求Star,求Star。請Star完畢後繼續閱讀後文。
2. 微服務架構演進及優缺點
2.1 單體架構階段
最初,大多數應用都是作為單體應用開發的。所有功能都集中在一個程式碼庫中,部署也是作為一個整體。這種形式也是我們學習程式設計之初,最原始的模樣。確切的說,這種形態並不屬於微服務。如下圖所示:
優點: 簡單、易於開發和測試,適合小團隊和小規模應用。
缺點: 這種架構隨著應用規模增大,可能會面臨維護困難、擴充套件性差等問題。
2.2 垂直拆分階段
隨著應用規模的成長,此時會考慮將每個功能模組(服務)拆分為獨立的應用,也就是垂直拆分,擁有自己的程式碼庫、資料庫和部署生命週期。服務之間透過輕量級協議(如HTTP、gRPC)通訊。也就是正式開啟了面向服務的架構(SOA)。這種形態體現為:服務發現透過DNS解析,Consumer與Provider之間會有LB進行流量治理。服務間透過API進行通訊。如下圖所示:
優點: 獨立部署和擴充套件,每個服務可以由獨立的團隊開發和維護,提高了敏捷性。
缺點: 增加了分散式系統的複雜性,需要處理服務間通訊、資料一致性、服務發現、負載均衡等問題,也因為中間引入LB而降低了效能。
2.3 微服務成熟階段
這個階段引入更多的微服務治理和管理工具,使用專業的微服務框架或中介軟體,透過專門定製的微服務通訊協議,讓應用取得更高的吞吐效能。如API閘道器、註冊中心、分散式追蹤等。DevOps和持續整合/持續部署(CI/CD)流程成熟。代表產物如Spring Cloud,Dubbo等。此時典型的微服務場景還都是具體的微服務SDK提供的治理能力。通訊流程為:SDK負責向註冊中心註冊當前服務資訊,當需要進行服務消費時,同樣向註冊中心請求服務提供者資訊,然後直連服務提供者IP及埠併傳送請求。如下圖所示:
優點: 高度可擴充套件、彈性和靈活性,支援高頻率的釋出和更新。
缺點: 系統複雜性和運維成本較高,需要成熟的技術棧和團隊能力。微服務治理能力依賴SDK,升級更新成本高,需要繫結業務應用更新。
2.4 服務網格架構
隨著雲原生容器化時代的到來,服務網格是一種專門用於管理微服務之間通訊的基礎設施層。它通常包含一組輕量級的網路代理(通常稱為 sidecar),這些代理與每個服務例項一起部署,這利用了K8s中Pod的基礎能力。服務網格負責處理服務間的通訊、流量管理、安全性、監控和彈性等功能。這種微服務治理方式也可以稱之為Proxy模式,其中SideCar即作為服務之間的Proxy。如下圖所示:
優點: 主要優點是解耦業務邏輯與服務治理的能力,透過集中控制平面(control plane)簡化了運維管理。
缺點: 增加了資源消耗,更高的運維挑戰。
3. 專案架構設計
有沒有一種微服務治理方案,既要有SDK架構的高效能、多功能的好處,又要有邊車架構的零侵入優勢, 還要方便好用?這就是專案設計的初衷。專案的設計充分考慮了上面微服務的架構歷史,結合多活流量治理模型,進行了重新設計。其中專案設計到的主要技術點如下,並進行詳細解析。如下圖所示:
3.1 Proxyless模式
Proxyless模式(無代理模式)是為了最佳化效能和減少資源消耗而引入的。傳統的微服務網格通常使用邊車代理(Sidecar Proxy)來處理服務之間的通訊、安全、流量管理等功能。
我們選擇透過Java Agent模式實現Proxyless模式是一種將服務網格的功能(如服務發現、負載均衡、流量管理和安全性)直接整合到Java應用程式中的方法。這種方式可以利用Java Agent在執行時對應用程式進行位元組碼操作,從而無縫地將服務網格功能注入到應用程式中,而無需顯式修改應用程式碼。Java Agent模式實現Proxyless的優點如下:
效能最佳化:
-
減少網路延遲:傳統的邊車代理模式會引入額外的網路跳數,因為每個請求都需要透過邊車代理進行處理。透過Java Agent直接將服務網格功能注入到應用程式中,可以減少這些額外的網路開銷,從而降低延遲。
-
降低資源消耗:不再需要執行額外的邊車代理,從而減少了CPU、記憶體和網路資源的佔用。這對需要高效利用資源的應用非常重要。
簡化運維:
-
統一管理:透過Java Agent實現Proxyless模式,所有服務網格相關的配置和管理可以集中在控制平面進行,而無需在每個服務例項中單獨配置邊車代理。這簡化了運維工作,特別是在大型分散式系統中。
-
減少環境複雜性:透過消除邊車代理的配置和部署,環境的複雜性降低,減少了可能出現的配置錯誤或版本不相容問題。
-
資料局面升級:Java Agent作為服務治理資料面,天然與應用程式解耦,這點是相對於SDK的最大優點。當資料面面臨版本升級迭代時,可以統一管控而不依賴於使用者應用的重新打包構建。
靈活性:
-
無需修改原始碼與現有生態系統相容:Java Agent可以在執行時對應用程式進行位元組碼操作,直接在位元組碼層面插入服務網格相關的邏輯,而無需開發者修改應用程式的原始碼。這使得現有應用能夠輕鬆整合Proxyless模式。
-
動態載入和解除安裝:Java Agent可以在應用程式啟動時或執行時動態載入和解除安裝。這意味著服務網格功能可以靈活地新增或移除,適應不同的執行時需求。
適用性廣:
-
支援遺留系統:對於無法修改原始碼的遺留系統,Java Agent是一種理想的方式,能夠將現代化的服務網格功能整合到老舊系統中,提升其功能和效能。
透過Java Agent實現Proxyless模式,能夠在保持現有系統穩定性的同時,享受服務網格帶來的強大功能,是一種高效且靈活的解決方案。
3.2 微核心架構概述
微核心架構是一種軟體設計模式,主要分為核心功能(微核心)和一系列的外掛或服務模組。微核心負責處理系統的基礎功能,而其他功能則透過獨立的外掛或模組實現。這種架構的主要優點是模組化、可擴充套件性強,並且系統的核心部分保持輕量級。
核心元件:框架的核心元件更多的定義核心功能介面的抽象設計,模型的定義以及agent載入與類隔離等核心功能,為達到最小化依賴,很多核心功能都是基於自研程式碼實現。具體可參見joylive-core
程式碼模組。
外掛化設計:使用了模組化和外掛化的設計,分別抽象了像保護外掛,註冊外掛,路由外掛,透傳外掛等豐富的外掛生態,極大的豐富了框架的可擴充套件性,為適配多樣化的開源生態奠定了基礎。具體可參見joylive-plugin
程式碼模組。
3.3 外掛擴充套件體系
專案基於Java的SPI機制實現了外掛化的擴充套件方式,這也是Java生態的主流方式。
3.3.1 定義擴充套件
定義擴充套件介面,並使用@Extensible
註解來進行擴充套件的宣告。下面是個負載均衡擴充套件示例:
@Extensible("LoadBalancer")
public interface LoadBalancer {
int ORDER_RANDOM_WEIGHT = 0;
int ORDER_ROUND_ROBIN = ORDER_RANDOM_WEIGHT + 1;
default <T extends Endpoint> T choose(List<T> endpoints, Invocation<?> invocation) {
Candidate<T> candidate = elect(endpoints, invocation);
return candidate == null ? null : candidate.getTarget();
}
<T extends Endpoint> Candidate<T> elect(List<T> endpoints, Invocation<?> invocation);
}
3.3.2 實現擴充套件
實現擴充套件介面,並使用@Extension
註解來進行擴充套件實現的宣告。如下是實現了LoadBalancer
介面的實現類:
@Extension(value = RoundRobinLoadBalancer.LOAD_BALANCER_NAME, order = LoadBalancer.ORDER_ROUND_ROBIN)
@ConditionalOnProperties(value = {
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_ENABLED, matchIfMissing = true),
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LANE_ENABLED, matchIfMissing = true),
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED, matchIfMissing = true)
}, relation = ConditionalRelation.OR)
public class RoundRobinLoadBalancer extends AbstractLoadBalancer {
public static final String LOAD_BALANCER_NAME = "ROUND_ROBIN";
private static final Function<Long, AtomicLong> COUNTER_FUNC = s -> new AtomicLong(0L);
private final Map<Long, AtomicLong> counters = new ConcurrentHashMap<>();
private final AtomicLong global = new AtomicLong(0);
@Override
public <T extends Endpoint> Candidate<T> doElect(List<T> endpoints, Invocation<?> invocation) {
AtomicLong counter = global;
ServicePolicy servicePolicy = invocation.getServiceMetadata().getServicePolicy();
LoadBalancePolicy loadBalancePolicy = servicePolicy == null ? null : servicePolicy.getLoadBalancePolicy();
if (loadBalancePolicy != null) {
counter = counters.computeIfAbsent(loadBalancePolicy.getId(), COUNTER_FUNC);
}
long count = counter.getAndIncrement();
if (count < 0) {
counter.set(0);
count = counter.getAndIncrement();
}
// Ensure the index is within the bounds of the endpoints list.
int index = (int) (count % endpoints.size());
return new Candidate<>(endpoints.get(index), index);
}