天天看點

「開源」Spring Boot 增強架構,提供健康檢查、日志空間、類隔離能力

作者:一飛開源
一飛開源,介紹創意、新奇、有趣、實用的免費開源應用、系統、軟體、硬體及技術,一個探索、發現、分享、使用與互動交流的開源技術社群平台。緻力于打造活力開源社群,共建開源新生态!

一、開源項目簡介

SOFABoot 是螞蟻集團開源的基于 Spring Boot 的研發架構,它在 Spring Boot 的基礎上,提供了諸如 Readiness Check,上下文隔離,類隔離,日志空間隔離等等能力。在增強了 Spring Boot 的同時,SOFABoot 提供了讓使用者可以在 Spring Boot 中非常友善地使用 SOFA 中間件的能力。

二、開源協定

使用Apache-2.0開源協定

三、界面展示

「開源」Spring Boot 增強架構,提供健康檢查、日志空間、類隔離能力

四、功能概述

1、背景

Spring Boot 是一個非常優秀的開源架構,可以非常友善地就建構出一個基于 Spring 的應用程式,但是在使用過程中,還是會遇到一些問題:

  • Spring Boot 提供了一個基礎的健康檢查的能力,中間件和應用都可以擴充來實作自己的健康檢查邏輯。但是 Spring Boot 的健康檢查隻有 Liveness Check 的能力,缺少 Readiness Check 的能力,這樣會有比較緻命的問題。當一個微服務應用啟動的時候,必須要先保證啟動後應用是健康的,才可以将上遊的流量放進來(來自于 RPC,網關,定時任務等等流量),否則就可能會導緻一定時間内大量的錯誤發生。
  • Spring Boot 雖然通過依賴管理(Dependency Management)的方式最大程度的保證了 Spring Boot 管理的 JAR 包之間的相容性,但是不可避免的,當引入一些其他的 JAR 包的時候,還是可能會遇到沖突,而且很多時候這種沖突解決起來并不是這麼容易,一個例子是當沖突的包是序列化相關的類庫時,比如說 Hessian,如果應用中的一個元件需要使用 Hessian 3,而另一個則必須要使用 Hessian 4,由于 Hessian 3 和 Hessian 4 之間的不相容性,并且序列化還涉及到微服務中的上下遊服務,要把 Hessian 統一到一個版本絕非易事。
  • 在超大規模微服務運維的場景下,運維能力的平台化是一定要解決的問題,而監控又是其中非常主要的一個點,針對于日志監控這種情況,Spring Boot 并沒有提供任何解決方案。大部分的開源元件,具體要列印哪些日志,列印到什麼路徑,什麼檔案下面,都是由應用的使用者來決定,這樣會導緻每一個應用的日志配置都各式各樣,每一個應用都需要去監控系統中配置自己應用的日志監控,導緻關鍵的監控的實施成本特别高。
  • 在企業級應用場景,子產品化開發是解決多團隊溝通成本的有效解決方案,每個業務團隊專注于開發自己的應用子產品,每個子產品自包含,便于開發及自測,減少團隊間的溝通成本。但是 Spring Boot 預設不支援子產品化開發,所有 Bean 共用一個 Spring 上下文,在多團隊開發時,如果不同團隊定義了相同 BeanId,運作時将出現 BeanId 沖突錯誤。

為了解決以上的問題,又因為 SOFA 中間件中的各個元件本身就需要內建 Spring Boot,是以螞蟻集團基于 Spring Boot 開發并開源了 SOFABoot,來解決以上的問題,也友善使用者在 Spring Boot 中友善地去使用 SOFA 中間件。

2、功能簡介

為了解決 Spring Boot 在實施大規模微服務架構時候的問題,SOFABoot 提供了以下的能力:

2.1 增強 Spring Boot 的健康檢查能力

