5. spring boot集成AWS XRay實現代碼性能分析

目錄

 

什麼是 AWS X-Ray

AWS X-Ray 守護程序

Linux下載守護程序

在 Linux 上運行 X-Ray 守護程序

Java 的 AWS X-Ray 開發工具包

創建一個aws子模塊

創建子模塊

在父模塊中對子模塊以及xray相關的包進行版本管理

在aws子模塊中添加xray,servlet,spring相關的依賴

創建XRayFilter對需要進行監控的請求進行過濾

XRayFilterCofig

XRayFilterCofig 接口

XRayFilterCofigImpl 類

配置application.properties

實現XRayController對xray配置進行設置

實現XrayFilter

對XRayFilter的bean進行註冊

使用自定義切面創建子分段

創建切面

配置使用切面

測試

項目地址

使用X-Ray SDK for spring創建子分段

配置spring-data-jpa

修改子模塊的pom文件添加

實現繼承自AbstractXRayInterceptor的Aspect

測試

項目地址

參考


什麼是 AWS X-Ray

AWS X-Ray 是一項服務,收集您應用程序所服務的請求的相關數據,並提供用於查看、篩選和獲取數據洞察力的工具,以確定問題和發現優化的機會。對於任何被跟蹤的對您應用程序的請求,您不僅可以查看請求和響應的詳細信息,還可以查看您的應用程序對下游 AWS 資源、微服務、數據庫和 HTTP Web API 進行的調用的詳細信息。

X-Ray 開發工具包提供:

  • 攔截器,可添加到您的代碼中以跟蹤傳入 HTTP 請求

  • 客戶端處理程序,可分析您的應用程序用來調用其他 AWS 服務的 AWS 開發工具包客戶端

  • HTTP 客戶端,用於分析對其他內部和外部 HTTP Web 服務的調用

該開發工具包還支持分析對 SQL 數據庫的調用、自動 AWS 開發工具包客戶端分析以及其他功能。

藉助 X-Ray SDK for Java,通過進行兩項配置更改,您可以跟蹤自己應用程序的所有主要和下游 AWS 資源:

  • 在 WebConfig 類或 web.xml 文件中,向 servlet 配置中添加X-Ray SDK for Java的跟蹤篩選器。

  • 將 X-Ray SDK for Java 子模塊作爲 Maven 或 Gradle 生成配置中的生成依賴項

AWS X-Ray 守護程序

AWS X-Ray 守護程序是一個軟件應用程序,它偵聽 UDP 端口 2000 上的流量,收集原始分段數據,並將其中繼到 AWS X-Ray API。守護程序與 AWS X-Ray 開發工具包結合使用,並且必須正在運行,這樣開發工具包發送的數據才能到達 X-Ray 服務。

在 AWS Lambda 和 AWS Elastic Beanstalk 上,使用這些服務與 X-Ray 的集成來運行該守護程序。每次對採樣請求調用函數時,Lambda 都會自動運行該守護程序。在 Elastic Beanstalk 上,使用 XRayEnabled 配置選項在您環境中的實例上運行該守護程序。

要在本地、內部部署或其他 AWS 服務上運行 X-Ray 守護程序,請從 Amazon S3 下載它運行它,然後賦予其權限以將分段文檔上傳到 X-Ray。

Linux下載守護程序

下載地址: https://s3.ap-southeast-1.amazonaws.com/aws-xray-assets.ap-southeast-1/xray-daemon/aws-xray-daemon-linux-3.x.zip 

在 Linux 上運行 X-Ray 守護程序

在本地運行時,守護程序可以從 AWS 開發工具包憑證文件 (您的用戶目錄中的 .aws/credentials) 或從環境變量讀取憑證。

守護程序在端口 2000 上偵聽 UDP 數據。您可以使用配置文件和命令行選項更改端口和其他選項。

您可以從命令行運行守護程序可執行文件。使用 -o 選項以本地模式運行,-n 選項設置區域。

~/xray-daemon$ ./xray -o -n ap-southeast-1

要在後臺運行守護程序,請使用 &

~/xray-daemon$ ./xray -o -n ap-southeast-1 &

有如下回顯表示開啓成功

