现在开源框架采用的采样策略一般是采用类header向下传递采样标记,这种情况下有以下几个问题。
1、当中间链路节点出现需要采样的情况时,req header向后传递采样标志,rsp header向前传递采样标志。这种在异步调用情况下,rsp header向前传递会失效。
2、采用这种标记的只能在单条链路上有效,异常节点之前出现分叉链路情况的时候,分叉的另外链路无法打上采样标记。
使用flink对链路进行采样,可以根据traceId作为key,采用sessionTime Windows作为窗口,保证单个链路数据的span能在sessionTime内搜集到flink的source源中。
基于flink的窗口:
1、单某个节点的数据符合采样的需求,将这个窗口的链路数据置为采样,并将这个异常节点放到窗口的异常列表中(单独存储,便于搜索)。
2、从整个链路中获取头节点(链路的搜索一般是搜索头结点的数据,比如api请求发起的整个链路。
当sessionTime超过预设的时间,我们可以假定整体链路已经处理完毕,窗口进入处理方法中,我们可以将以上分类的数据发送到sink(kafka producer)中。
后续的处理节点可以将分类数据中的首节点和异常节点放入es中,便于对关键字段进行搜索;全部数据存储到hbase中,通过traceid生成rowkey,spanid和节点的server/client端生成列族id。从es中搜索到traceid,然后通过traceid从hbase中搜索到全链路的数据。
实现中需要解决的问题:
1、traceid除了在同一链路中,其他时间段是不会重复出现的,通过traceid作为flink的窗口的key,当这个链路处理完成之后,flink的窗口状态不会清空,当flink运行一段时间之后,会导致堆内存耗尽,需要增加自定义的trigger实现,processfunction处理完成之后抛弃traceid对应的状态。
public static Trigger<Span, TimeWindow> getTrigger() {
return new Trigger<Span, TimeWindow>() {
private int flag = 0;
@Override
public TriggerResult onElement(Span span, long l, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
triggerContext.registerProcessingTimeTimer(timeWindow.maxTimestamp());
if(flag > 99){
flag = 0;
return TriggerResult.FIRE_AND_PURGE;
}else{
flag++;
}
return TriggerResult.CONTINUE;
}
@Override
public TriggerResult onProcessingTime(long l, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
return TriggerResult.FIRE_AND_PURGE;
}
...
};
}
2、依赖链路收集具有较好的实时性,flink的source源需尽量保证通traceid的所有span链路数据能在一段可接受的sessionTime内能被收集起来。
3、链路数据qps很高,需要的flink资源会比较多,推荐数据源能保证数据的时间顺序,这样flink使用eventTime能将内存的使用维持在一个比较平稳的状态。