目录
什么是 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 资源、微服务、数据库和 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 守护程序是一个软件应用程序,它侦听 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。
下载地址: https://s3.ap-southeast-1.amazonaws.com/aws-xray-assets.ap-southeast-1/xray-daemon/aws-xray-daemon-linux-3.x.zip
在本地运行时,守护程序可以从 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
X-Ray SDK for Java是面向 Java Web 应用程序的一组库,提供用于生成跟踪数据并将其发送到 X-Ray 守护程序的类和方法。跟踪数据包括由应用程序提供服务的传入 HTTP 请求的相关信息,以及应用程序使用 AWS 开发工具包、HTTP 客户端或 SQL 数据库连接器对下游服务进行的调用。您还可以手动创建分段并在注释和元数据中添加调试信息。
首先通过添加AWSXRayServletFilter 作为 servlet 筛选器来跟踪传入请求。Servlet 筛选器创建一个分段。当分段打开时,您可以使用开发工具包客户端的方法将信息添加到分段,并创建子分段以跟踪下游调用。开发工具包还会自动记录在分段打开时应用程序引发的异常。
从版本 1.3 开始,您可以使用 Spring 中面向方面的编程 (AOP) 来分析应用程序。这意味着,您可以在应用程序运行于 AWS 上时分析应用程序,而无需向应用程序的运行时添加任何代码。
该子模块主要用于提供各种aws的服务,与xray相关的所有代码都会放在该模块中。
选中social-network-serverless project,然后Intellij -> File -> New Project,在弹出的对话框中选择Maven, 使用默认配置,然后Next进入下一步配置。
修改父模块的pom文件,在dependencyManagement中加入social-network-serverless-aws
com.jessica
social-network-serverless-aws
${project.version}
添加xray包的版本管理
com.amazonaws
aws-xray-recorder-sdk-bom
2.4.0
pom
import
添加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以上的版本。
javax.servlet
javax.servlet-api
3.1.0
创建filter时用到了servlet-api的filter相关的类
本模块主要用到了spring-context提供了spring常用的annotation,比如@Configuration, @Bean等
本模块主要用到了spring-web提供的@RestController的annotation
spring-aop和spring-aspects主要用于面向自定义切面的实现
social-network-serverless
com.jessica
0.0.1-SNAPSHOT
4.0.0
social-network-serverless-aws
com.amazonaws
aws-xray-recorder-sdk-core
com.amazonaws
aws-xray-recorder-sdk-aws-sdk
javax.servlet
javax.servlet-api
provided
org.springframework
spring-context
org.springframework
spring-web
org.springframework
spring-aop
org.springframework
spring-aspects
org.springframework.boot
spring-boot
对filter主要有两点考虑:
为了达到上述目的,可以创建一个XRayFilterCofig,对url和开关状态进行管理,并实现通过application.properties对配置的初始值进行设置.
首先,为了利用spring的DI,XRayFilterCofig需要有一个接口以及相应的实现
public interface XRayFilterConfig {
/**
* @return
*/
Set getUrlPatterns();
/**
* @param enabled
*/
void setFilterEnabled(boolean enabled);
/**
* @return
*/
boolean isFilterEnabled();
}
/**
* 对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 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;
}
}
在social-network-serverless-web模块中修改application.properties文件,设置url和开关的初始值。
xray.url=/user/create,\
/xray/switch
xray.filter.enabled=true
/**
* 对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();
}
}
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,而是使用FilterRegistrationBean对XRayFilter的bran进行注册,是为了给XRayFilter设置urlPattern
@Configuration
public class XRayConfig {
@Autowired
private XRayFilterConfig filterConfig;
@Bean
public FilterRegistrationBean xRayFilter() {
FilterRegistrationBean 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中的方法。
以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
使用AWSXRayServletFilter和xray中提供的spring相关的annotation来实现子分段创建.
首先将在social-network-serverless-web子模块的src/main/resources目录下的xray-aop.xml文件中的切面配置注释掉.
在social-network-serverless父模块中添加spring-data-jpa的版本管理
org.springframework.data
spring-data-jpa
2.2.6.RELEASE
在social-network-serverless-aws的pom文件中添加aws-xray-recorder-sdk-spring和spring-data-jpa的依赖.
com.amazonaws
aws-xray-recorder-sdk-spring
org.springframework.data
spring-data-jpa
因为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的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