2020-05-16T16:00:53+08:00 [Info] Initializing AWS X-Ray daemon 3.2.0
2020-05-16T16:00:53+08:00 [Info] Using buffer memory limit of 79 MB
2020-05-16T16:00:53+08:00 [Info] 1264 segment buffers allocated
2020-05-16T16:00:53+08:00 [Info] Using region: ap-southeast-1
2020-05-16T16:00:53+08:00 [Info] HTTP Proxy server using X-Ray Endpoint : https://xray.ap-southeast-1.amazonaws.com
2020-05-16T16:00:53+08:00 [Info] Starting proxy http server on 127.0.0.1:2000

使用 pkill 終止在後臺運行的守護程序進程。

~$ pkill xray

Java 的 AWS X-Ray 開發工具包

X-Ray SDK for Java是面向 Java Web 應用程序的一組庫,提供用於生成跟蹤數據並將其發送到 X-Ray 守護程序的類和方法。跟蹤數據包括由應用程序提供服務的傳入 HTTP 請求的相關信息,以及應用程序使用 AWS 開發工具包、HTTP 客戶端或 SQL 數據庫連接器對下游服務進行的調用。您還可以手動創建分段並在註釋和元數據中添加調試信息。

首先通過添加AWSXRayServletFilter 作爲 servlet 篩選器來跟蹤傳入請求。Servlet 篩選器創建一個分段。當分段打開時,您可以使用開發工具包客戶端的方法將信息添加到分段,並創建子分段以跟蹤下游調用。開發工具包還會自動記錄在分段打開時應用程序引發的異常。

從版本 1.3 開始,您可以使用 Spring 中面向方面的編程 (AOP) 來分析應用程序。這意味着,您可以在應用程序運行於 AWS 上時分析應用程序,而無需嚮應用程序的運行時添加任何代碼。

創建一個aws子模塊

創建子模塊

該子模塊主要用於提供各種aws的服務,與xray相關的所有代碼都會放在該模塊中。

選中social-network-serverless project,然後Intellij -> File -> New Project,在彈出的對話框中選擇Maven, 使用默認配置,然後Next進入下一步配置。

  • 配置項目的名稱,位置和artifact信息(可以根據自己需要進行配置),點擊Finish完成模塊創建。
    • Parent:social-network-serverless
    • Name:social-network-serverless-aws
    • Grouop: com.jessica
    • Artifact:social-network-serverless-aws

在父模塊中對子模塊以及xray相關的包進行版本管理

修改父模塊的pom文件,在dependencyManagement中加入social-network-serverless-aws

<dependency>
    <groupId>com.jessica</groupId>
    <artifactId>social-network-serverless-aws</artifactId>
    <version>${project.version}</version>
</dependency>

添加xray包的版本管理

<dependency>
     <groupId>com.amazonaws</groupId>
     <artifactId>aws-xray-recorder-sdk-bom</artifactId>
     <version>2.4.0</version>
     <type>pom</type>
     <scope>import</scope>
</dependency>

添加servlet-api的版本管理:需要注意的是這裏必須使用3.1.0以上的版本,不然的話啓動時會出現如下錯誤: java.lang.NoSuchMethodError: javax.servlet.ServletContext.getVirtualServerName()Ljava/lang/String。

錯誤原因是spring boot 2.2.7.RELEASE版本的項目依賴了tomcat-embed-core-9.0.34.jar已經有ServletContext了,並且ServletContext接口確確實實定義了getVirtualServerName()方法,如果此時引入的servlet包中ServletContext沒有提供getVirtualServerName方法的話,jvm先加載了servelt-api中的ServletContext類,後來再加載tomcat-embed-core-9.0.34中的ServletContext時發現它已經存在就不再加載了,這就會導致已加載的ServletContext接口沒有getVirtualServerName方法的定義。3.1.0以上的版本的servlet-api提供了getVirtualServerName方法,所以這裏必須使用3.1.0以上的版本。

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

在aws子模塊中添加xray,servlet,spring相關的依賴

創建filter時用到了servlet-api的filter相關的類

本模塊主要用到了spring-context提供了spring常用的annotation,比如@Configuration, @Bean等

本模塊主要用到了spring-web提供的@RestController的annotation

spring-aop和spring-aspects主要用於面向自定義切面的實現

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>social-network-serverless</artifactId>
        <groupId>com.jessica</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>social-network-serverless-aws</artifactId>

    <dependencies>
        <!--aws xray-->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-xray-recorder-sdk-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-xray-recorder-sdk-aws-sdk</artifactId>
        </dependency>

        <!--servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
        </dependency>
    </dependencies>

</project>

創建XRayFilter對需要進行監控的請求進行過濾