針對 Spring Boot 缺少 Readiness Check 能力的情況,SOFABoot 增加了 Spring Boot 現有的健康檢查的能力,提供了 Readiness Check 的能力。利用 Readiness Check 的能力,SOFA 中間件中的各個元件隻有在 Readiness Check 通過之後,才将流量引入到應用的執行個體中,比如 RPC,隻有在 Readiness Check 通過之後,才會向服務注冊中心注冊,後面來自上遊應用的流量才會進入。

除了中間件可以利用 Readiness Check 的事件來控制流量的進入之外,PAAS 系統也可以通過通路 http://localhost:8080/health/readiness 來擷取應用的 Readiness Check 的狀況,用來控制例如負載均衡裝置等等流量的進入。

2.2 提供類隔離的能力

為了解決 Spring Boot 下的類依賴沖突的問題,SOFABoot 基于 SOFAArk 提供了 Spring Boot 上的類隔離的能力,在一個 SOFABoot 的系統中,隻要引入 SOFAArk 相關的依賴,就可以将 SOFA 中間件相關的類和應用相關的類的 ClassLoader 進行隔離,防止出現類沖突。當然,使用者也可以基于 SOFAArk,将其他的中間件、第三方的依賴和應用的類進行隔離。

2.3 日志空間隔離能力

為了統一大規模微服務場景下的中間件日志的列印,SOFABoot 提供了日志空間隔離的能力給 SOFA 中間件,SOFA 中間件中的各個元件采用日志空間隔離的能力之後,自動就會将本身的日志和應用的普通日志隔離開來,并且列印的日志的路徑也是相對固定,非常友善進行統一地監控。

2.4 SOFA 中間件的內建管理

基于 Spring Boot 的自動配置能力,SOFABoot 提供了 SOFA 中間件統一易用的程式設計接口以及 Spring Boot 的 Starter,友善在 Spring Boot 環境下使用 SOFA 中間件,SOFA 中間件中的各個元件都是獨立可插拔的,節約開發時間,和後期維護的成本。

2.5 子產品化開發

SOFABoot 從 2.4.0 版本開始支援基于 Spring 上下文隔離的子產品化開發能力,每個 SOFABoot 子產品使用獨立的 Spring 上下文,避免不同 SOFABoot 子產品間的 BeanId 沖突,有效降低企業級多子產品開發時團隊間的溝通成本。

五、技術選型

示例

SOFABoot 的示例工程 sofaboot-samples 包含以下 demo 項目:

  • SOFABoot 示例工程
  • SOFABoot 示例工程(包含類隔離能力)
  • SOFABoot 示例工程(包含子產品化開發能力)
  • SOFABoot 示例工程(使用 SOFARPC)
  • SOFABoot 示例工程(使用定時任務)

在本文檔中,将建立一個 Spring Boot 的工程,引入 SOFABoot 基礎依賴,并且引入 SOFABoot 的健康檢查擴充能力,示範如何快速上手 SOFABoot。

環境準備

要使用 SOFABoot,需要先準備好基礎環境,SOFABoot 依賴以下環境: - JDK7 或 JDK8 - 需要采用 Apache Maven 3.2.5 或者以上的版本來編譯

建立工程

SOFABoot 是直接建構在 Spring Boot 之上,是以可以使用 Spring Boot 的工程生成工具 來生成,在本文檔中,我們需要添加一個 Web 的依賴,以便最後在浏覽器中檢視效果。

引入 SOFABoot

在建立好一個 Spring Boot 的工程之後,接下來就需要引入 SOFABoot 的依賴,首先,需要将上文中生成的 Spring Boot 工程的 zip 包解壓後,修改 maven 項目的配置檔案 pom.xml,将

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>${spring.boot.version}</version>
    <relativePath/> 
</parent>
           

替換為:

<parent>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>sofaboot-dependencies</artifactId>
    <version>${sofa.boot.version}</version>
</parent>
           

這裡的 ${sofa.boot.version} 指定具體的 SOFABoot 版本,參考釋出曆史。 然後,添加 SOFABoot 健康檢查擴充能力的依賴及 Web 依賴(友善檢視健康檢查結果):

