写点什么

一个简单的单体服务流量标记 demo

作者:zuozewei
  • 2021 年 12 月 21 日
  • 本文字数:3103 字

    阅读完需:约 10 分钟

一个简单的单体服务流量标记demo

一、概念

在全链路压测中生成流量后,实际业务中需要区分流量(正常流量 & 压测流量),我们称之为链路打标,也可以叫做流量标记,而一般对外的接口都是使用 http 的方式暴露的,http 是一个比较通用的协议,一般我们会通过 header 的增加一个标记项。例如 key 是 “flag”,value 是你需要携带的数据,可以是普通的字符串,也可以 json 串,但是要注意控制 value 的长度,因为中间件有限制 header 的长度。


在向下游服务发起请求时,如果是压测流量把 header 头中的标记字段往下透传,下游继续在业务中往下透传,接收到如果是压测流量,就使用相应的压测数据。


我们知道目前微服务架构中「分布式跟踪系统」作为基础设施,不会限制「使用线程池等会池化复用线程的组件」,并期望对业务逻辑尽可能的透明。从技术能力上讲,「全链路压测」 与 「分布式跟踪系统」 是一样的,即链路打标。目前市面上,几乎所有分布式链路跟踪的实现,理论基础都是基于 Google Dapper 的那篇论文,其中最重要的核心概念就是 traceIdspanId


二、设计方案

我们这里演示的 demo 很简单,主要就是使用自定义拦截器和 logback 日志自定义格式化跟踪:


  • 首先流量标记在客户端上生成,在请求业务接口的时候传递给服务端;

  • 然后服务端进行拦截,在请求真正的接口前获取 header 中传递的标记,并存储在 ThreadLocal 中,做为请求线程共享的局部变量;

  • 在请求结束的时候需要手动调用 remove() 方法清除 Map 中的标记条目,避免内存泄漏,标记随着时间推移会有很多;

  • 最后借助 logback 实现自定义日志打印跟踪(线上需要做到日志隔离)。


三、demo 实现

1、开发环境

  • IDEA 2020.10

  • Maven 3.6.2

  • SpringBoot 2.2.0

2、构建项目

新建一个 SpringBoot 工程,并引包:


 <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
复制代码

3、自定义拦截器

创建 FlagTrackInterceptor 拦截器类:


/** * @Description: 标记追踪拦截器 * @Param: * @return: * @Author: zuozewei * @Date: 2021/3/15 */
@Componentpublic class FlagTrackInterceptor implements HandlerInterceptor { /** * 存储 flag */ private static final ThreadLocal<String> FLAG_THREAD_LOCAL = new ThreadLocal<>();
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /** * 获取请求头 header 中传递的 flag,若没有,则 UUID 代替 */ String flag = Optional.ofNullable(request.getHeader("flag")).orElse(UUID.randomUUID().toString().replaceAll("-","")); // 请求前设置 FLAG_THREAD_LOCAL.set(flag); return true; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 移除,防止内存泄漏 FLAG_THREAD_LOCAL.remove(); }
public static String getFlag() { return FLAG_THREAD_LOCAL.get(); }
public static void setFlag(String flag){ FLAG_THREAD_LOCAL.set(flag); }
}
复制代码


这里处理 flag,通过 ThreadLocal 使用,可以保证每个请求都拥有唯一的一个流量标记。

4、注册自定义拦截器

新建 CustomInterceptorConfig 自定义拦截器注册配置类:


** * @Description: 注册自定义拦截器 * @Param: * @return: * @Author: zuozewei * @Date: 2021/3/26 */
@Configurationpublic class CustomInterceptorConfig implements WebMvcConfigurer {
@Autowired private FlagTrackInterceptor flagTrackInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(flagTrackInterceptor); }}
复制代码

5、自定义 logback 日志格式化

新建 FlagPatternConverter 自定义日志格式化类:


/** * @Description:  自定义日志格式化 * @Param: * @return: * @Author: zuozewei * @Date: 2021/3/26 * */
public class FlagPatternConverter extends ClassicConverter { @Override public String convert(ILoggingEvent iLoggingEvent) { String flag = FlagTrackInterceptor.getFlag(); return StringUtils.isEmpty(flag) ? "flag" : flag; }}
复制代码


需要继承 ClassicConverter 类,并重写 convert 方法,继承 PatternLayout,加入自己要自定义的内容,对于全链路压测,需要加入的是流量标记。

6、控制器

新建 FlagTrackController 控制器类:


/** * @Description:  测试日志追踪 * @Param: * @return: * @Author: zuozewei * @Date: 2021/3/26 */
@Slf4j@RestController@RequestMapping("/test")public class FlagTrackController {
@GetMapping("/log") public String flagTrack(){ log.info("-----> 测试 info <-----"); log.warn("-----> 测试 warn <-----"); log.error("-----> 测试 error <-----"); return null; }
}
复制代码

7、logback 日志配置文件

创建 logback 日志配置文件 logback-spring.xml


<?xml version="1.0" encoding="UTF-8"?><configuration>    <!-- 获取 flag 配置类-->    <conversionRule conversionWord="flag" converterClass="com.zuozewei.flagtrack.config.FlagPatternConverter" />    <!-- 自定义 logback 日志格式-->    <property name="CUSTOM_LOG_PATTERN"              value="[[[%date{yyyy-MM-dd HH:mm:ss} | %-5level | %flag | %thread | %file:%line | %logger : %.1000m]]]%n" />
<!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CUSTOM_LOG_PATTERN}</pattern> </encoder> </appender>
<root level="INFO"> <!-- 控制台打印 --> <appender-ref ref="STDOUT"/> </root></configuration>
复制代码


最后,我们需要在 logback.xml 里面配置一下日志的 pattern 和 PatternLayout

8、项目结构

最终的项目结构如下:


四、测试

1、正常流量

通过 Postman 模拟请求:http://localhost:8080/test/log(header 中不添加 flag )



控制台输出日志结果如下:


2、流量标记

通过 Postman 模拟请求:http://localhost:8080/test/log(header 中添加标记 「flag:7d-test」 )



控制台输出日志结果如下:


五、小结

简单基于 SpringBoot,使用拦截器及自定义日志演示一个简单的单体服务流量标记方案。如果接口内部存在多线程异步调用,这时用上面提供的方案的流量标记还会有效吗?如果不能实现真实的链路传递,那么又该如何实现呢?


源码地址:


发布于: 53 分钟前阅读数: 7
用户头像

zuozewei

关注

测试及性能领域创作者 2017.12.23 加入

「7DGroup」技术公众号作者,CSDN博客专家、测试领域优质创作者,华为云·云享专家,极客时间《全链路压测实战30讲》专栏作者之一,极客时间《性能测试实战30讲》、《高楼的性能工程实战课》专栏编委。

评论

发布
暂无评论
一个简单的单体服务流量标记demo