前言
全链路压测时一项系统性工程,包含数据工厂,影子环境,压测脚本,数据偏移,压测平台,链路路由等等
本文重点介绍其中一环,业务应用代码增强
原理
- 红色表示压测流量
- 黑色表示业务流量
- 红色的流量由压测平台[比如jmeter压测集群]发起
- http发送请求需要在请求头设置一个标记,此标记可以被skywalking识别,后skywalking在TracerContext中标记该请求为压测请求[俗称流量染色]
- 同时该标记能够从应用一透传到应用二透传到应用三
- 在写入db时,根据流量染色情况进行路由,业务流量写入正常db,压测流量写入影子库
- redis,es等也是同理,实现上存储层如db都是同一台物理节点,逻辑存储位置不同,比如同一个mysql,其业务db是biz_db,则影子库是shadow_biz_db
- mq影子流量和业务流量共用topic
基于skywalking的压测实现
本文重点介绍基于skywalking的影子传播机制与存储如何落入影子库表
数据容器改造
- 除去skywalking源码后,新增压测标记字段
public class TraceSegment {
/**
* 压测标:表明当前链路是业务流量还是压测流量
*/
private boolean pressureTest ;
}
public class ContextCarrier implements Serializable {
/**
* 全链路压测标志
*/
private boolean pressureTest;
}
public class ContextSnapshot {
/**
* 全链路压测标志
*/
private boolean pressureTest;
}
对Segment的序列化反序列化改造
public class TracingContext implements AbstractTracerContext {
@Override
public void inject(ContextCarrier carrier) {
...... 删除skywalking源码
跨进程级别的压测标传递
carrier.setPressureTest(segment.isPressureTest());
...... 删除skywalking源码
}
@Override
public void extract(ContextCarrier carrier) {
...... 删除skywalking源码
跨进程级别的压测标志注入
this.segment.setPressureTest(carrier.isPressureTest());
}
@Override
public ContextSnapshot capture() {
...... 删除skywalking源码
跨线程级别的压测标志注入
snapshot.setPressureTest(segment.isPressureTest());
return snapshot;
}
@Override
public void continued(ContextSnapshot snapshot) {
...... 删除skywalking源码
this.segment.setPressureTest(snapshot.isPressureTest());
}
}
说明
- 基于上述方案后Segment如果有pressureTest压测标记
- 则跨进程时先序列化成ContextCarrier,进入下一进程在转为Segment时,能够完成pressureTest压测标记跨进程传递
- ContextSnapshot同理完成跨线程传递
染色一压测流量识别
- http请求时,获取其请求头pressureTest标记,给Segment进行染色
- 标志当前流量是压测流量还是业务流量
- 注意该请求头只有压测流量携带,业务流量不可以出现,否则造成干扰
public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor {
@Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
...... 删除skywalking源码
压测流量识别 染色
String pressureTest = request.getHeader("pressureTest");
if(!StringUtil.isEmpty(pressureTest)){
Boolean isPressureTest = Boolean.FALSE;
try {
isPressureTest = Boolean.valueOf(pressureTest);
} catch (Exception e) {
}
TracingContext abstractTracerContext = (TracingContext) ContextManager.get();
TraceSegment segment = abstractTracerContext.getSegment();
segment.setPressureTest(isPressureTest);
}
}
}
小结
- 通过对skywalking的改造和tomcat流量的改造
- 我们的流量具有了识别染色能力和传递压测标能力
- 则当流量进入http和dubbo以及mq到达jdbc时,压测流量的pressureTest始终是true,业务流量始终是false
核心一影子路由
- 以下为mysql8.x的jdbc插件影子路由实现
- 当我们发现是压测流量时,修改业务库为压测库,在库名前增强"shadow_’
- 此时,业务流量写入db.影子流量写入shadow_db
- 影子库需要和业务库在同一台db实例上,影子库需提前创建,表与业务库相同
public class StatementCreateInterceptor implements InstanceConstructorInterceptor {
@Override
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
TracingContext abstractTracerContext = (TracingContext) ContextManager.get();
boolean pressureTest = abstractTracerContext.getSegment().isPressureTest();
if(pressureTest) {
if (objInst instanceof ClientPreparedStatement) {
第三个参数
allArguments[2] = "shadow_" + allArguments[2];
((ClientPreparedStatement) objInst).setCurrentCatalog((String) allArguments[2]);
}
if (objInst instanceof StatementImpl) {
第二个参数
allArguments[1] = "shadow_" + allArguments[1];
((StatementImpl) objInst).setCurrentCatalog((String) allArguments[1]);
}
}
}
}
总结
- 综上,我们完成了流量染色,链路携带染色标记,基于染色标记完成影子库写入,从而实现了全链路压测中最重要的一环,业务应用字节码增强改造
- 需要注意,实际环境要覆盖所有的技术组件,比如redis,es等等
- 还需要考虑类似定时任务等各种业务场景的处理,安全问题等等
- 本文重点在于引路,如读者有兴趣可以自行专研其他组件压测实现[如有疑问可留言]