<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>healthcheck-sofa-boot-starter</artifactId>
</dependency>

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>
           

最後,在工程的 application.properties 檔案下添加 SOFABoot 工程常用的參數配置,其中 spring.application.name 是必需的參數,用于标示目前應用的名稱;logging path 用于指定日志的輸出目錄。

# Application Name
spring.application.name=SOFABoot Demo
# logging path
logging.path=./logs
           

運作

可以将工程導入到 IDE 中運作生成的工程裡面中的 main 方法(一般上在 XXXApplication 這個類中)啟動應用,也可以直接在該工程的根目錄下運作 mvn spring-boot:run,将會在控制台中看到啟動列印的日志:

2018-04-05 21:36:26.572  INFO ---- Initializing ProtocolHandler ["http-nio-8080"]
2018-04-05 21:36:26.587  INFO ---- Starting ProtocolHandler [http-nio-8080]
2018-04-05 21:36:26.608  INFO ---- Using a shared selector for servlet write/read
2018-04-05 21:36:26.659  INFO ---- Tomcat started on port(s): 8080 (http)
           

可以通過在浏覽器中輸入 http://localhost:8080/sofaboot/versions 來檢視目前 SOFABoot 中使用 Maven 插件生成的版本資訊彙總,結果類似如下:

[
  {
    GroupId: "com.alipay.sofa",
    Doc-Url: "https://github.com/sofastack/sofa-boot",
    ArtifactId: "infra-sofa-boot-starter",
    Built-Time: "2018-04-05T20:55:26+0800",
    Commit-Time: "2018-04-05T20:54:26+0800",
    Commit-Id: "049bf890bb468aafe6a3e07b77df45c831076996",
    Version: "2.4.4"
  }
]
           

注: 在 SOFABoot 3.x 中調整了 endpoint 路徑,sofaboot/versions 更改為 actuator/versions

可以通過在浏覽器中輸入 http://localhost:8080/health/readiness 檢視應用 Readiness Check 的狀況,類似如下:

{
  status: "UP",
  sofaBootComponentHealthCheckInfo: {
    status: "UP"
  },
  springContextHealthCheckInfo: {
    status: "UP"
  },
  DiskSpace: {
    status: "UP",
    total: 250140434432,
    free: 22845308928,
    threshold: 10485760
  }
}
           

注: 在 SOFABoot 3.x 中調整了 endpoint 路徑,health/readiness 更改為 actuator/readiness

status: "UP" 表示應用 Readiness Check 健康的。可以通過在浏覽器中輸入 http://localhost:8080/health 來檢視應用的運作時健康狀态(可能會随着時間發生變化)。

注: 在 SOFABOOT 3.X 中調整了 endpoint 路徑,/health 更改為 /actuator/health

檢視日志

在上面的 application.properties 裡面,我們配置的日志列印目錄是 ./logs 即目前應用的根目錄(我們可以根據自己的實踐需要配置),在目前工程的根目錄下可以看到類似如下結構的日志檔案:

./logs
├── health-check
│   ├── sofaboot-common-default.log
│   └── sofaboot-common-error.log
├── infra
│   ├── common-default.log
│   └── common-error.log
└── spring.log
           

如果應用啟動失敗或者健康檢查傳回失敗,可以通過相應的日志檔案找到錯誤的原因,有些需要關注 common-error.log 日志。

測試

我們知道,SpringBoot 官方提供了和 JUnit4 內建的 SpringRunner, 用于內建測試用例的編寫; 在 SOFABoot 中,依然可以使用原生的 SpringRunner, 但是推薦使用 SOFABoot 自帶的 SofaBootRunner 以及 SofaJUnit4Runner 編寫內建測試和單元測試;應用需要額外引入如下 Starter:

<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>test-sofa-boot-starter</artifactId>
</dependency>
           

需要注意的是,如果需要使用 SOFABoot 的類隔離的能力,則必須需要引入上述的依賴,并且使用 SofaBootRunner 和 SofaJUnit4Runner 來測試。

