1. 引言
1.1 項目的背景及意義
在當(dāng)今的微服務(wù)架構(gòu)中,應(yīng)用程序通常被拆分成多個獨立的服務(wù),這些服務(wù)通過網(wǎng)絡(luò)進(jìn)行通信。這種架構(gòu)的優(yōu)勢在于可以提高系統(tǒng)的可擴(kuò)展性和靈活性,但也帶來了新的挑戰(zhàn),比如:
服務(wù)間通信的復(fù)雜性:不同服務(wù)之間需要進(jìn)行可靠的通信,處理失敗重試、負(fù)載均衡等問題。
故障的容錯處理:系統(tǒng)的復(fù)雜性給與運維及故障處理帶來更大的挑戰(zhàn),如何快速處理故障解決線上問題,這是考驗一個企業(yè)基礎(chǔ)設(shè)施建設(shè)的重要關(guān)卡。
最初,開發(fā)者使用SDK來解決這些問題,通過在代碼中集成各種庫和工具來實現(xiàn)服務(wù)治理。然而,隨著微服務(wù)架構(gòu)的規(guī)模不斷擴(kuò)大,這種方法逐漸顯現(xiàn)出局限性:
代碼侵入性:需要在每個服務(wù)的代碼中集成和配置各種庫,增加了代碼的復(fù)雜性和維護(hù)成本。
一致性問題:不同服務(wù)可能使用不同版本的庫,導(dǎo)致治理邏輯不一致,SDK的升級難度凸顯。
為了解決這些問題,服務(wù)網(wǎng)格(Service Mesh)應(yīng)運而生。服務(wù)網(wǎng)格通過在服務(wù)間引入一個代理層(通常稱為Sidecar),將服務(wù)治理的邏輯從應(yīng)用代碼中分離出來,實現(xiàn)了更好的治理和管理。然而,服務(wù)網(wǎng)格的引入也帶來了額外的復(fù)雜性和性能開銷。
在這樣的背景下,我們通過Java字節(jié)碼增強(qiáng)技術(shù),在服務(wù)治理領(lǐng)域提供了一種創(chuàng)新的解決方案。它結(jié)合了SDK和服務(wù)網(wǎng)格的優(yōu)點,提供了無侵入性的治理邏輯注入、靈活的擴(kuò)展性和高效的性能優(yōu)化。這種方法不僅簡化了微服務(wù)架構(gòu)中的服務(wù)治理,還提升了系統(tǒng)的可觀測性和安全性,對于現(xiàn)代微服務(wù)環(huán)境具有重要意義。
1.2 項目概述
Joylive Agent 是一個基于字節(jié)碼增強(qiáng)的框架,專注于多活和單元化場景下的流量治理。它提供了以下功能:多活流量調(diào)度、全鏈路灰度發(fā)布、QPS和并發(fā)限制、標(biāo)簽路由、負(fù)載均衡,熔斷降級,鑒權(quán)等流量治理策略。其特性包括微內(nèi)核架構(gòu)、強(qiáng)類隔離、業(yè)務(wù)零侵入等,使其在保持高性能的同時對業(yè)務(wù)代碼影響最小,是面向Java領(lǐng)域的新一代Proxyless Service Mesh探索實現(xiàn)。
項目地址:
https://github.com/jd-opensource/joylive-agent
重要的事情說三遍:求Star,求Star,求Star。請Star完畢后繼續(xù)閱讀后文。
2. 微服務(wù)架構(gòu)演進(jìn)及優(yōu)缺點
2.1 單體架構(gòu)階段
最初,大多數(shù)應(yīng)用都是作為單體應(yīng)用開發(fā)的。所有功能都集中在一個代碼庫中,部署也是作為一個整體。這種形式也是我們學(xué)習(xí)編程之初,最原始的模樣。確切的說,這種形態(tài)并不屬于微服務(wù)。如下圖所示:

優(yōu)點: 簡單、易于開發(fā)和測試,適合小團(tuán)隊和小規(guī)模應(yīng)用。
缺點: 這種架構(gòu)隨著應(yīng)用規(guī)模增大,可能會面臨維護(hù)困難、擴(kuò)展性差等問題。
2.2 垂直拆分階段
隨著應(yīng)用規(guī)模的成長,此時會考慮將每個功能模塊(服務(wù))拆分為獨立的應(yīng)用,也就是垂直拆分,擁有自己的代碼庫、數(shù)據(jù)庫和部署生命周期。服務(wù)之間通過輕量級協(xié)議(如HTTP、gRPC)通信。也就是正式開啟了面向服務(wù)的架構(gòu)(SOA)。這種形態(tài)體現(xiàn)為:服務(wù)發(fā)現(xiàn)通過DNS解析,Consumer與Provider之間會有LB進(jìn)行流量治理。服務(wù)間通過API進(jìn)行通信。如下圖所示:

優(yōu)點: 獨立部署和擴(kuò)展,每個服務(wù)可以由獨立的團(tuán)隊開發(fā)和維護(hù),提高了敏捷性。
缺點: 增加了分布式系統(tǒng)的復(fù)雜性,需要處理服務(wù)間通信、數(shù)據(jù)一致性、服務(wù)發(fā)現(xiàn)、負(fù)載均衡等問題,也因為中間引入LB而降低了性能。
2.3 微服務(wù)成熟階段
這個階段引入更多的微服務(wù)治理和管理工具,使用專業(yè)的微服務(wù)框架或中間件,通過專門定制的微服務(wù)通訊協(xié)議,讓應(yīng)用取得更高的吞吐性能。如API網(wǎng)關(guān)、注冊中心、分布式追蹤等。DevOps和持續(xù)集成/持續(xù)部署(CI/CD)流程成熟。代表產(chǎn)物如Spring Cloud,Dubbo等。此時典型的微服務(wù)場景還都是具體的微服務(wù)SDK提供的治理能力。通訊流程為:SDK負(fù)責(zé)向注冊中心注冊當(dāng)前服務(wù)信息,當(dāng)需要進(jìn)行服務(wù)消費時,同樣向注冊中心請求服務(wù)提供者信息,然后直連服務(wù)提供者IP及端口并發(fā)送請求。如下圖所示:

優(yōu)點: 高度可擴(kuò)展、彈性和靈活性,支持高頻率的發(fā)布和更新。
缺點: 系統(tǒng)復(fù)雜性和運維成本較高,需要成熟的技術(shù)棧和團(tuán)隊能力。微服務(wù)治理能力依賴SDK,升級更新成本高,需要綁定業(yè)務(wù)應(yīng)用更新。
2.4 服務(wù)網(wǎng)格架構(gòu)
隨著云原生容器化時代的到來,服務(wù)網(wǎng)格是一種專門用于管理微服務(wù)之間通信的基礎(chǔ)設(shè)施層。它通常包含一組輕量級的網(wǎng)絡(luò)代理(通常稱為 sidecar),這些代理與每個服務(wù)實例一起部署,這利用了K8s中Pod的基礎(chǔ)能力。服務(wù)網(wǎng)格負(fù)責(zé)處理服務(wù)間的通信、流量管理、安全性、監(jiān)控和彈性等功能。這種微服務(wù)治理方式也可以稱之為Proxy模式,其中SideCar即作為服務(wù)之間的Proxy。如下圖所示:

優(yōu)點: 主要優(yōu)點是解耦業(yè)務(wù)邏輯與服務(wù)治理的能力,通過集中控制平面(control plane)簡化了運維管理。
缺點: 增加了資源消耗,更高的運維挑戰(zhàn)。
3. 項目架構(gòu)設(shè)計
有沒有一種微服務(wù)治理方案,既要有SDK架構(gòu)的高性能、多功能的好處,又要有邊車架構(gòu)的零侵入優(yōu)勢, 還要方便好用?這就是項目設(shè)計的初衷。項目的設(shè)計充分考慮了上面微服務(wù)的架構(gòu)歷史,結(jié)合多活流量治理模型,進(jìn)行了重新設(shè)計。其中項目設(shè)計到的主要技術(shù)點如下,并進(jìn)行詳細(xì)解析。如下圖所示:

3.1 Proxyless模式
Proxyless模式(無代理模式)是為了優(yōu)化性能和減少資源消耗而引入的。傳統(tǒng)的微服務(wù)網(wǎng)格通常使用邊車代理(Sidecar Proxy)來處理服務(wù)之間的通信、安全、流量管理等功能。
我們選擇通過Java Agent模式實現(xiàn)Proxyless模式是一種將服務(wù)網(wǎng)格的功能(如服務(wù)發(fā)現(xiàn)、負(fù)載均衡、流量管理和安全性)直接集成到Java應(yīng)用程序中的方法。這種方式可以利用Java Agent在運行時對應(yīng)用程序進(jìn)行字節(jié)碼操作,從而無縫地將服務(wù)網(wǎng)格功能注入到應(yīng)用程序中,而無需顯式修改應(yīng)用代碼。Java Agent模式實現(xiàn)Proxyless的優(yōu)點如下:
性能優(yōu)化:
減少網(wǎng)絡(luò)延遲:傳統(tǒng)的邊車代理模式會引入額外的網(wǎng)絡(luò)跳數(shù),因為每個請求都需要通過邊車代理進(jìn)行處理。通過Java Agent直接將服務(wù)網(wǎng)格功能注入到應(yīng)用程序中,可以減少這些額外的網(wǎng)絡(luò)開銷,從而降低延遲。
降低資源消耗:不再需要運行額外的邊車代理,從而減少了CPU、內(nèi)存和網(wǎng)絡(luò)資源的占用。這對需要高效利用資源的應(yīng)用非常重要。
簡化運維:
統(tǒng)一管理:通過Java Agent實現(xiàn)Proxyless模式,所有服務(wù)網(wǎng)格相關(guān)的配置和管理可以集中在控制平面進(jìn)行,而無需在每個服務(wù)實例中單獨配置邊車代理。這簡化了運維工作,特別是在大型分布式系統(tǒng)中。
減少環(huán)境復(fù)雜性:通過消除邊車代理的配置和部署,環(huán)境的復(fù)雜性降低,減少了可能出現(xiàn)的配置錯誤或版本不兼容問題。
數(shù)據(jù)局面升級:Java Agent作為服務(wù)治理數(shù)據(jù)面,天然與應(yīng)用程序解耦,這點是相對于SDK的最大優(yōu)點。當(dāng)數(shù)據(jù)面面臨版本升級迭代時,可以統(tǒng)一管控而不依賴于用戶應(yīng)用的重新打包構(gòu)建。
靈活性:
無需修改源代碼與現(xiàn)有生態(tài)系統(tǒng)兼容:Java Agent可以在運行時對應(yīng)用程序進(jìn)行字節(jié)碼操作,直接在字節(jié)碼層面插入服務(wù)網(wǎng)格相關(guān)的邏輯,而無需開發(fā)者修改應(yīng)用程序的源代碼。這使得現(xiàn)有應(yīng)用能夠輕松集成Proxyless模式。
動態(tài)加載和卸載:Java Agent可以在應(yīng)用程序啟動時或運行時動態(tài)加載和卸載。這意味著服務(wù)網(wǎng)格功能可以靈活地添加或移除,適應(yīng)不同的運行時需求。
適用性廣:
支持遺留系統(tǒng):對于無法修改源代碼的遺留系統(tǒng),Java Agent是一種理想的方式,能夠?qū)F(xiàn)代化的服務(wù)網(wǎng)格功能集成到老舊系統(tǒng)中,提升其功能和性能。
通過Java Agent實現(xiàn)Proxyless模式,能夠在保持現(xiàn)有系統(tǒng)穩(wěn)定性的同時,享受服務(wù)網(wǎng)格帶來的強(qiáng)大功能,是一種高效且靈活的解決方案。
3.2 微內(nèi)核架構(gòu)概述
微內(nèi)核架構(gòu)是一種軟件設(shè)計模式,主要分為核心功能(微內(nèi)核)和一系列的插件或服務(wù)模塊。微內(nèi)核負(fù)責(zé)處理系統(tǒng)的基礎(chǔ)功能,而其他功能則通過獨立的插件或模塊實現(xiàn)。這種架構(gòu)的主要優(yōu)點是模塊化、可擴(kuò)展性強(qiáng),并且系統(tǒng)的核心部分保持輕量級。
核心組件:框架的核心組件更多的定義核心功能接口的抽象設(shè)計,模型的定義以及agent加載與類隔離等核心功能,為達(dá)到最小化依賴,很多核心功能都是基于自研代碼實現(xiàn)。具體可參見joylive-core代碼模塊。
插件化設(shè)計:使用了模塊化和插件化的設(shè)計,分別抽象了像保護(hù)插件,注冊插件,路由插件,透傳插件等豐富的插件生態(tài),極大的豐富了框架的可擴(kuò)展性,為適配多樣化的開源生態(tài)奠定了基礎(chǔ)。具體可參見joylive-plugin代碼模塊。
3.3 插件擴(kuò)展體系
項目基于Java的SPI機(jī)制實現(xiàn)了插件化的擴(kuò)展方式,這也是Java生態(tài)的主流方式。
3.3.1 定義擴(kuò)展
定義擴(kuò)展接口,并使用@Extensible注解來進(jìn)行擴(kuò)展的聲明。下面是個負(fù)載均衡擴(kuò)展示例:
@Extensible("LoadBalancer")public interface LoadBalancer {
int ORDER_RANDOM_WEIGHT = 0;
int ORDER_ROUND_ROBIN = ORDER_RANDOM_WEIGHT + 1;
default T choose(List endpoints, Invocation??> invocation) {
Candidate candidate = elect(endpoints, invocation);
return candidate == null ? null : candidate.getTarget();
}
Candidate elect(List endpoints, Invocation??> invocation);}
3.3.2 實現(xiàn)擴(kuò)展
實現(xiàn)擴(kuò)展接口,并使用@Extension注解來進(jìn)行擴(kuò)展實現(xiàn)的聲明。如下是實現(xiàn)了LoadBalancer接口的實現(xiàn)類:
@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 COUNTER_FUNC = s -> new AtomicLong(0L);
private final Map counters = new ConcurrentHashMap?>();
private final AtomicLong global = new AtomicLong(0);
@Override
public Candidate doElect(List 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);
}}
該類上的注解如下:
@Extension注解聲明擴(kuò)展實現(xiàn),并提供了名稱
@ConditionalOnProperty注解聲明啟用的條件,可以組合多個條件
3.3.3 啟用擴(kuò)展
在SPI文件
META-INF/services/com.jd.live.agent.governance.invoke.loadbalance.LoadBalancer中配置擴(kuò)展全路徑名
com.jd.live.agent.governance.invoke.loadbalance.roundrobin.RoundRobinLoadBalancer即達(dá)到啟用效果。
更多詳情可查閱:
https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/extension.md
3.4 依賴注入設(shè)計
說到依賴注入估計大家會立馬想到Spring,的確這是Spring的看家本領(lǐng)。在復(fù)雜的工程中,自動化的依賴注入確實會簡化工程的實現(xiàn)復(fù)雜度。讓開發(fā)人員從復(fù)雜的依賴構(gòu)建中脫離出來,專注于功能點設(shè)計開發(fā)。依賴注入的實現(xiàn)是基于上面插件擴(kuò)展體系,是插件擴(kuò)展的功能增強(qiáng)。并且依賴注入支持了兩類場景:注入對象與注入配置。該功能主要有4個注解類與3個接口構(gòu)成。
3.4.1 @Injectable
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Injectable {
boolean enable() default true;}
這是一個非常簡潔的可以應(yīng)用于類、接口(包括注解類型)或枚舉注解。其目的為了標(biāo)識哪些類開啟了自動注入對象的要求。這點不同于Spring的控制范圍,而是按需注入。實例構(gòu)建完成后,在自動注入的邏輯過程中會針對添加@Injectable注解的實例進(jìn)行依賴對象注入。
3.4.2 @Inject
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Inject {
String value() default "";
boolean nullable() default false;
ResourcerType loader() default ResourcerType.CORE_IMPL;}
該注解用于自動注入值到字段。它用于指定一個字段在運行時應(yīng)該注入一個值,支持基于配置的注入。還可以指示被注入的值是否可以為 null,如果注入過程中無注入實例或注入實例為null,而nullable配置為false,則會拋出異常。loader定義了指定要為注釋字段加載的資源或類實現(xiàn)的類型。ResourcerType為枚舉類型,分別是:CORE,CORE_IMPL,PLUGIN。劃分依據(jù)是工程打包后的jar文件分布目錄。因為不同目錄的類加載器是不同的(類隔離的原因,后面會講到)。所以可以簡單理解,這個配置是用于指定加載該對象所對應(yīng)的類加載器。
3.4.3 @Configurable
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Configurable {
String prefix() default "";
boolean auto() default false;}
這個注解類似于@Injectable,用于指定哪些類啟用自動注入配置文件的支持。prefix指定用于配置鍵的前綴。這通常意味著前綴將來自類名或基于某種約定。auto指示配置值是否應(yīng)自動注入到注解類的所有合規(guī)字段中。默認(rèn)值為 false,這意味著默認(rèn)情況下未啟用自動注入。
3.4.4 @Config
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Config {
String value() default "";
boolean nullable() default true;}
該注解用于指定字段配置詳細(xì)信息。它定義了字段的配置鍵以及配置是否為可選。此注釋可用于在運行時自動將配置值加載到字段中,并支持指定缺少配置(無配置)是否被允許。nullable指示字段的配置是否是可選的。如果為真,則系統(tǒng)將允許配置缺失而不會導(dǎo)致錯誤。
下面是具體的使用示例:
@Injectable@Extension(value = "CircuitBreakerFilter", order = OutboundFilter.ORDER_CIRCUIT_BREAKER)public class CircuitBreakerFilter implements OutboundFilter, ExtensionInitializer { @Inject private Map factories; @Inject(nullable = true) private CircuitBreakerFactory defaultFactory; @Inject(GovernanceConfig.COMPONENT_GOVERNANCE_CONFIG) private GovernanceConfig governanceConfig; ...}@Configurable(prefix = "app")public class Application { @Setter @Config("name") private String name; @Setter @Config("service") private AppService service; ...}
更多細(xì)節(jié),因為篇幅原因不再展開。詳情可以了解:
https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/extension.md
3.5 字節(jié)碼增強(qiáng)機(jī)制
Java 的字節(jié)碼增強(qiáng)(Bytecode Enhancement)是一種動態(tài)改變 Java 字節(jié)碼的技術(shù),允許開發(fā)者在類加載之前或加載過程中修改類的字節(jié)碼。這種機(jī)制在 AOP(面向切面編程)、框架增強(qiáng)、性能監(jiān)控、日志記錄等領(lǐng)域廣泛應(yīng)用。目前看此技術(shù)在APM產(chǎn)品使用較多,而針對流量治理方向的開源實現(xiàn)還是比較少的。
Java字節(jié)碼增強(qiáng)的主要方法有:
運行時增強(qiáng):使用Java Agent在類加載時修改字節(jié)碼。
加載時增強(qiáng):在類加載到JVM之前修改字節(jié)碼。
編譯時增強(qiáng):在編譯階段修改或生成字節(jié)碼。
進(jìn)行字節(jié)碼增強(qiáng)的框架有很多,例如:JavaAssist、ASM、ByteBuddy、ByteKit等。我們針對字節(jié)碼增強(qiáng)的過程及重要對象進(jìn)行了接口抽象,并以插件化方式適配了ByteBuddy,開發(fā)了一種默認(rèn)實現(xiàn)。當(dāng)然你也可以使用其他的框架實現(xiàn)相應(yīng)的接口,作為擴(kuò)展的其他實現(xiàn)方式。下面以ByteBuddy為例,展示一個入門實例:
import net.bytebuddy.ByteBuddy;import net.bytebuddy.implementation.MethodDelegation;import net.bytebuddy.implementation.SuperMethodCall;import net.bytebuddy.matcher.ElementMatchers;import java.lang.reflect.Method;// 原始類class SimpleClass { public void sayHello() { System.out.println("Hello, World!"); }}// 攔截器class SimpleInterceptor { public static void beforeMethod() { System.out.println("Before saying hello"); } public static void afterMethod() { System.out.println("After saying hello"); }}public class ByteBuddyExample { public static void main(String[] args) throws Exception { // 使用ByteBuddy創(chuàng)建增強(qiáng)類 Class??> dynamicType = new ByteBuddy() .subclass(SimpleClass.class) .method(ElementMatchers.named("sayHello")) .intercept(MethodDelegation.to(SimpleInterceptor.class) .andThen(SuperMethodCall.INSTANCE)) .make() .load(ByteBuddyExample.class.getClassLoader()) .getLoaded(); // 創(chuàng)建增強(qiáng)類的實例 Object enhancedInstance = dynamicType.getDeclaredConstructor().newInstance(); // 調(diào)用增強(qiáng)后的方法 Method sayHelloMethod = enhancedInstance.getClass().getMethod("sayHello"); sayHelloMethod.invoke(enhancedInstance); }}
這個例子展示了如何使用ByteBuddy來增強(qiáng)SimpleClass的sayHello方法。讓我解釋一下這個過程:
我們定義了一個簡單的SimpleClass,它有一個sayHello方法。
我們創(chuàng)建了一個SimpleInterceptor類,包含了我們想要在原方法執(zhí)行前后添加的邏輯。
在ByteBuddyExample類的main方法中,我們使用ByteBuddy來創(chuàng)建一個增強(qiáng)的類:
我們創(chuàng)建了SimpleClass的一個子類。
我們攔截了名為"sayHello"的方法。
我們使用MethodDelegation.to(SimpleInterceptor.class)來添加前置和后置邏輯。
我們使用SuperMethodCall.INSTANCE來確保原始方法被調(diào)用。
我們創(chuàng)建了增強(qiáng)類的實例,并通過反射調(diào)用了sayHello方法。
當(dāng)你運行這個程序時,輸出將會是:
Before saying hello Hello, World!After saying hello
當(dāng)然,工程級別的實現(xiàn)是遠(yuǎn)比上面Demo的組織形式復(fù)雜的。插件是基于擴(kuò)展實現(xiàn)的,有多個擴(kuò)展組成,對某個框架進(jìn)行特定增強(qiáng),實現(xiàn)了多活流量治理等等業(yè)務(wù)邏輯。一個插件打包成一個目錄,如下圖所示:
.└── plugin
├── dubbo
│ ├── joylive-registry-dubbo2.6-1.0.0.jar
│ ├── joylive-registry-dubbo2.7-1.0.0.jar
│ ├── joylive-registry-dubbo3-1.0.0.jar
│ ├── joylive-router-dubbo2.6-1.0.0.jar
│ ├── joylive-router-dubbo2.7-1.0.0.jar
│ ├── joylive-router-dubbo3-1.0.0.jar
│ ├── joylive-transmission-dubbo2.6-1.0.0.jar
│ ├── joylive-transmission-dubbo2.7-1.0.0.jar
│ └── joylive-transmission-dubbo3-1.0.0.jar
該dubbo插件,支持了3個版本,增強(qiáng)了注冊中心,路由和鏈路透傳的能力。下面介紹一下在joylive-agent中如何實現(xiàn)一個字節(jié)碼增強(qiáng)插件。
3.5.1 增強(qiáng)插件定義接口
增強(qiáng)插件(功能實現(xiàn)層面的插件)接口的定義采用了插件(系統(tǒng)架構(gòu)層面的插件)擴(kuò)展機(jī)制。如下代碼所示,定義的增強(qiáng)插件名稱為PluginDefinition。
@Extensible("PluginDefinition")public interface PluginDefinition {
ElementMatcher getMatcher();
InterceptorDefinition[] getInterceptors();}
接口共定義了兩個方法:getMatcher用于獲取匹配要增強(qiáng)類的匹配器,getInterceptors是返回要增強(qiáng)目標(biāo)類的攔截器定義對象。
3.5.2 攔截器定義接口
攔截器定義接口主要是用來確定攔截增強(qiáng)位置(也就是方法),定位到具體方法也就找到了具體增強(qiáng)邏輯執(zhí)行的位置。攔截器定義接口并沒有采用擴(kuò)展機(jī)制,這是因為具體到某個增強(qiáng)目標(biāo)類后,要增強(qiáng)的方法與增強(qiáng)邏輯已經(jīng)是確定行為,不再需要通過擴(kuò)展機(jī)制實例化對象,在具體的增強(qiáng)插件定義接口實現(xiàn)里會直接通過new的方式構(gòu)造蘭機(jī)器定義實現(xiàn)。攔截器定義接口如下:
public interface InterceptorDefinition {
ElementMatcher getMatcher();
Interceptor getInterceptor();}
該接口同樣抽象了兩個方法:getMatcher用于獲取匹配要增強(qiáng)方法的匹配器,getInterceptor是用于返回具體的增強(qiáng)邏輯實現(xiàn),我們稱之為攔截器(Interceptor)。
3.5.3 攔截器接口
攔截器的實現(xiàn)類是具體增強(qiáng)邏輯的載體,當(dāng)我們要增強(qiáng)某個類的某個方法時,與AOP機(jī)制同理,我們抽象了幾處攔截位置。分別是:方法執(zhí)行前(剛進(jìn)入方法執(zhí)行邏輯);方法執(zhí)行結(jié)束時;方法執(zhí)行成功時(無異常);方法執(zhí)行出錯時(有異常)。接口定義如下:
public interface Interceptor {
void onEnter(ExecutableContext ctx);
void onSuccess(ExecutableContext ctx);
void onError(ExecutableContext ctx);
void onExit(ExecutableContext ctx);}
增強(qiáng)邏輯的實現(xiàn)可以針對不同的功能目標(biāo),選擇合適的增強(qiáng)點。同樣,攔截點的入?yún)xecutableContext也是非常重要的組成部分,它承載了運行時的上下文信息,并且針對不同的增強(qiáng)目標(biāo)我們做了不同的實現(xiàn)。如下圖所示:

更多詳情可查看:
https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/plugin.md
3.6 類加載與類隔離
類加載的原理比較容易理解,因為在Java Agent模式下,應(yīng)用啟動時agent需要加載它所依賴的jar包。然而,如果這些類在加載后被用戶應(yīng)用的類加載器感知到,就可能導(dǎo)致類沖突甚至不兼容的風(fēng)險。因此,引入類隔離機(jī)制是為了解決這個問題。類隔離的實現(xiàn)原理并不復(fù)雜。首先,需要實現(xiàn)自定義的類加載器;其次,需要打破默認(rèn)的雙親委派機(jī)制。通過這兩步,類隔離可以實現(xiàn)多層次的隔離,從而避免類沖突和不兼容問題。如下圖所示:

3.7 面向請求的抽象
整個框架的核心行為就是治理請求,而站在一個應(yīng)用的視角我們可以大體把請求抽象為兩類:InboundRequest與OutboundRequest。InboundRequest是外部進(jìn)入當(dāng)前應(yīng)用的請求,OutboundRequest是當(dāng)前應(yīng)用發(fā)往外部資源的請求。同樣的處理這些請求的過濾器也同樣分為InboundFilter與OutboundFilter。
請求接口

請求抽象實現(xiàn)

具體框架的請求適配,如Dubbo

