Proxyless的多活流量和微服務治理

京东云开发者發表於2024-09-02

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 單體架構階段

最初,大多數應用都是作為單體應用開發的。所有功能都集中在一個程式碼庫中,部署也是作為一個整體。這種形式也是我們學習程式設計之初,最原始的模樣。確切的說,這種形態並不屬於微服務。如下圖所示:

BJ3FFZ-1724662920364

優點: 簡單、易於開發和測試,適合小團隊和小規模應用。

缺點: 這種架構隨著應用規模增大,可能會面臨維護困難、擴充套件性差等問題。

2.2 垂直拆分階段

隨著應用規模的成長,此時會考慮將每個功能模組(服務)拆分為獨立的應用,也就是垂直拆分,擁有自己的程式碼庫、資料庫和部署生命週期。服務之間透過輕量級協議(如HTTP、gRPC)通訊。也就是正式開啟了面向服務的架構(SOA)。這種形態體現為:服務發現透過DNS解析,Consumer與Provider之間會有LB進行流量治理。服務間透過API進行通訊。如下圖所示:

q6L414-1724662970007

優點: 獨立部署和擴充套件,每個服務可以由獨立的團隊開發和維護,提高了敏捷性。

缺點: 增加了分散式系統的複雜性,需要處理服務間通訊、資料一致性、服務發現、負載均衡等問題,也因為中間引入LB而降低了效能。

2.3 微服務成熟階段

這個階段引入更多的微服務治理和管理工具,使用專業的微服務框架或中介軟體,透過專門定製的微服務通訊協議,讓應用取得更高的吞吐效能。如API閘道器、註冊中心、分散式追蹤等。DevOps和持續整合/持續部署(CI/CD)流程成熟。代表產物如Spring Cloud,Dubbo等。此時典型的微服務場景還都是具體的微服務SDK提供的治理能力。通訊流程為:SDK負責向註冊中心註冊當前服務資訊,當需要進行服務消費時,同樣向註冊中心請求服務提供者資訊,然後直連服務提供者IP及埠併傳送請求。如下圖所示:

q6L414-1724662970007

優點: 高度可擴充套件、彈性和靈活性,支援高頻率的釋出和更新。

缺點: 系統複雜性和運維成本較高,需要成熟的技術棧和團隊能力。微服務治理能力依賴SDK,升級更新成本高,需要繫結業務應用更新。

2.4 服務網格架構

隨著雲原生容器化時代的到來,服務網格是一種專門用於管理微服務之間通訊的基礎設施層。它通常包含一組輕量級的網路代理(通常稱為 sidecar),這些代理與每個服務例項一起部署,這利用了K8s中Pod的基礎能力。服務網格負責處理服務間的通訊、流量管理、安全性、監控和彈性等功能。這種微服務治理方式也可以稱之為Proxy模式,其中SideCar即作為服務之間的Proxy。如下圖所示:

rxadFQ-1724663052165

優點: 主要優點是解耦業務邏輯與服務治理的能力,透過集中控制平面(control plane)簡化了運維管理。

缺點: 增加了資源消耗,更高的運維挑戰。

3. 專案架構設計

有沒有一種微服務治理方案,既要有SDK架構的高效能、多功能的好處,又要有邊車架構的零侵入優勢, 還要方便好用?這就是專案設計的初衷。專案的設計充分考慮了上面微服務的架構歷史,結合多活流量治理模型,進行了重新設計。其中專案設計到的主要技術點如下,並進行詳細解析。如下圖所示:

q6L414-1724662970007

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);
    }
}

該類上的註解如下:

  1. @Extension註解宣告擴充套件實現,並提供了名稱

  2. @ConditionalOnProperty註解宣告啟用的條件,可以組合多個條件

3.3.3 啟用擴充套件

在SPI檔案META-INF/services/com.jd.live.agent.governance.invoke.loadbalance.LoadBalancer中配置擴充套件全路徑名
com.jd.live.agent.governance.invoke.loadbalance.roundrobin.RoundRobinLoadBalancer即達到啟用效果。

更多詳情可查閱:https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/extension.md

3.4 依賴注入設計

說到依賴注入估計大家會立馬想到Spring,的確這是Spring的看家本領。在複雜的工程中,自動化的依賴注入確實會簡化工程的實現複雜度。讓開發人員從複雜的依賴構建中脫離出來,專注於功能點設計開發。依賴注入的實現是基於上面外掛擴充套件體系,是外掛擴充套件的功能增強。並且依賴注入支援了兩類場景:注入物件與注入配置。該功能主要有4個註解類與3個介面構成。