子產品化開發

這部分将示範如何在 SOFABoot 環境下進行子產品化開發,您可以直接在工程下找到本文檔的示例代碼,項目一共包括四個子產品:

.
│
├── service-facade 
│ 
├── service-provider
│ 
├── service-consumer
│ 
└── sofa-boot-run
           

各個子產品的作用如下:

  • service-facade: 示範 JVM 服務釋出與引用的 API 包;
  • service-provider: 示範 XML 方式、Annotation 方式、API 方式釋出 JVM 服務;
  • service-consumer: 示範 XML 方式、Annotation 方式、API 方式引用 JVM 服務;
  • sofa-boot-run: 啟動包含 SOFABoot 子產品的 SOFA Boot 應用。

建議在學習該 Demo 之前,參考子產品隔離文檔

定義服務 API

service-facade 子產品包含用于示範 JVM 服務釋出與引用的 API :

public interface SampleJvmService {
    String message();
}
           

釋出 JVM 服務

service-provider 是一個 SOFABoot 子產品,用于示範 XML 方式、Annotation 方式、API 方式釋出 JVM 服務。

定義 SOFABoot 子產品

為 service-provider 子產品增加 sofa-module.properties 檔案,将其定義為 SOFABoot 子產品:

Module-Name=com.alipay.sofa.service-provider
           

XML 方式釋出服務

實作 SampleJvmService 接口:

public class SampleJvmServiceImpl implements SampleJvmService {
    private String message;

    @Override
    public String message() {
        System.out.println(message);
        return message;
    }

    // getters and setters
}
           

增加 META-INF/spring/service-provide.xml 檔案,将 SampleJvmServiceImpl 釋出為 JVM 服務:

<bean id="sampleJvmService" class="com.alipay.sofa.isle.sample.SampleJvmServiceImpl">
    <property name="message" value="Hello, jvm service xml implementation."/>
</bean>

<sofa:service ref="sampleJvmService" interface="com.alipay.sofa.isle.sample.SampleJvmService">
    <sofa:binding.jvm/>
</sofa:service>
           

Annotation 方式釋出服務

實作 SampleJvmService 接口并增加 @SofaService 注解:

@SofaService(uniqueId = "annotationImpl")
public class SampleJvmServiceAnnotationImpl implements SampleJvmService {
    @Override
    public String message() {
        String message = "Hello, jvm service annotation implementation.";
        System.out.println(message);
        return message;
    }
}
           

為了區分 XML 方式釋出的 JVM 服務,注解上需要增加 uniqueId 屬性。

将 SampleJvmServiceAnnotationImpl 配置成一個 Spring Bean,保證 @SofaService 注解生效:

<bean id="sampleJvmServiceAnnotation" class="com.alipay.sofa.isle.sample.SampleJvmServiceAnnotationImpl"/>
           

API 方式釋出服務

增加 PublishServiceWithClient 類,示範 API 方式釋出服務:

public class PublishServiceWithClient implements ClientFactoryAware {
    private ClientFactory clientFactory;

    public void init() {
        ServiceClient serviceClient = clientFactory.getClient(ServiceClient.class);
        ServiceParam serviceParam = new ServiceParam();
        serviceParam.setInstance(new SampleJvmServiceImpl("Hello, jvm service service client implementation."));
        serviceParam.setInterfaceType(SampleJvmService.class);
        serviceParam.setUniqueId("serviceClientImpl");
        serviceClient.service(serviceParam);
    }

    @Override
    public void setClientFactory(ClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }
}
           

将 PublishServiceWithClient 配置為 Spring Bean,并設定 init-method ,使PublishServiceWithClient 在 Spring 重新整理時釋出服務:

<bean id="publishServiceWithClient" class="com.alipay.sofa.isle.sample.PublishServiceWithClient" init-method="init"/>
           

引用 JVM 服務

service-consumer 是一個 SOFABoot 子產品,用于示範 XML 方式、Annotation 方式、API 方式引用 JVM 服務。