如上圖所示,展現(xiàn)了適配Dubbo的請求對象的DubboOutboundRequest與DubboInboundRequest,體現(xiàn)了一個OutboundRequest與InboundRequest的實現(xiàn)與繼承關(guān)系。整體看起來確實比較復(fù)雜。這是因為在請求抽象時,不僅要考慮請求是Inbound還是Outbound,還要適配不同的協(xié)議框架。例如,像Dubbo和JSF這樣的私有通訊協(xié)議框架需要統(tǒng)一為RpcRequest接口的實現(xiàn),而SpringCloud這樣的HTTP通訊協(xié)議則統(tǒng)一為HttpRequest。再加上Inbound和Outbound的分類維度,整體的抽象在追求高擴(kuò)展性的同時也增加了復(fù)雜性。
4. 核心功能
下面提供的流量治理功能,以API網(wǎng)關(guān)作為東西向流量第一入口進(jìn)行流量識別染色。在很大程度上,API網(wǎng)關(guān)作為東西向流量識別的第一入口發(fā)揮了重要作用。API網(wǎng)關(guān)在接收到南北向流量后,后續(xù)將全部基于東西向流量治理。
4.1 多活模型及流量調(diào)度
應(yīng)用多活通常包括同城多活和異地多活,異地多活可采用單元化技術(shù)來實現(xiàn)。下面描述整個多活模型涉及到的概念及嵌套關(guān)系。具體實現(xiàn)原理如下圖所示:

4.1.1 多活空間
在模型和權(quán)限設(shè)計方面,我們支持多租戶模式。一個租戶可以有多個多活空間,多活空間構(gòu)成如下所示:
.└── 多活空間
├── 單元路由變量(*)
├── 單元(*)
│ ├── 分區(qū)(*)
├── 單元規(guī)則(*)
├── 多活域名(*)
│ ├── 單元子域名(*)
│ ├── 路徑(*)
│ │ ├── 業(yè)務(wù)參數(shù)(*)
4.1.2 單元
單元是邏輯上的概念,一般對應(yīng)一個地域。常用于異地多活場景,通過用戶維度拆分業(yè)務(wù)和數(shù)據(jù),每個單元獨立運作,降低單元故障對整體業(yè)務(wù)的影響。
單元的屬性包括單元代碼、名稱、類型(中心單元或普通單元)、讀寫權(quán)限、標(biāo)簽(如地域和可用區(qū))、以及單元下的分區(qū)。
單元分區(qū)是單元的組成部分,單元內(nèi)的邏輯分區(qū),對應(yīng)云上的可用區(qū)或物理數(shù)據(jù)中心,屬性類似單元。
4.1.3 路由變量
路由變量是決定流量路由到哪個單元的依據(jù),通常是用戶賬號。每個變量可以通過不同的取值方式(如Cookie、請求頭等)獲取,且可以定義轉(zhuǎn)換函數(shù)來獲取實際用戶標(biāo)識。
變量取值方式則描述如何從請求參數(shù)、請求頭或Cookie中獲取路由變量。
4.1.4 單元規(guī)則
單元規(guī)則定義了單元和分區(qū)之間的流量調(diào)度規(guī)則。根據(jù)路由變量計算出的值,通過取模判斷流量應(yīng)路由到哪個單元。它的屬性包括多活類型、變量、變量取值方式、計算函數(shù)、變量缺失時的操作、以及具體的單元路由規(guī)則。
單元路由規(guī)則定義了單元內(nèi)的流量路由規(guī)則,包括允許的路由變量白名單、前綴、值區(qū)間等。
分區(qū)路由規(guī)則定義了單元內(nèi)各個分區(qū)的流量路由規(guī)則,包括允許的變量白名單、前綴及權(quán)重。
4.1.5 多活域名
多活域名描述啟用多活的域名,用于在網(wǎng)關(guān)層進(jìn)行流量攔截和路由。支持跨地域和同城多活的流量管理,配置路徑規(guī)則以匹配請求路徑并執(zhí)行相應(yīng)的路由規(guī)則。
單元子域名描述各個單元的子域名,通常用于在HTTP請求或回調(diào)時閉環(huán)在單元內(nèi)進(jìn)行路由。
路徑規(guī)則定義了根據(jù)請求路徑匹配的路由規(guī)則,取最長匹配路徑來選擇適用的路由規(guī)則。
業(yè)務(wù)參數(shù)規(guī)則基于請求參數(shù)值進(jìn)一步精細(xì)化路由,選擇特定的單元規(guī)則。
4.1.6 模型骨架
以下是多活治理模型的基本配置樣例,包括API版本、空間名稱、單元、域名、規(guī)則、變量等。
[
{
"apiVersion": "apaas.cos.com/v2alpha1",
"kind": "MultiLiveSpace",
"metadata": {
"name": "mls-abcdefg1",
"namespace": "apaas-livespace"
},
"spec": {
"id": "v4bEh4kd6Jvu5QBX09qYq-qlbcs",
"code": "7Jei1Q5nlDbx0dRB4ZKd",
"name": "TestLiveSpace",
"version": "2023120609580935201",
"tenantId": "tenant1",
"units": [
],
"domains": [
],
"unitRules": [
],
"variables": [
]
}
}]
以上概念會比較晦澀難懂,更多詳情可以訪問:
https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/livespace.md
4.2 全鏈路灰度(泳道)
泳道是一種隔離和劃分系統(tǒng)中不同服務(wù)或組件的方式,類似于游泳池中的泳道劃分,確保每個服務(wù)或組件在自己的“泳道”中獨立運作。泳道概念主要用于以下幾種場景:
多租戶架構(gòu)中的隔離
在多租戶系統(tǒng)中,泳道通常用于隔離不同租戶的資源和服務(wù)。每個租戶有自己的獨立“泳道”,以確保數(shù)據(jù)和流量的隔離,防止不同租戶之間的相互影響。
流量隔離與管理
泳道可以用于根據(jù)特定規(guī)則(例如用戶屬性、地理位置、業(yè)務(wù)特性等)將流量分配到不同的微服務(wù)實例或集群中。這種方式允許團(tuán)隊在某些條件下測試新版本、進(jìn)行藍(lán)綠部署、金絲雀發(fā)布等,而不會影響到其他泳道中的流量。
業(yè)務(wù)邏輯劃分
在某些場景下,泳道也可以代表業(yè)務(wù)邏輯的劃分。例如,一個電商平臺可能會針對不同的用戶群體(如普通用戶、VIP用戶)提供不同的服務(wù)路徑和處理邏輯,形成不同的泳道。
版本管理
泳道可以用來管理微服務(wù)的不同版本,使得新版本和舊版本可以在不同的泳道中并行運行,從而降低升級時的風(fēng)險。
開發(fā)和測試
在開發(fā)和測試過程中,不同的泳道可以用于隔離開發(fā)中的新功能、測試環(huán)境、甚至是不同開發(fā)團(tuán)隊的工作,從而減少互相干擾。
泳道的核心目的是通過隔離服務(wù)或資源,提供獨立性和靈活性,確保系統(tǒng)的穩(wěn)定性和可擴(kuò)展性。這種隔離機(jī)制幫助組織更好地管理復(fù)雜系統(tǒng)中的多樣性,尤其是在處理高并發(fā)、多租戶、或者需要快速迭代的場景下。功能流程如下圖所示:

模型定義及更多詳情可以訪問:
https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/lane.md
4.3 微服務(wù)治理策略
微服務(wù)治理策略是指在微服務(wù)架構(gòu)中,為確保服務(wù)的穩(wěn)定性、可靠性、安全性以及高效運作而制定的一系列管理和控制措施。這些策略幫助企業(yè)有效地管理、監(jiān)控、協(xié)調(diào)和優(yōu)化成百上千個微服務(wù)的運行,以應(yīng)對分布式系統(tǒng)的復(fù)雜性。目前我們實現(xiàn)了部分主流的微服務(wù)策略例如:負(fù)載均衡,重試,限流,熔斷降級,標(biāo)簽路由,訪問鑒權(quán)等,更多的實用策略也在陸續(xù)補(bǔ)充中。微服務(wù)框架方面已經(jīng)支持主流框架,如:Spring Cloud,Dubbo2/3,JSF,SofaRpc等。
由于篇幅原因,具體的治理策略與模型就不詳盡展開介紹了,下圖概括了服務(wù)治理的全貌。