對filter主要有兩點考慮:

  • 一是可以對請求的URL進行過濾,實現只對特定的url進行監控
  • 二是有一個監控的開關,只有當開關打開時才進行監控,開關關閉時則不需要進行監控

XRayFilterCofig

爲了達到上述目的,可以創建一個XRayFilterCofig,對url和開關狀態進行管理,並實現通過application.properties對配置的初始值進行設置.

首先,爲了利用spring的DI,XRayFilterCofig需要有一個接口以及相應的實現

XRayFilterCofig 接口

public interface XRayFilterConfig {
    /**
     * @return
     */
    Set<String> getUrlPatterns();

    /**
     * @param enabled
     */
    void setFilterEnabled(boolean enabled);

    /**
     * @return
     */
    boolean isFilterEnabled();
}

XRayFilterCofigImpl 類

/**
 * 對XRayFilter的配置進行管理:urlPattern以及filter開啓標誌位
 */
@Component
public class XRayFilterConfigImpl implements XRayFilterConfig {
    @Value("${xray.url:*}")
    private String url;

    @Value("${xray.filter.enabled:false}")
    private boolean enabled;

    @Override
    public Set<String> getUrlPatterns() {
        String[] filterUrls = this.url.trim().split(",");
        return new HashSet<>(Arrays.asList(filterUrls));
    }

    @Override
    public void setFilterEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public boolean isFilterEnabled() {
        return this.enabled;
    }
}

配置application.properties

在social-network-serverless-web模塊中修改application.properties文件,設置url和開關的初始值。

xray.url=/user/create,\
/xray/switch
xray.filter.enabled=true

實現XRayController對xray配置進行設置

/**
 * 對XRayFilter是否開啓進行管理
 */
@RestController
@RequestMapping("/xray")
public class XRayController {
    @Autowired
    private XRayFilterConfig filterConfig;

    @RequestMapping(value = "/switch", method = RequestMethod.POST)
    public boolean setEnabled(@RequestParam boolean enabled) {
        this.filterConfig.setFilterEnabled(enabled);
        return true;
    }

    @RequestMapping(value = "/enabled", method = RequestMethod.GET)
    public boolean isEnabled() {
        return this.filterConfig.isFilterEnabled();
    }
}

實現XrayFilter

  • 這裏採用自定義XrayFilter的方式是爲了對AWSXRayServletFilter進行功能增強:
    • AWSXRayServletFilter的基本作用是對請求進行過濾,創建XRay的主分片,並在responseHeader中添加名爲X-Amzn-Trace-Id的header
    • XRayFilter的增強在於只有在filterConfig的filterEnable標誌爲true時纔會應用,爲false時直接跳過當前filter
  •  XRayFilter並沒有添加Component註解,有兩點原因:
    • 一是因爲繼承自AWSXRayServletFilter,構造時必須提供有一個string或者SegmentNamingStrategy類型的構造參數,需要爲該參數創建一個bean才能進行自動依賴注入,
    • 二是爲了使用FilterRegistrationBean對XRayFilter進行bean註冊,以實現爲filter添加url的過濾規則
  • 因爲XRayFilter沒有添加Component註解,因此不能使用Autowired對filterConfig進行注入,將其聲明爲final採用強制以構造函數參數的方式進行初始化
public class XRayFilter extends AWSXRayServletFilter {
    // 因爲XRayFilter沒有添加Component註解,因此不能使用Autowired對filterConfig進行注入,需要以構造函數參數的方式進行初始化
    private XRayFilterConfig filterConfig;

    public XRayFilter(String segmentName, XRayFilterConfig filterConfig) {
        super(segmentName);
        this.filterConfig = filterConfig;
    }

    static {
        AWSXRayRecorderBuilder builder = AWSXRayRecorderBuilder.standard();
        URL ruleFile = XRayConfig.class.getClassLoader().getResource("xray-rules.json");
        builder.withSamplingStrategy(new CentralizedSamplingStrategy(ruleFile));
        AWSXRay.setGlobalRecorder(builder.build());
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (this.filterConfig != null && this.filterConfig.isFilterEnabled()) {
            super.doFilter(request, response, chain);
        } else {
            chain.doFilter(request, response);
        }
    }
}

對XRayFilter的bean進行註冊

不直接聲明XRayFilter的bean,而是使用FilterRegistrationBean對XRayFilter的bran進行註冊,是爲了給XRayFilter設置urlPattern

@Configuration
public class XRayConfig {

