目錄
在aws子模塊中添加xray,servlet,spring相關的依賴
實現繼承自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下載守護程序
在 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