目标
skywalking 默认情况会采集大量 trace 数据,这样可以比较全的追踪所有请求调用链路的请求,但同时对 ES 存储资源要求非常高,需要我们投入很大的存储节点才可以。那么有没有一种采样的请求上报的机制呢?答案是有的,通过设置采样数据的比例,我们就可以在资源成本和采集条目之前取得一个平衡。
现状
日常排查问题的现实情况往往是,我们只需要能采集到出现问题的请求链路即可,而不需要能够采集所有请求的链路数据,在加了采样条目设置后,有一部分 trace 数据会被丢掉,当然如果正好丢掉了我们出问题的 trace 请求,那么就需要我们稳定复现请求,从而控制概率采集到该问题数据。
skywalking 里面配置采样条目
在 skwalking 的 agent 目录下的 agent.conf 文件有采样参数的设置 sample_n_per_3_secs,默认情况下该值 是 0 或者 -1 的情况下是关闭采样功能,如果大于 0,则代表agent 3 秒内采集多少条数据上报 oap
agent.sample_n_per_3_secs=${SW_AGENT_SAMPLE:40}
注意:该值的修改需要每次进行 agent 打包后生效
采样功能的原理分析
SamplingService 类里面的 handleSamplingRateChanged 方案,会启动一个线程,每隔 3 秒定时重置采样值的计数器:
void handleSamplingRateChanged() {if (getSamplingRate() > 0) {if (!on) {on = true;this.resetSamplingFactor();ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(new DefaultNamedThreadFactory("SamplingService"));scheduledFuture = service.scheduleAtFixedRate(new RunnableWithExceptionProtection(this::resetSamplingFactor, t -> LOGGER.error("unexpected exception.", t)), 0, 3, TimeUnit.SECONDS);LOGGER.debug("Agent sampling mechanism started. Sample {} traces in 3 seconds.",getSamplingRate());}} else {if (on) {if (scheduledFuture != null) {scheduledFuture.cancel(true);}on = false;}}
}
计数器采用 java 并发包下面的原子类计数,从而确保多线程环境下该值的并发更新问题:
private void resetSamplingFactor() {samplingFactorHolder = new AtomicInteger(0);}
然后提供了一个方法,用于判断是否到达采样阈值:
public boolean trySampling(String operationName) {if (on) {int factor = samplingFactorHolder.get();if (factor < getSamplingRate()) {return samplingFactorHolder.compareAndSet(factor, factor + 1);} else {return false;}}return true;}
在这个方法里面可以看到,如果原子类 AtomicInteger 实例的 get 方法的值小于阈值,然后就进行一次 CAS 更新操作,当 CAS 成功时代表该 trace context 数据允许上报 oap,否则就代表达到了采样阈值,该 trace context 数据丢弃。
上报还是不上报的逻辑在 ContextManagerExtendService 类的 createTraceContext 方法中可以找到:
public AbstractTracerContext createTraceContext(String operationName, boolean forceSampling) {AbstractTracerContext context;/** Don't trace anything if the backend is not available.*/if (!Config.Agent.KEEP_TRACING && GRPCChannelStatus.DISCONNECT.equals(status)) {return new IgnoredTracerContext();}int suffixIdx = operationName.lastIndexOf(".");if (suffixIdx > -1 && Arrays.stream(ignoreSuffixArray).anyMatch(a -> a.equals(operationName.substring(suffixIdx)))) {context = new IgnoredTracerContext();} else {SamplingService samplingService = ServiceManager.INSTANCE.findService(SamplingService.class);//如果该条打上了强制采样标签 或 满足满足采样条件就可以直接上报 oapif (forceSampling || samplingService.trySampling(operationName)) {context = new TracingContext(operationName);} else {//否则就忽略该次 trace context, 不做任何处理context = new IgnoredTracerContext();}}return context;}
总结
通过合理的 skywalking 的采样机制的设置,可以大大减轻服务端 ES 的存储压力,以及减少 agent 端消耗应用的的 cpu 和内存的资源,也包括减少上报 oap 网络带宽的占用等,从而达到在资源成本和采集请求覆盖面得到一个平衡
参考
Table of Agent Configuration Properties | Apache SkyWalking