定義 SOFABoot 子產品

為 service-consumer 子產品增加 sofa-module.properties 檔案,将其定義為 SOFABoot 子產品:

Module-Name=com.alipay.sofa.service-consumer
Require-Module=com.alipay.sofa.service-provider
           

在 sofa-module.properties 檔案中需要指定 Require-Module,保證 service-provider 子產品在 service-consumer 子產品之前重新整理。

XML 方式引用服務

增加 META-INF/spring/service-consumer.xml 檔案,引用 service-provider 子產品釋出的服務:

<sofa:reference id="sampleJvmService" interface="com.alipay.sofa.isle.sample.SampleJvmService">
    <sofa:binding.jvm/>
</sofa:service>
           

Annotation 方式引用服務

定義 JvmServiceConsumer 類,并在其 sampleJvmServiceAnnotationImpl 屬性上增加 @SofaReference 注解:

public class JvmServiceConsumer implements ClientFactoryAware {
    @SofaReference(uniqueId = "annotationImpl")
    private SampleJvmService sampleJvmServiceAnnotationImpl;
}
           

将 JvmServiceConsumer 配置成一個 Spring Bean,保證 @SofaReference 注解生效:

<bean id="consumer" class="com.alipay.sofa.isle.sample.JvmServiceConsumer" init-method="init" />
           

API 方式引用服務

JvmServiceConsumer 實作 ClientFactoryAware 接口,并在其 init 方法中引用 JVM 服務:

public class JvmServiceConsumer implements ClientFactoryAware {
    private ClientFactory    clientFactory;

    public void init() {
        ReferenceClient referenceClient = clientFactory.getClient(ReferenceClient.class);
        ReferenceParam<SampleJvmService> referenceParam = new ReferenceParam<>();
        referenceParam.setInterfaceType(SampleJvmService.class);
        referenceParam.setUniqueId("serviceClientImpl");
        SampleJvmService sampleJvmServiceClientImpl = referenceClient.reference(referenceParam);
        sampleJvmServiceClientImpl.message();
    }

    @Override
    public void setClientFactory(ClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }
}
           

啟動 SOFABoot 應用

将子產品 parent 配置為 SOFABoot:

<parent>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>sofaboot-dependencies</artifactId>
    <version>2.4.4</version>
</parent>
           

為子產品增加 isle-sofa-boot-starter 及 service-provider 、 service-consumer 依賴:

<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>isle-sofa-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>service-provider</artifactId>
</dependency>
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>service-consumer</artifactId>
</dependency>
           

啟動 ApplicationRun 類,控制台将看到以下輸出:

Hello, jvm service xml implementation.
Hello, jvm service annotation implementation.
Hello, jvm service service client implementation.
           

編寫測試用例

SOFABoot 子產品化測試方法與 Spring Boot 測試方法一緻,隻需在測試用例上增加 @SpringBootTest 注解及 @RunWith(SpringRunner.class) 注解即可。在測試用例中,還可以使用 @SofaReference 注解,對 SOFABoot 子產品釋出的服務進行測試:

@SpringBootTest
@RunWith(SpringRunner.class)
public class SofaBootWithModulesTest {
    @SofaReference
    private SampleJvmService sampleJvmService;

    @SofaReference(uniqueId = "annotationImpl")
    private SampleJvmService sampleJvmServiceAnnotationImpl;

    @SofaReference(uniqueId = "serviceClientImpl")
    private SampleJvmService sampleJvmServiceClientImpl;

    @Test
    public void test() {
        Assert.assertEquals("Hello, jvm service xml implementation.", sampleJvmService.message());
        Assert.assertEquals("Hello, jvm service annotation implementation.",
            sampleJvmServiceAnnotationImpl.message());
        Assert.assertEquals("Hello, jvm service service client implementation.",
            sampleJvmServiceClientImpl.message());
    }
}           

六、源碼位址

通路一飛開源:https://code.exmay.com/

繼續閱讀