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 进行的调用的详细信息。

5. spring boot集成AWS XRay实现代码性能分析_第1张图片

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


    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

在aws子模块中添加xray,servlet,spring相关的依赖

创建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
        
    

创建XRayFilter对需要进行监控的请求进行过滤

对filter主要有两点考虑:

  • 一是可以对请求的URL进行过滤,实现只对特定的url进行监控
  • 二是有一个监控的开关,只有当开关打开时才进行监控,开关关闭时则不需要进行监控

XRayFilterCofig

为了达到上述目的,可以创建一个XRayFilterCofig,对url和开关状态进行管理,并实现通过application.properties对配置的初始值进行设置.

首先,为了利用spring的DI,XRayFilterCofig需要有一个接口以及相应的实现

XRayFilterCofig 接口

public interface XRayFilterConfig {
    /**
     * @return
     */
    Set 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 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 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。

5. spring boot集成AWS XRay实现代码性能分析_第2张图片

打开aws xray的页面https://ap-southeast-1.console.aws.amazon.com/xray/home?region=ap-southeast-1#/traces ,在搜索框中输入trace id进行搜索,即可看到对应的结果。注意搜索的时候控制台的分区与本地启动xray dameon的分区保持一致,这里用的都是ap-southeast-1.

5. spring boot集成AWS XRay实现代码性能分析_第3张图片

尝试发送一个get user的请求,在response header中可以看到没有X-Amzn-Trace-Id的header信息,即并没有记录该请求的访问时间.

5. spring boot集成AWS XRay实现代码性能分析_第4张图片

尝试发送一个get xray filter enable状态的请求,http://127.0.0.1:8080/xray/enabled, 在response header中可以看到没有X-Amzn-Trace-Id的header信息,即并没有记录该请求的访问时间,且请求返回值为true。

5. spring boot集成AWS XRay实现代码性能分析_第5张图片

尝试发送一个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的切点,所以只有一个主分段,并没有子分段信息.

5. spring boot集成AWS XRay实现代码性能分析_第6张图片

5. spring boot集成AWS XRay实现代码性能分析_第7张图片

尝试发送一个get xray filter enable状态的请求,http://127.0.0.1:8080/xray/enabled, 在response header中可以看到没有X-Amzn-Trace-Id的header信息,即并没有记录该请求的访问时间,且请求返回值为false。

5. spring boot集成AWS XRay实现代码性能分析_第8张图片

尝试发送一个create user的请求,在response header中可以看到也没有X-Amzn-Trace-Id的header信息,因为filter已经关闭。

5. spring boot集成AWS XRay实现代码性能分析_第9张图片

项目地址

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的版本管理


    org.springframework.data
    spring-data-jpa
    2.2.6.RELEASE

修改子模块的pom文件添加

在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的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)方法来进行修改,这里不再赘述.

5. spring boot集成AWS XRay实现代码性能分析_第10张图片

项目地址

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

你可能感兴趣的:(aws,spring,boot,aws,xray)