3.4.1 @Injectable

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Injectable {
    boolean enable() default true;
}

這是一個非常簡潔的可以應用於類、介面(包括註解型別)或列舉註解。其目的為了標識哪些類開啟了自動注入物件的要求。這點不同於Spring的控制範圍,而是按需注入。例項構建完成後,在自動注入的邏輯過程中會針對新增@Injectable註解的例項進行依賴物件注入。

3.4.2 @Inject

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inject {
    String value() default "";
    boolean nullable() default false;
    ResourcerType loader() default ResourcerType.CORE_IMPL;
}

該註解用於自動注入值到欄位。它用於指定一個欄位在執行時應該注入一個值,支援基於配置的注入。還可以指示被注入的值是否可以為 null,如果注入過程中無注入例項或注入例項為null,而nullable配置為false,則會丟擲異常。loader定義了指定要為註釋欄位載入的資源或類實現的型別。ResourcerType為列舉型別,分別是:CORE,CORE_IMPL,PLUGIN。劃分依據是工程打包後的jar檔案分佈目錄。因為不同目錄的類載入器是不同的(類隔離的原因,後面會講到)。所以可以簡單理解,這個配置是用於指定載入該物件所對應的類載入器。

3.4.3 @Configurable

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Configurable {
    String prefix() default "";
    boolean auto() default false;
}

這個註解類似於@Injectable,用於指定哪些類啟用自動注入配置檔案的支援。prefix指定用於配置鍵的字首。這通常意味著字首將來自類名或基於某種約定。auto指示配置值是否應自動注入到註解類的所有合規欄位中。預設值為 false,這意味著預設情況下未啟用自動注入。

3.4.4 @Config

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Config {
    String value() default "";
    boolean nullable() default true;
}

該註解用於指定欄位配置詳細資訊。它定義了欄位的配置鍵以及配置是否為可選。此註釋可用於在執行時自動將配置值載入到欄位中,並支援指定缺少配置(無配置)是否被允許。nullable指示欄位的配置是否是可選的。如果為真,則系統將允許配置缺失而不會導致錯誤。

下面是具體的使用示例:

@Injectable
@Extension(value = "CircuitBreakerFilter", order = OutboundFilter.ORDER_CIRCUIT_BREAKER)
public class CircuitBreakerFilter implements OutboundFilter, ExtensionInitializer {
    @Inject
    private Map<String, CircuitBreakerFactory> 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;
  
    ...
}

更多細節,因為篇幅原因不再展開。詳情可以瞭解:https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/extension.md

3.5 位元組碼增強機制

Java 的位元組碼增強(Bytecode Enhancement)是一種動態改變 Java 位元組碼的技術,允許開發者在類載入之前或載入過程中修改類的位元組碼。這種機制在 AOP(面向切面程式設計)、框架增強、效能監控、日誌記錄等領域廣泛應用。目前看此技術在APM產品使用較多,而針對流量治理方向的開源實現還是比較少的。

Java位元組碼增強的主要方法有:

  1. 執行時增強:使用Java Agent在類載入時修改位元組碼。

  2. 載入時增強:在類載入到JVM之前修改位元組碼。

  3. 編譯時增強:在編譯階段修改或生成位元組碼。

進行位元組碼增強的框架有很多,例如:JavaAssist、ASM、ByteBuddy、ByteKit等。我們針對位元組碼增強的過程及重要物件進行了介面抽象,並以外掛化方式適配了ByteBuddy,開發了一種預設實現。當然你也可以使用其他的框架實現相應的介面,作為擴充套件的其他實現方式。下面以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建立增強類
        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();

        // 建立增強類的例項
        Object enhancedInstance = dynamicType.getDeclaredConstructor().newInstance();

        // 呼叫增強後的方法
        Method sayHelloMethod = enhancedInstance.getClass().getMethod("sayHello");
        sayHelloMethod.invoke(enhancedInstance);
    }
}

這個例子展示瞭如何使用ByteBuddy來增強SimpleClasssayHello方法。讓我解釋一下這個過程:

  1. 我們定義了一個簡單的SimpleClass,它有一個sayHello方法。

  2. 我們建立了一個SimpleInterceptor類,包含了我們想要在原方法執行前後新增的邏輯。

  3. ByteBuddyExample類的main方法中,我們使用ByteBuddy來建立一個增強的類:

    • 我們建立了SimpleClass的一個子類。

    • 我們攔截了名為"sayHello"的方法。

    • 我們使用MethodDelegation.to(SimpleInterceptor.class)來新增前置和後置邏輯。

    • 我們使用SuperMethodCall.INSTANCE來確保原始方法被呼叫。

  4. 我們建立了增強類的例項,並透過反射呼叫了sayHello方法。