值得一提有兩點:
策略的實現(xiàn)屏蔽了底層框架的差異性,這得益于上面提到的面向請求的抽象。
統(tǒng)一治理層級的劃分,多層級的策略掛載框架允許治理策略可以靈活的控制策略生效的影響半徑。
統(tǒng)一HTTP和傳統(tǒng)RPC的治理策略配置層級具體細(xì)節(jié)如下:
.└── 服務(wù)
├── 分組*
│ ├── 路徑*
│ │ ├── 方法*
服務(wù)治理策略放在分組、路徑和方法上,可以逐級設(shè)置,下級默認(rèn)繼承上級的配置。服務(wù)的默認(rèn)策略設(shè)置到默認(rèn)分組default上。
| 類型 | 服務(wù) | 分組 | 路徑 | 方法 |
| HTTP | 域名 | 分組 | URL路徑 | HTTP方法 |
| RPC 應(yīng)用級注冊 | 應(yīng)用名 | 分組 | 接口名 | 方法名 |
| RPC 接口級注冊 | 接口名 | 分組 | / | 方法名 |
模型定義及更多詳情可以訪問:
https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/governance.md
5. 功能實現(xiàn)示例
5.1 服務(wù)注冊
5.1.1 服務(wù)注冊
在應(yīng)用啟動過程中,注冊插件會攔截獲取到消費者和服務(wù)提供者的初始化方法,會修改其元數(shù)據(jù),增加多活和泳道的標(biāo)簽。后續(xù)往注冊中心注冊的時候就會帶有相關(guān)的標(biāo)簽了。這是框架所有治理功能的前提基礎(chǔ)。以下是Dubbo服務(wù)提供者注冊樣例:
@Injectable@Extension(value = "ServiceConfigDefinition_v3", order = PluginDefinition.ORDER_REGISTRY)@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) @ConditionalOnClass(ServiceConfigDefinition.TYPE_CONSUMER_CONTEXT_FILTER)@ConditionalOnClass(ServiceConfigDefinition.TYPE_SERVICE_CONFIG)public class ServiceConfigDefinition extends PluginDefinitionAdapter {
protected static final String TYPE_SERVICE_CONFIG = "org.apache.dubbo.config.ServiceConfig";
private static final String METHOD_BUILD_ATTRIBUTES = "buildAttributes";
private static final String[] ARGUMENT_BUILD_ATTRIBUTES = new String[]{
"org.apache.dubbo.config.ProtocolConfig"
};
// ......
public ServiceConfigDefinition() {
this.matcher = () -> MatcherBuilder.named(TYPE_SERVICE_CONFIG);
this.interceptors = new InterceptorDefinition[]{
new InterceptorDefinitionAdapter(
MatcherBuilder.named(METHOD_BUILD_ATTRIBUTES).
and(MatcherBuilder.arguments(ARGUMENT_BUILD_ATTRIBUTES)),
() -> new ServiceConfigInterceptor(application, policySupplier))
};
}}
public class ServiceConfigInterceptor extends InterceptorAdaptor {
// ......
@Override
public void onSuccess(ExecutableContext ctx) {
MethodContext methodContext = (MethodContext) ctx;
Map map = (Map) methodContext.getResult();
application.label(map::putIfAbsent);
// ......
}}
上面例子所呈現(xiàn)的效果是,當(dāng)dubbo應(yīng)用啟動時,增強(qiáng)插件攔截dubbo框架
org.apache.dubbo.config.ServiceConfig中的buildAttributes方法進(jìn)行增強(qiáng)處理。從ServiceConfigInterceptor的實現(xiàn)中可以看出,當(dāng)buildAttributes方法執(zhí)行成功后,對該方法的返回的Map對象繼續(xù)增加了框架額外的元數(shù)據(jù)標(biāo)簽。
5.1.2 服務(wù)策略訂閱
如果注意ServiceConfigInterceptor的增強(qiáng)會發(fā)現(xiàn),在給注冊示例打標(biāo)之后,還有一部分邏輯,如下:
public class ServiceConfigInterceptor extends InterceptorAdaptor {
@Override
public void onSuccess(ExecutableContext ctx) {
MethodContext methodContext = (MethodContext) ctx;
// ......
AbstractInterfaceConfig config = (AbstractInterfaceConfig) ctx.getTarget();
ApplicationConfig application = config.getApplication();
String registerMode = application.getRegisterMode();
if (DEFAULT_REGISTER_MODE_INSTANCE.equals(registerMode)) {
policySupplier.subscribe(application.getName());
} else if (DEFAULT_REGISTER_MODE_INTERFACE.equals(registerMode)) {
policySupplier.subscribe(config.getInterface());
} else {
policySupplier.subscribe(application.getName());
policySupplier.subscribe(config.getInterface());
}
}}
policySupplier.subscribe所執(zhí)行的是策略訂閱邏輯。因為策略是支持熱更新并實時生效的,策略訂閱邏輯便是開啟了訂閱當(dāng)前服務(wù)在控制臺所配置策略的邏輯。
5.2 流量控制
5.2.1 入流量攔截點
入流量攔截也就是攔截Inbound請求,攔截相關(guān)框架的入流量處理鏈的入口或靠前的處理器的相關(guān)邏輯并予以增強(qiáng)。下面以Dubbo3的攔截點為例。
@Injectable@Extension(value = "ClassLoaderFilterDefinition_v3")@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_ENABLED, matchIfMissing = true)@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_DUBBO_ENABLED, matchIfMissing = true)@ConditionalOnProperty(value = GovernanceConfig.CONFIG_REGISTRY_ENABLED, matchIfMissing = true)@ConditionalOnProperty(value = GovernanceConfig.CONFIG_TRANSMISSION_ENABLED, matchIfMissing = true)@ConditionalOnClass(ClassLoaderFilterDefinition.TYPE_CLASSLOADER_FILTER)public class ClassLoaderFilterDefinition extends PluginDefinitionAdapter {
protected static final String TYPE_CLASSLOADER_FILTER = "org.apache.dubbo.rpc.filter.ClassLoaderFilter";
private static final String METHOD_INVOKE = "invoke";
protected static final String[] ARGUMENT_INVOKE = new String[]{
"org.apache.dubbo.rpc.Invoker",
"org.apache.dubbo.rpc.Invocation"
};
// ......
public ClassLoaderFilterDefinition() {
this.matcher = () -> MatcherBuilder.named(TYPE_CLASSLOADER_FILTER);
this.interceptors = new InterceptorDefinition[]{
new InterceptorDefinitionAdapter(
MatcherBuilder.named(METHOD_INVOKE).
and(MatcherBuilder.arguments(ARGUMENT_INVOKE)),
() -> new ClassLoaderFilterInterceptor(context)
)
};
}}
public class ClassLoaderFilterInterceptor extends InterceptorAdaptor {
private final InvocationContext context;
public ClassLoaderFilterInterceptor(InvocationContext context) {
this.context = context;
}
@Override
public void onEnter(ExecutableContext ctx) {
MethodContext mc = (MethodContext) ctx;
Object[] arguments = mc.getArguments();
Invocation invocation = (Invocation) arguments[1];
try {
context.inbound(new DubboInboundInvocation(new DubboInboundRequest(invocation), context));
} catch (RejectException e) {
Result result = new AppResponse(new RpcException(RpcException.FORBIDDEN_EXCEPTION, e.getMessage()));
mc.setResult(result);
mc.setSkip(true);
}
}}
public interface InvocationContext {
// ......
default void inbound(InboundInvocation invocation) {
InboundFilterChain.Chain chain = new InboundFilterChain.Chain(getInboundFilters());
chain.filter(invocation);
}
}
上面展示了針對Dubbo框架的增強(qiáng)處理是選擇了
org.apache.dubbo.rpc.filter.ClassLoaderFilter的invoke方法作為攔截點,織入我們統(tǒng)一的InboundFilterChain對象作為入流量處理鏈。我們可以根據(jù)需求實現(xiàn)不同的InboundFilter即可,我們內(nèi)置了一部分實現(xiàn)。如下所示:

| 過濾器 | 名稱 | 說明 |
| RateLimitInboundFilter | 限流過濾器 | 根據(jù)當(dāng)前服務(wù)的限流策略來進(jìn)行限流 |
| ConcurrencyLimitInboundFilter | 并發(fā)過濾器 | 根據(jù)當(dāng)前服務(wù)的并發(fā)策略來進(jìn)行限流 |
| ReadyInboundFilter | 治理就緒過濾器 | 判斷治理狀態(tài),只有就緒狀態(tài)才能進(jìn)入流量 |
| UnitInboundFilter | 單元過濾器 | 判斷當(dāng)前請求是否匹配當(dāng)前單元,以及當(dāng)前單元是否可以訪問 |
| CellInboundFilter | 分區(qū)過濾器 | 判斷當(dāng)前分區(qū)是否可以訪問 |
| FailoverInboundFilter | 糾錯過濾器 | 目前對錯誤流量只實現(xiàn)了拒絕 |
5.2.2 出流量攔截點
出流量攔截也就是攔截Outbound請求,攔截相關(guān)框架的出流量處理鏈的入口并予以增強(qiáng)。下面以Dubbo2的攔截點為例。
如果只開啟了多活或泳道治理,則只需對后端實例進(jìn)行過濾,可以攔截負(fù)載均衡或服務(wù)實例提供者相關(guān)方法
@Injectable@Extension(value = "LoadBalanceDefinition_v2.7")@ConditionalOnProperties(value = {
@ConditionalOnProperty(name = {
GovernanceConfig.CONFIG_LIVE_ENABLED,
GovernanceConfig.CONFIG_LANE_ENABLED
}, matchIfMissing = true, relation = ConditionalRelation.OR),
@ConditionalOnProperty(name = GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED, value = "false"),
@ConditionalOnProperty(name = GovernanceConfig.CONFIG_LIVE_DUBBO_ENABLED, matchIfMissing = true)}, relation = ConditionalRelation.AND)@ConditionalOnClass(LoadBalanceDefinition.TYPE_ABSTRACT_CLUSTER)@ConditionalOnClass(ClassLoaderFilterDefinition.TYPE_CONSUMER_CLASSLOADER_FILTER)public class LoadBalanceDefinition extends PluginDefinitionAdapter {
protected static final String TYPE_ABSTRACT_CLUSTER = "com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker";
private static final String METHOD_SELECT = "select";
private static final String[] ARGUMENT_SELECT = new String[]{
"org.apache.dubbo.rpc.cluster.LoadBalance",
"org.apache.dubbo.rpc.Invocation",
"java.util.List",
"java.util.List"
};
// ......
public LoadBalanceDefinition() {
this.matcher = () -> MatcherBuilder.isSubTypeOf(TYPE_ABSTRACT_CLUSTER)
.and(MatcherBuilder.not(MatcherBuilder.isAbstract()));
this.interceptors = new InterceptorDefinition[]{
new InterceptorDefinitionAdapter(
MatcherBuilder.named(METHOD_SELECT)
.and(MatcherBuilder.arguments(ARGUMENT_SELECT)),
() -> new LoadBalanceInterceptor(context)
)
};
}}
攔截器里面調(diào)用上下文的路由方法
public class LoadBalanceInterceptor extends InterceptorAdaptor {
// ......
@Override
public void onEnter(ExecutableContext ctx) {
MethodContext mc = (MethodContext) ctx;
Object[] arguments = ctx.getArguments();
List> invokers = (List>) arguments[2];
List> invoked = (List>) arguments[3];
DubboOutboundRequest request = new DubboOutboundRequest((Invocation) arguments[1]);
DubboOutboundInvocation invocation = new DubboOutboundInvocation(request, context);
DubboCluster3 cluster = clusters.computeIfAbsent((AbstractClusterInvoker??>) ctx.getTarget(), DubboCluster3::new);
try {
List> instances = invokers.stream().map(DubboEndpoint::of).collect(Collectors.toList());
if (invoked != null) {
invoked.forEach(p -> request.addAttempt(new DubboEndpoint?>(p).getId()));
}
List? extends Endpoint?> endpoints = context.route(invocation, instances);
if (endpoints != null && !endpoints.isEmpty()) {
mc.setResult(((DubboEndpoint??>) endpoints.get(0)).getInvoker());
} else {
mc.setThrowable(cluster.createNoProviderException(request));
}
} catch (RejectException e) {
mc.setThrowable(cluster.createRejectException(e, request));
}
mc.setSkip(true);
}}
如果開啟了微服務(wù)治理,則設(shè)計到重試,需要對集群調(diào)用進(jìn)行攔截
@Injectable@Extension(value = "ClusterDefinition_v2.7")@ConditionalOnProperty(name = GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED, matchIfMissing = true)@ConditionalOnProperty(name = GovernanceConfig.CONFIG_LIVE_DUBBO_ENABLED, matchIfMissing = true)@ConditionalOnClass(ClusterDefinition.TYPE_ABSTRACT_CLUSTER)@ConditionalOnClass(ClassLoaderFilterDefinition.TYPE_CONSUMER_CLASSLOADER_FILTER)public class ClusterDefinition extends PluginDefinitionAdapter {
protected static final String TYPE_ABSTRACT_CLUSTER = "org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker";
private static final String METHOD_DO_INVOKE = "doInvoke";
private static final String[] ARGUMENT_DO_INVOKE = new String[]{
"org.apache.dubbo.rpc.Invocation",
"java.util.List",
"org.apache.dubbo.rpc.cluster.LoadBalance"
};
// ......
public ClusterDefinition() {
this.matcher = () -> MatcherBuilder.isSubTypeOf(TYPE_ABSTRACT_CLUSTER)
.and(MatcherBuilder.not(MatcherBuilder.isAbstract()));
this.interceptors = new InterceptorDefinition[]{
new InterceptorDefinitionAdapter(
MatcherBuilder.named(METHOD_DO_INVOKE)
.and(MatcherBuilder.arguments(ARGUMENT_DO_INVOKE)),
() -> new ClusterInterceptor(context)
)
};
}}
攔截器里面構(gòu)造集群對象進(jìn)行同步或異步調(diào)用
public class ClusterInterceptor extends InterceptorAdaptor {
// ......
@Override
public void onEnter(ExecutableContext ctx) {
MethodContext mc = (MethodContext) ctx;
Object[] arguments = ctx.getArguments();
DubboCluster3 cluster = clusters.computeIfAbsent((AbstractClusterInvoker??>) ctx.getTarget(), DubboCluster3::new);
List> invokers = (List>) arguments[1];
List> instances = invokers.stream().map(DubboEndpoint::of).collect(Collectors.toList());
DubboOutboundRequest request = new DubboOutboundRequest((Invocation) arguments[0]);
DubboOutboundInvocation invocation = new DubboOutboundInvocation(request, context);
DubboOutboundResponse response = cluster.request(context, invocation, instances);
if (response.getThrowable() != null) {
mc.setThrowable(response.getThrowable());
} else {
mc.setResult(response.getResponse());
}
mc.setSkip(true);
}}
同樣,出流量攔截也是采用了責(zé)任鏈模式設(shè)計了OutboundFilterChain,用戶可以根據(jù)自己的需求擴(kuò)展實現(xiàn)OutboundFilter,目前針對已支持功能內(nèi)置了部分實現(xiàn),如下所示:

| 過濾器 | 名稱 | 說明 |
| StickyFilter | 粘連過濾器 | 根據(jù)服務(wù)的粘連策略進(jìn)行過濾 |
| LocalhostFilter | 本機(jī)過濾器 | 本地開發(fā)調(diào)試插件 |
| HealthyFilter | 健康過濾器 | 根據(jù)后端實例的健康狀態(tài)進(jìn)行過濾 |
| VirtualFilter | 虛擬節(jié)點過濾器 | 復(fù)制出指定數(shù)量的節(jié)點,用于開發(fā)測試 |
| UnitRouteFilter | 單元路由過濾器 | 根據(jù)多活路由規(guī)則及微服務(wù)的多活策略,根據(jù)請求的目標(biāo)單元進(jìn)行過濾 |
| TagRouteFilter | 標(biāo)簽路由過濾器 | 根據(jù)服務(wù)配置的標(biāo)簽路由策略進(jìn)行過濾 |
| LaneFilter | 泳道過濾器 | 根據(jù)泳道策略進(jìn)行過濾 |
| CellRouteFilter | 分區(qū)路由過濾器 | 根據(jù)多活路由規(guī)則及微服務(wù)的多活策略,根據(jù)請求的目標(biāo)分區(qū)進(jìn)行過濾 |
| RetryFilter | 重試過濾器 | 嘗試過濾掉已經(jīng)重試過的節(jié)點 |
| LoadBalanceFilter | 負(fù)載均衡過濾器 | 根據(jù)服務(wù)配置的負(fù)載均衡策略進(jìn)行路由 |
6. 部署實踐
6.1 基于Kubernates實踐場景
我們開源的另一個項目joylive-injector是針對K8s場景打造的自動注入組件。joylive-injector是基于kubernetes的動態(tài)準(zhǔn)入控制webhook,它可以用于修改kubernete資源。它會監(jiān)視工作負(fù)載(如deployments)的CREATE、UPDATE、DELETE事件和pods的CREATE事件,并為POD添加initContainer、默認(rèn)增加環(huán)境變量JAVA_TOOL_OPTIONS、掛載configmap、修改主容器的卷裝載等操作。目前已支持的特性如下:
支持自動將joylive-agent注入應(yīng)用的Pod。
支持多版本joylive-agent與對應(yīng)配置管理。
支持注入指定版本joylive-agent及對應(yīng)配置。
所以,針對采用K8s進(jìn)行應(yīng)用發(fā)布管理的場景中,集成joylive-agent變得非常簡單,在安裝joylive-injector組件后,只需要在對應(yīng)的deployment文件中加入標(biāo)簽x-live-enabled: "true"即可,如下所示:
apiVersion: apps/v1kind: Deploymentmetadata:
labels:
app: joylive-demo-springcloud2021-provider x-live-enabled: "true"
name: joylive-demo-springcloud2021-providerspec:
replicas: 1
selector:
matchLabels:
app: joylive-demo-springcloud2021-provider template:
metadata:
labels:
app: joylive-demo-springcloud2021-provider x-live-enabled: "true"
spec:
containers:
- env:
- name: CONFIG_LIVE_SPACE_API_TYPE value: multilive - name: CONFIG_LIVE_SPACE_API_URL value: http://api.live.local/v1 - name: CONFIG_LIVE_SPACE_API_HEADERS value: pin=demo - name: CONFIG_SERVICE_API_TYPE value: jmsf - name: CONFIG_SERVICE_API_URL value: http://api.jmsf.local/v1 - name: LIVE_LOG_LEVEL value: info - name: CONFIG_LANE_ENABLED value: "false"
- name: NACOS_ADDR value: nacos-server.nacos.svc:8848
- name: NACOS_USERNAME value: nacos - name: NACOS_PASSWORD value: nacos - name: APPLICATION_NAME value: springcloud2021-provider - name: APPLICATION_SERVICE_NAME value: service-provider - name: APPLICATION_SERVICE_NAMESPACE value: default - name: SERVER_PORT value: "18081"
- name: APPLICATION_LOCATION_REGION value: region1 - name: APPLICATION_LOCATION_ZONE value: zone1 - name: APPLICATION_LOCATION_LIVESPACE_ID value: v4bEh4kd6Jvu5QBX09qYq-qlbcs - name: APPLICATION_LOCATION_UNIT value: unit1 - name: APPLICATION_LOCATION_CELL value: cell1 - name: APPLICATION_LOCATION_LANESPACE_ID value: "1"
- name: APPLICATION_LOCATION_LANE value: production image: hub-vpc.jdcloud.com/jmsf/joylive-demo-springcloud2021-provider:1.1.0-5aab82b3-AMD64 imagePullPolicy: Always name: joylive-demo-springcloud2021-provider ports:
- containerPort: 18081
name: http protocol: TCP resources:
requests:
cpu: "4"
memory: "8Gi"
limits:
cpu: "4"
memory: "8Gi"
terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: { }
terminationGracePeriodSeconds: 30
啟動后Pod如下圖所示即代表注入成功,隨后觀察應(yīng)用日志及功能測試即可。

我們有更高的目標(biāo)和方向,希望更多有志于打造開源優(yōu)品的朋友加入進(jìn)來,一起為開源事業(yè)貢獻(xiàn)自己的光與熱。
下一篇:KubeCon China 2024全球大會在香港舉行,京東云受邀參加探討云原生、開源及 AI
審核編輯 黃宇
-
SDK
+關(guān)注
關(guān)注
3文章
1091瀏覽量
50928 -
微服務(wù)
+關(guān)注
關(guān)注
0文章
147瀏覽量
8015 -
微服務(wù)架構(gòu)
+關(guān)注
關(guān)注
0文章
26瀏覽量
3143
發(fā)布評論請先 登錄
微服務(wù)架構(gòu)和CQRS架構(gòu)基本概念介紹
微服務(wù)網(wǎng)關(guān)gateway的相關(guān)資料推薦
什么是微服務(wù)_微服務(wù)知識點全面總結(jié)
微服務(wù)架構(gòu)多微才合適
java微服務(wù)架構(gòu)有哪些
什么是微服務(wù)和容器?微服務(wù)和容器的作用是什么
Dubbo 如何成為連接異構(gòu)微服務(wù)體系的最佳服務(wù)開發(fā)框架
微服務(wù)架構(gòu)技術(shù)棧選型解讀
華為云服務(wù)治理?| 微服務(wù)常見故障模式
華為云服務(wù)治理 | 服務(wù)治理的一般性原則
華為云服務(wù)治理—隔離倉的作用
分布式政企應(yīng)用如何快速實現(xiàn)云原生的微服務(wù)架構(gòu)改造
基于Traefik自研的微服務(wù)網(wǎng)關(guān)
設(shè)計微服務(wù)架構(gòu)的原則

Proxyless的多活流量和微服務(wù)治理
評論