通过kamon.trace.sampler
可以配置一个采样器,这可以决定哪些 span 应该被发送到 span 报告器。采样器类型有几个可能的值,分别是:
ConstantSampler
类ConstantSampler
类RandomSampler
类,默认概率为 1%AdaptiveSampler
类kamon.trace.Sampler
接口的类的全类名,如果类加载失败则回退到概率为 10% 的 random 采样器虽然上面有多种采样器,但其实 kamon 中的采样器只有三类:
ThreadLocalRandom.current().nextLong()
产生随机数,如果随机数是落在一个区间内(下界_lowerBoundary
,上界_upperBoundary
),则需要采样,否则不采样。判断操作是否应该被采样,下文均使用 判定采样 一词,判定成功,采样。判定失败,不采样。
不采样的决定仍然会产生可以收集指标并与上下文一起传播的 span,它们只是不会被发送到span 报告者那里。
通常,如果需要的是抽样采样而不是全量采样,此时我们需要使用 random 采样器或 adaptive 采样器,甚至自己定义一个。默认情况下,kamon 默认提供的是 adaptive,并且总的最大吞吐量kamon.trace.adaptive-sampler.throughput
默认是 600。采样器将尽最大努力平衡采样,以产生不超过此数量的采样。
此外kamon还提供了更详细的配置以更细粒度决定采样,下面是如何配置一组操作的吞吐量范围:
kamon.trace.groups.groupName.minimum-throughput: [number]
定义了组内每个操作每分钟的最小采样次数。尽管采样器会尽力提供最小的采样次数,但实际的最小次数会因应用流量和总体吞吐量目标的不同而不同。kamon.trace.groups.groupName.maximum-throughput: [number]
定义了组内每个操作每分钟预期的最大采样次数,而不管在达到全局吞吐量目标之前是否有剩余空间。组 是指描述了一组命名的操作和适用于它们的规则。如果 kamon 启用的是 always 或 never 采样器,则组配置会被忽略。
例如,如果想确保一个操作永远不会被采样,比如健康检查,可以这样配置:
groups {
health-checks {
operations = ["GET \/status"]
rules {
sample = never
}
}
使用kamon.trace.random-sampler.probability
配置采样的概率。必须是一个介于0和1之间的值,用途如下。
val _upperBoundary = Long.MaxValue * probability
val _lowerBoundary = -_upperBoundary
如果配置的是 0.01,那么最终区间就是:[-0.01 * Long.MaxValue, 0.01 * Long.MaxValue]
最后来看看随机采样的实现,核心代码如下:
def decide(): SamplingDecision = {
_decisions.increment()
// 伪随机数
val random = ThreadLocalRandom.current().nextLong()
if (random >= _lowerBoundary && random <= _upperBoundary)
SamplingDecision.Sample // 意味着会采样
else
SamplingDecision.DoNotSample // 意味着不会采样
}
简而言之,随机采样器确实是仅随机的,它只根据伪随机数是否在区间来判定采样。
decide
是采样器Sampler
特质的唯一一个公开方法,在需要判断是否应该采样时被调用。SamplingDecision
是一个采样判定结果,比如子类DoNotSample
就表示某个 trace 的所有 span 都不应该被捕获或报告。
自适应采样器的实现稍微复杂一点,先了解了随机采样,更方便后面理解自适应采样器。
因为采样器不会超出最大吞吐量配置,但是可能存在一种相反的特殊情况:吞吐量离预设值还有一段距离。
而自适应采样器便能实现对较低吞吐量的采样器进行补偿,补偿策略便是基于操作的历史平均吞吐量、当前吞吐量、rules 配置的该操作最大吞吐量,动态地将所有采样器的采样率调整到接近设定的目标吞吐量。
因此自适应采样器的实现稍微复杂一点,它还分为 rebalancing(再平衡) 和 adapting(适应) 两个阶段,
历史记录是指:计算在过去60个区间内做出了多少个采样决定。如果自采样器创建以来的时间少于60个间隔,则使用可用值的平均值来填补空白。这种特殊的逻辑用于在每个单独操作的启动过程中使之顺利进行。
rebalancing 阶段发生在有新操作时(判定采样时),判定操作是否有采样器,有则直接取,并获得判定,否则构造采样器,并获得判定。最终返回判定,调用方就知道是否应该采样了。
操作采样器使用内部状态来决定是否应该对一个特定的操作进行采样。 这里说的采样器具体是指
OperationSampler
及其子类,而不是上面的Sampler
。
rebalancing 阶段的主要逻辑:
Sample
,否则就是 DoNotSample
adapting 阶段是一个后台定时任务(每秒执行一次)在执行,使用所有操作的随机采样器信息和历史采样信息来更新它们的采样概率,如果有剩余的吞吐量,还可以选择提升操作。
当前操作的吞吐量如果比历史记录平均值和该操作配置的最大吞吐量小,那么就可以尝试提升。
adapting 阶段的主要逻辑:
提升就是修改吞吐量,并得到 新吞吐量与平均吞吐量的占比 ,即为新的概率,该概率被用于与上界相乘,如果最后上界增大了,那么随后随机数就会更大概率落在区间内。
与随机采样器一样,如果计算出的 占比 的是 0.01,那么最终区间就是:[-0.01 * Long.MaxValue, 0.01 * Long.MaxValue]
由于 adapting 阶段是一种自适应算法,会自动根据历史数据计算出最合适的吞吐量,所以我们只能配置全局最大吞吐量,和每个操作的最大吞吐量。(有些接口可能不需要太详细精确的 trace,就可以降低吞吐量(采样率))
kamon.trace.Identifier.Factory#generate
ThreadLocalRandom.current().nextLong()