當你執行這個程式時,輸出將會是:

Before saying hello
Hello, World!
After saying hello

當然,工程級別的實現是遠比上面Demo的組織形式複雜的。外掛是基於擴充套件實現的,有多個擴充套件組成,對某個框架進行特定增強,實現了多活流量治理等等業務邏輯。一個外掛打包成一個目錄,如下圖所示:

.
└── 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個版本,增強了註冊中心,路由和鏈路透傳的能力。下面介紹一下在joylive-agent中如何實現一個位元組碼增強外掛。

3.5.1 增強外掛定義介面

增強外掛(功能實現層面的外掛)介面的定義採用了外掛(系統架構層面的外掛)擴充套件機制。如下程式碼所示,定義的增強外掛名稱為PluginDefinition

@Extensible("PluginDefinition")
public interface PluginDefinition {

    ElementMatcher<TypeDesc> getMatcher();
    
    InterceptorDefinition[] getInterceptors();
}

介面共定義了兩個方法:getMatcher用於獲取匹配要增強類的匹配器,getInterceptors是返回要增強目標類的攔截器定義物件。

3.5.2 攔截器定義介面

攔截器定義介面主要是用來確定攔截增強位置(也就是方法),定位到具體方法也就找到了具體增強邏輯執行的位置。攔截器定義介面並沒有採用擴充套件機制,這是因為具體到某個增強目標類後,要增強的方法與增強邏輯已經是確定行為,不再需要透過擴充套件機制例項化物件,在具體的增強外掛定義介面實現裡會直接透過new的方式構造蘭機器定義實現。攔截器定義介面如下:

public interface InterceptorDefinition {
    
    ElementMatcher<MethodDesc> getMatcher();
    
    Interceptor getInterceptor();
}

該介面同樣抽象了兩個方法:getMatcher用於獲取匹配要增強方法的匹配器,getInterceptor是用於返回具體的增強邏輯實現,我們稱之為攔截器(Interceptor)。

3.5.3 攔截器介面

攔截器的實現類是具體增強邏輯的載體,當我們要增強某個類的某個方法時,與AOP機制同理,我們抽象了幾處攔截位置。分別是:方法執行前(剛進入方法執行邏輯);方法執行結束時;方法執行成功時(無異常);方法執行出錯時(有異常)。介面定義如下:

public interface Interceptor {
    
    void onEnter(ExecutableContext ctx);

    void onSuccess(ExecutableContext ctx);

    void onError(ExecutableContext ctx);

    void onExit(ExecutableContext ctx);
}

增強邏輯的實現可以針對不同的功能目標,選擇合適的增強點。同樣,攔截點的入參ExecutableContext也是非常重要的組成部分,它承載了執行時的上下文資訊,並且針對不同的增強目標我們做了不同的實現。如下圖所示:

dCbCFi-1724663099625

更多詳情可檢視:https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/plugin.md

3.6 類載入與類隔離

類載入的原理比較容易理解,因為在Java Agent模式下,應用啟動時agent需要載入它所依賴的jar包。然而,如果這些類在載入後被使用者應用的類載入器感知到,就可能導致類衝突甚至不相容的風險。因此,引入類隔離機制是為了解決這個問題。類隔離的實現原理並不複雜。首先,需要實現自定義的類載入器;其次,需要打破預設的雙親委派機制。透過這兩步,類隔離可以實現多層次的隔離,從而避免類衝突和不相容問題。如下圖所示:

q6L414-1724662970007

3.7 面向請求的抽象

整個框架的核心行為就是治理請求,而站在一個應用的視角我們可以大體把請求抽象為兩類:InboundRequest與OutboundRequest。InboundRequest是外部進入當前應用的請求,OutboundRequest是當前應用發往外部資源的請求。同樣的處理這些請求的過濾器也同樣分為InboundFilter與OutboundFilter。

  • 請求介面

7IL6Is-1724663140562

  • 請求抽象實現

q6L414-1724662970007

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

q6L414-1724662970007