    @Autowired
    private XRayFilterConfig filterConfig;

    @Bean
    public FilterRegistrationBean<AWSXRayServletFilter> xRayFilter() {
        FilterRegistrationBean<AWSXRayServletFilter> registrationBean
                = new FilterRegistrationBean<>();
        XRayFilter filter = new XRayFilter("social-network-serverless-framework", this.filterConfig);
        registrationBean.setFilter(filter);
        this.filterConfig.getUrlPatterns().forEach(url -> registrationBean.addUrlPatterns(url));
        return registrationBean;
    }
}

使用自定義切面創建子分段

創建切面

利用AOP的around來創建子分段,在執行目標方法之前創建子分段並開始計時,在執行目標方法之後結束子分段。

這裏需要注意的點是切面實現只要調用的方法符合pointcut指定的JoinPoint的規則,XRayTraceAspect的traceAround方法就會執行,不管調用該JoinPoint對應的requst的url是否符合XRayilter的urlPattern或者XRayilter是否開啓。但是主分片只有在XRayilter被應用,即XRayilter開啓且requst的url符合XRayilter的urlPattern時纔會創建,因此可以利用主分片是否存在來作爲是否需要創建子分片的條件。

另外爲了便於在xray中查看子分段對應的方法的耗時,將子分段命名爲簡單類名.方法名,並將類的完整類名作爲metadata保存在子分段中。

/**
 * XRayTraceAspect的主要作用是創建XRay的子分片
 */
@Aspect
@Component
@Qualifier("XRayTraceAspect")
public class XRayTraceAspect {
    public Object traceAround(ProceedingJoinPoint jp) throws Throwable {
        // 利用主分片是否存在來作爲是否需要創建子分片的條件
        Segment segment = null;
        try {
            segment = AWSXRay.getCurrentSegment();
            // 主分片存在,則創建子分片
            MethodSignature methodSignature = (MethodSignature) jp.getSignature();
            String className = methodSignature.getDeclaringType().getName();
            // 創建子分片,並設置子分片的名字爲simpleClassName.methodName,此時開始計時
            Subsegment subsegment = AWSXRay.beginSubsegment(methodSignature.getDeclaringType().getSimpleName() + "." +methodSignature.getName());
            // 設置子分片的metadata爲className
            subsegment.putMetadata("className", className);
            Object result = null;
            try {
                // 執行JoinPoint
                result = jp.proceed(jp.getArgs());
            } catch (Throwable throwable) {
                // 向子分片添加異常信息
                subsegment.addException(throwable);
                throw new RuntimeException(throwable);
            } finally {
                // 結束計時
                AWSXRay.endSubsegment();
            }
            return result;
        } catch (SegmentNotFoundException exception) {
            // 主分片不存在直接執行JoinPoint
            return jp.proceed(jp.getArgs());
        }
    }
}

配置使用切面

在social-network-serverless-web子模塊的src/main/resources目錄下創建xray-aop.xml文件,對切面的切點進行配置。

切點爲所有的service和dao中的方法。

<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop = "http://www.springframework.org/schema/aop"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
    <aop:config>
        <!--aspect的ref值與XRayTraceAspect類的Qualifier註解指定的名稱相同-->
        <aop:aspect id = "XRayTraceAspect" ref = "XRayTraceAspect">
            <!--service rule-->
            <aop:pointcut id = "service"
                          expression = "execution(* com.jessica.social.network.serverless.service.impl.*.*(..))"/>
            <aop:around pointcut-ref="service" method = "traceAround" arg-names="jp"/>

            <!--dao rule-->
            <aop:pointcut id = "dao"
                          expression = "execution(* com.jessica.social.network.serverless.dao.impl.*.*(..))"/>
            <aop:around pointcut-ref="dao" method = "traceAround" arg-names="jp"/>
        </aop:aspect>
    </aop:config>
</beans>

以java config文件的方式引入切面配置,在social-network-serverless-web子模塊中創建XRayAOPConfig文件,引入xray-aop.xml文件的配置.

package com.jessica.social.network.serverless.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@ImportResource("classpath:xray-aop.xml")
public class XRayAOPConfig {
}

測試

啓動服務器,訪問http://127.0.0.1:8080/swagger-ui.html 進入swagger頁面,嘗試發送一個create user的請求,在response header中可以看到X-Amzn-Trace-Id的header信息,記錄trace id。