如上圖所示,展現了適配Dubbo的請求物件的DubboOutboundRequestDubboInboundRequest,體現了一個OutboundRequest與InboundRequest的實現與繼承關係。整體看起來確實比較複雜。這是因為在請求抽象時,不僅要考慮請求是Inbound還是Outbound,還要適配不同的協議框架。例如,像Dubbo和JSF這樣的私有通訊協議框架需要統一為RpcRequest介面的實現,而SpringCloud這樣的HTTP通訊協議則統一為HttpRequest。再加上Inbound和Outbound的分類維度,整體的抽象在追求高擴充套件性的同時也增加了複雜性。

4. 核心功能

下面提供的流量治理功能,以API閘道器作為東西向流量第一入口進行流量識別染色。在很大程度上,API閘道器作為東西向流量識別的第一入口發揮了重要作用。API閘道器在接收到南北向流量後,後續將全部基於東西向流量治理。

4.1 多活模型及流量排程

應用多活通常包括同城多活和異地多活,異地多活可採用單元化技術來實現。下面描述整個多活模型涉及到的概念及巢狀關係。具體實現原理如下圖所示:

q6L414-1724662970007

4.1.1 多活空間

在模型和許可權設計方面,我們支援多租戶模式。一個租戶可以有多個多活空間,多活空間構成如下所示:

.
└── 多活空間
    ├── 單元路由變數(*)
    ├── 單元(*)
    │   ├── 分割槽(*)
    ├── 單元規則(*)
    ├── 多活域名(*)
    │   ├── 單元子域名(*)
    │   ├── 路徑(*)
    │   │   ├── 業務引數(*)

4.1.2 單元

單元是邏輯上的概念,一般對應一個地域。常用於異地多活場景,透過使用者維度拆分業務和資料,每個單元獨立運作,降低單元故障對整體業務的影響。

單元的屬性包括單元程式碼、名稱、型別(中心單元或普通單元)、讀寫許可權、標籤(如地域和可用區)、以及單元下的分割槽。

單元分割槽是單元的組成部分,單元內的邏輯分割槽,對應雲上的可用區或物理資料中心,屬性類似單元。

4.1.3 路由變數

路由變數是決定流量路由到哪個單元的依據,通常是使用者賬號。每個變數可以透過不同的取值方式(如Cookie、請求頭等)獲取,且可以定義轉換函式來獲取實際使用者標識。

變數取值方式則描述如何從請求引數、請求頭或Cookie中獲取路由變數。

4.1.4 單元規則

單元規則定義了單元和分割槽之間的流量排程規則。根據路由變數計算出的值,透過取模判斷流量應路由到哪個單元。它的屬性包括多活型別、變數、變數取值方式、計算函式、變數缺失時的操作、以及具體的單元路由規則。

單元路由規則定義了單元內的流量路由規則,包括允許的路由變數白名單、字首、值區間等。

分割槽路由規則定義了單元內各個分割槽的流量路由規則,包括允許的變數白名單、字首及權重。

4.1.5 多活域名

多活域名描述啟用多活的域名,用於在閘道器層進行流量攔截和路由。支援跨地域和同城多活的流量管理,配置路徑規則以匹配請求路徑並執行相應的路由規則。

單元子域名描述各個單元的子域名,通常用於在HTTP請求或回撥時閉環在單元內進行路由。

路徑規則定義了根據請求路徑匹配的路由規則,取最長匹配路徑來選擇適用的路由規則。

業務引數規則基於請求引數值進一步精細化路由,選擇特定的單元規則。

4.1.6 模型骨架

以下是多活治理模型的基本配置樣例,包括API版本、空間名稱、單元、域名、規則、變數等。

[
  {
    "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 全鏈路灰度(泳道)

泳道是一種隔離和劃分系統中不同服務或元件的方式,類似於游泳池中的泳道劃分,確保每個服務或元件在自己的“泳道”中獨立運作。泳道概念主要用於以下幾種場景:

  • 多租戶架構中的隔離

在多租戶系統中,泳道通常用於隔離不同租戶的資源和服務。每個租戶有自己的獨立“泳道”,以確保資料和流量的隔離,防止不同租戶之間的相互影響。

  • 流量隔離與管理

泳道可以用於根據特定規則(例如使用者屬性、地理位置、業務特性等)將流量分配到不同的微服務例項或叢集中。這種方式允許團隊在某些條件下測試新版本、進行藍綠部署、金絲雀釋出等,而不會影響到其他泳道中的流量。

  • 業務邏輯劃分

在某些場景下,泳道也可以代表業務邏輯的劃分。例如,一個電商平臺可能會針對不同的使用者群體(如普通使用者、VIP使用者)提供不同的服務路徑和處理邏輯,形成不同的泳道。

  • 版本管理

泳道可以用來管理微服務的不同版本,使得新版本和舊版本可以在不同的泳道中並行執行,從而降低升級時的風險。

  • 開發和測試

在開發和測試過程中,不同的泳道可以用於隔離開發中的新功能、測試環境、甚至是不同開發團隊的工作,從而減少互相干擾。

泳道的核心目的是透過隔離服務或資源,提供獨立性和靈活性,確保系統的穩定性和可擴充套件性。這種隔離機制幫助組織更好地管理複雜系統中的多樣性,尤其是在處理高併發、多租戶、或者需要快速迭代的場景下。功能流程如下圖所示:

q6L414-1724662970007

模型定義及更多詳情可以訪問:https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/lane.md

4.3 微服務治理策略

微服務治理策略是指在微服務架構中,為確保服務的穩定性、可靠性、安全性以及高效運作而制定的一系列管理和控制措施。這些策略幫助企業有效地管理、監控、協調和最佳化成百上千個微服務的執行,以應對分散式系統的複雜性。目前我們實現了部分主流的微服務策略例如:負載均衡,重試,限流,熔斷降級,標籤路由,訪問鑑權等,更多的實用策略也在陸續補充中。微服務框架方面已經支援主流框架,如:Spring Cloud,Dubbo2/3,JSF,SofaRpc等。

由於篇幅原因,具體的治理策略與模型就不詳盡展開介紹了,下圖概括了服務治理的全貌。

q6L414-1724662970007

值得一提有兩點:

  • 策略的實現遮蔽了底層框架的差異性,這得益於上面提到的面向請求的抽象。

  • 統一治理層級的劃分,多層級的策略掛載框架允許治理策略可以靈活的控制策略生效的影響半徑。

統一HTTP和傳統RPC的治理策略配置層級具體細節如下:

.
└── 服務
    ├── 分組*
    │   ├── 路徑*
    │   │   ├── 方法*

服務治理策略放在分組、路徑和方法上,可以逐級設定,下級預設繼承上級的配置。服務的預設策略設定到預設分組default上。

型別服務分組路徑方法
HTTP 域名 分組 URL路徑 HTTP方法
RPC 應用級註冊 應用名 分組 介面名 方法名
RPC 介面級註冊 介面名 分組 / 方法名

模型定義及更多詳情可以訪問:https://github.com/jd-opensource/joylive-agent/blob/main/docs/cn/governance.md

5. 功能實現示例

5.1 服務註冊

5.1.1 服務註冊

在應用啟動過程中,註冊外掛會攔截獲取到消費者和服務提供者的初始化方法,會修改其後設資料,增加多活和泳道的標籤。後續往註冊中心註冊的時候就會帶有相關的標籤了。這是框架所有治理功能的前提基礎。以下是Dubbo服務提供者註冊樣例:

@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<String, String> map = (Map<String, String>) methodContext.getResult();
        application.label(map::putIfAbsent);

        // ......
    }
}

上面例子所呈現的效果是,當dubbo應用啟動時,增強外掛攔截dubbo框架org.apache.dubbo.config.ServiceConfig中的buildAttributes方法進行增強處理。從ServiceConfigInterceptor的實現中可以看出,當buildAttributes方法執行成功後,對該方法的返回的Map物件繼續增加了框架額外的後設資料標籤。

5.1.2 服務策略訂閱

如果注意ServiceConfigInterceptor的增強會發現,在給註冊示例打標之後,還有一部分邏輯,如下:

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所執行的是策略訂閱邏輯。因為策略是支援熱更新並實時生效的,策略訂閱邏輯便是開啟了訂閱當前服務在控制檯所配置策略的邏輯。

5.2 流量控制

5.2.1 入流量攔截點

入流量攔截也就是攔截Inbound請求,攔截相關框架的入流量處理鏈的入口或靠前的處理器的相關邏輯並予以增強。下面以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 <R extends InboundRequest> void inbound(InboundInvocation<R> invocation) {
        InboundFilterChain.Chain chain = new InboundFilterChain.Chain(getInboundFilters());
        chain.filter(invocation);
    }
    
}

上面展示了針對Dubbo框架的增強處理是選擇了org.apache.dubbo.rpc.filter.ClassLoaderFilterinvoke方法作為攔截點,織入我們統一的InboundFilterChain物件作為入流量處理鏈。我們可以根據需求實現不同的InboundFilter即可,我們內建了一部分實現。如下所示:

sMBolv-1724663304346

過濾器名稱說明
RateLimitInboundFilter 限流過濾器 根據當前服務的限流策略來進行限流
ConcurrencyLimitInboundFilter 併發過濾器 根據當前服務的併發策略來進行限流
ReadyInboundFilter 治理就緒過濾器 判斷治理狀態,只有就緒狀態才能進入流量
UnitInboundFilter 單元過濾器 判斷當前請求是否匹配當前單元,以及當前單元是否可以訪問
CellInboundFilter 分割槽過濾器 判斷當前分割槽是否可以訪問
FailoverInboundFilter 糾錯過濾器 目前對錯誤流量只實現了拒絕

5.2.2 出流量攔截點

出流量攔截也就是攔截Outbound請求,攔截相關框架的出流量處理鏈的入口並予以增強。下面以Dubbo2的攔截點為例。

  1. 如果只開啟了多活或泳道治理,則只需對後端例項進行過濾,可以攔截負載均衡或服務例項提供者相關方法

@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)
                )
        };
    }
}

攔截器裡面呼叫上下文的路由方法

public class LoadBalanceInterceptor extends InterceptorAdaptor {

    // ......

    @Override
    public void onEnter(ExecutableContext ctx) {
        MethodContext mc = (MethodContext) ctx;
        Object[] arguments = ctx.getArguments();
        List<Invoker<?>> invokers = (List<Invoker<?>>) arguments[2];
        List<Invoker<?>> invoked = (List<Invoker<?>>) 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<DubboEndpoint<?>> 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);
    }

}
  1. 如果開啟了微服務治理,則設計到重試,需要對叢集呼叫進行攔截

@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)
                )
        };
    }
}

攔截器裡面構造叢集物件進行同步或非同步呼叫

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<Invoker<?>> invokers = (List<Invoker<?>>) arguments[1];
        List<DubboEndpoint<?>> 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);
    }

}

同樣,出流量攔截也是採用了責任鏈模式設計了OutboundFilterChain,使用者可以根據自己的需求擴充套件實現OutboundFilter,目前針對已支援功能內建了部分實現,如下所示:

L6xBqR-1724663331997

過濾器名稱說明
StickyFilter 粘連過濾器 根據服務的粘連策略進行過濾
LocalhostFilter 本機過濾器 本地開發除錯外掛
HealthyFilter 健康過濾器 根據後端例項的健康狀態進行過濾
VirtualFilter 虛擬節點過濾器 複製出指定數量的節點,用於開發測試
UnitRouteFilter 單元路由過濾器 根據多活路由規則及微服務的多活策略,根據請求的目標單元進行過濾
TagRouteFilter 標籤路由過濾器 根據服務配置的標籤路由策略進行過濾
LaneFilter 泳道過濾器 根據泳道策略進行過濾
CellRouteFilter 分割槽路由過濾器 根據多活路由規則及微服務的多活策略,根據請求的目標分割槽進行過濾
RetryFilter 重試過濾器 嘗試過濾掉已經重試過的節點
LoadBalanceFilter 負載均衡過濾器 根據服務配置的負載均衡策略進行路由

6. 部署實踐

6.1 基於Kubernates實踐場景

我們開源的另一個專案joylive-injector是針對K8s場景打造的自動注入元件。joylive-injector是基於kubernetes的動態准入控制webhook,它可以用於修改kubernete資源。它會監視工作負載(如deployments)的CREATE、UPDATE、DELETE事件和pods的CREATE事件,併為POD新增initContainer、預設增加環境變數JAVA_TOOL_OPTIONS、掛載configmap、修改主容器的卷裝載等操作。目前已支援的特性如下:

  • 支援自動將joylive-agent注入應用的Pod。

  • 支援多版本joylive-agent與對應配置管理。

  • 支援注入指定版本joylive-agent及對應配置。

所以,針對採用K8s進行應用釋出管理的場景中,整合joylive-agent變得非常簡單,在安裝joylive-injector元件後,只需要在對應的deployment檔案中加入標籤x-live-enabled: "true"即可,如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: joylive-demo-springcloud2021-provider
    x-live-enabled: "true"
  name: joylive-demo-springcloud2021-provider
spec:
  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如下圖所示即代表注入成功,隨後觀察應用日誌及功能測試即可。

q6L414-1724662970007

我們有更高的目標和方向,但是目前缺少戰友,希望更多有志於打造開源優品的同事加入進來,一起為JD開源事業貢獻自己的光與熱。

相關文章