打開aws xray的頁面https://ap-southeast-1.console.aws.amazon.com/xray/home?region=ap-southeast-1#/traces ,在搜索框中輸入trace id進行搜索,即可看到對應的結果。注意搜索的時候控制檯的分區與本地啓動xray dameon的分區保持一致,這裏用的都是ap-southeast-1.

嘗試發送一個get user的請求,在response header中可以看到沒有X-Amzn-Trace-Id的header信息,即並沒有記錄該請求的訪問時間.

嘗試發送一個get xray filter enable狀態的請求,http://127.0.0.1:8080/xray/enabled, 在response header中可以看到沒有X-Amzn-Trace-Id的header信息,即並沒有記錄該請求的訪問時間,且請求返回值爲true。

嘗試發送一個set xray filter enable狀態爲false的請求,http://127.0.0.1:8080/xray/switch?enabled=false, 在response header中可以看到有X-Amzn-Trace-Id的header信息,且請求返回值爲true。因爲該請求並沒有調用到符合xray-aop中定義的service和dao的切點,所以只有一個主分段,並沒有子分段信息.

嘗試發送一個get xray filter enable狀態的請求,http://127.0.0.1:8080/xray/enabled, 在response header中可以看到沒有X-Amzn-Trace-Id的header信息,即並沒有記錄該請求的訪問時間,且請求返回值爲false。

嘗試發送一個create user的請求,在response header中可以看到也沒有X-Amzn-Trace-Id的header信息,因爲filter已經關閉。

項目地址

https://github.com/JessicaWin/social-network-serverless/tree/v0.5

使用X-Ray SDK for spring創建子分段

使用AWSXRayServletFilter和xray中提供的spring相關的annotation來實現子分段創建.

首先將在social-network-serverless-web子模塊的src/main/resources目錄下的xray-aop.xml文件中的切面配置註釋掉.

配置spring-data-jpa

在social-network-serverless父模塊中添加spring-data-jpa的版本管理

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

修改子模塊的pom文件添加

在social-network-serverless-aws的pom文件中添加aws-xray-recorder-sdk-spring和spring-data-jpa的依賴.

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-xray-recorder-sdk-spring</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
</dependency>

因爲AbstractXRayInterceptor的springRepositories方法的切點中使用到了org.springframework.data.repository.Repository

@Pointcut("execution(public !void org.springframework.data.repository.Repository+.*(..))")
protected void springRepositories() {
}

如果不添加spring-data-jpa的包啓動時會報錯:Caused by: java.lang.IllegalArgumentException: warning no match for this type name: org.springframework.data.repository.Repository [Xlint:invalidAbsoluteTypeName]

實現繼承自AbstractXRayInterceptor的Aspect

重寫AbstractXRayInterceptor的xrayEnabledClasses方法,添加切點爲使用XRayEnabled,Service或者Repository的annotation.

@Aspect
@Component
public class XRayInspector extends AbstractXRayInterceptor {
    // 監控所有的帶有@XRayEnabled, @Service或者@Repository的註解的類
    @Override
    @Pointcut("@within(com.amazonaws.xray.spring.aop.XRayEnabled) || @within(org.springframework.stereotype.Service) || @within(org.springframework.stereotype.Repository)")
    public void xrayEnabledClasses() {
    }
}

測試

啓動服務器,訪問http://127.0.0.1:8080/swagger-ui.html 進入swagger頁面,嘗試發送一個create user的請求,在response header中可以看到X-Amzn-Trace-Id的header信息,記錄trace id。

打開aws xray的頁面https://ap-southeast-1.console.aws.amazon.com/xray/home?region=ap-southeast-1#/traces ,在搜索框中輸入trace id進行搜索,即可看到對應的結果。注意搜索的時候控制檯的分區與本地啓動xray dameon的分區保持一致,這裏用的都是ap-southeast-1.

通過下圖可以看到AbstractXRayInterceptor默認子分段的名字只是方法名,可以通過重寫processXRayTrace(ProceedingJoinPoint pjp)方法來進行修改,這裏不再贅述.

項目地址

https://github.com/JessicaWin/social-network-serverless/tree/v0.5.1

 

參考

https://docs.aws.amazon.com/zh_cn/xray/latest/devguide/aws-xray.html

https://docs.aws.amazon.com/zh_cn/xray/latest/devguide/xray-daemon.html

https://docs.aws.amazon.com/zh_cn/xray/latest/devguide/xray-sdk-java.html

https://www.baeldung.com/spring-boot-add-filter

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章