1、固定采样(sampler.type=const)sampler.param=1 全采样, sampler.param=0 不采样。
2、按百分比采样(sampler.type=probabilistic)sampler.param=0.1 则随机采十分之一的样本。
3、采样速度限制(sampler.type=ratelimiting)sampler.param=2.0 每秒采样两个 traces。
4、动态获取采样率 (sampler.type=remote) 此策略为默认配置,可以通过配置从 Agent 中获取采样率的动态设置。
5、自适应采样(Adaptive Sampling)开发计划中。
目前,在 Jaeger V1.27.0 版本中开始支持自适应采样模式。基于此模式,在 Jaeger 收集器中,通过观察从服务接收到的跨度并重新计算每个服务/端点组合的采样概率,以确保收集的跟踪量与 --sampling.target-samples-per-second 匹配。当检测到新服务或端点时,最初会使用 --sampling.initial-sampling-probability 对其进行采样,直到收集到足够的数据来计算适合通过端点的流量的速率。
自适应采样需要一个存储后端来存储观察到的流量数据和计算的概率。目前支持内存(用于一体式部署)和 Cassandra 作为采样存储后端。据官网所述,其正在开发以实现对其他后端的支持(问题跟踪)。
在分布式跟踪中,经常使用“采样”模型来减少后端收集和存储的跟踪数量,这通常是可取的,因为它很容易产生比有效存储和查询更多的数据。毕竟,采样允许我们只存储所产生的总轨迹的一个子集。
传统上,Jaeger SDK 支持多种采样技术。但最具有革新性的便是所谓的远程采样,这是 Jaeger 项目在开源中率先推出的一项功能。在此设置中,Jaeger SDK 将查询 Jaeger 后端以检索给定服务的采样规则配置,直至单个端点的粒度。这可能是一种非常强大的采样方法,因为它可以让操作员集中控制整个组织的采样率。
直到最近,在远程采样模式下控制后端返回那些采样规则的唯一方法是使用通过 --sampling.strategies-file 标志提供给收集器的配置文件。通常,运营商必须手动更新此文件以推出不同的采样规则。V1.27.0 中添加的自适应采样允许收集器通过观察系统中的当前流量和收集的跟踪数量来自动调整采样率以满足预先配置的目标。此功能已在 Uber 生产多年,最终在 Jaeger 的开源版本上可用。
自适应采样的革新
为什么我们需要远程和自适应采样?
始终可以将 SDK 配置为应用非常简单的采样策略,例如掷硬币决策,也称为概率采样。这在小型应用程序中可能工作得很好,但是当您的架构以 100 甚至 1000 个服务来衡量时,这些服务都具有不同的流量,每个服务的单个采样概率并不能很好地工作,并且为每个服务单独配置它服务是部署的噩梦。远程采样通过将所有采样配置集中在 Jaeger 收集器中解决了这个问题,其中可以将更改快速推送到任何服务。
但是,手动为每个服务配置采样规则,即使集中配置,仍然非常繁琐。自适应采样更进一步,并将其转换为声明式配置,其中操作员只需设置跟踪收集的目标速率,自适应采样引擎会为每个服务和每个端点动态调整采样率。
自适应采样的另一个好处是它可以自动对流量的变化做出反应。许多在线服务在白天表现出流量波动,例如 Uber 在高峰时段会有更多的请求。自适应采样引擎会自动调整采样率,以保持跟踪数据量稳定并在我们的采样预算范围内。
自适应采样原理
那么,自适应采样到底是如何工作的呢?我们来看一下。我们从分配给每个端点的一些默认采样概率 p 和我们想要收集的跟踪的目标速率 R 开始,例如每个端点每秒 1 个跟踪。收集器监视通过它们的跨度,寻找以该采样策略开始的迹线的根跨度,并计算被收集的迹线 R' 的实际速率。如果 R' > R,那么我们当前对该端点的概率太高,需要降低。相反,如果 R' < R 那么我们需要增加概率。由于实际流量总是有点嘈杂,很少出现R'==R的情况,所以采集器采用一定的容忍阈值k,使得上述规则实际上是R' > R k 且R' < R - k .一旦计算出新的概率 p',收集器会等待一定的时间间隔,以确保它被 SDK 检索并应用于新的跟踪,然后观察速率 R' 的新值并重复循环。 Yuri Shkuro 的《Mastering Distributed Tracing》一书更详细地描述了 Jaeger 收集器中实现的自适应概率计算所涉及的数学。
鉴于 Jaeger 允许我们同时运行多个收集器,我们还需要讨论这一切是如何完成的。自适应采样模块使用存储后端支持的比较和交换操作实现简单的领导者选举机制。每个收集器从服务接收不同的跨度流,并为每个服务/端点对维护内存中的跟踪计数聚合。然后在一定的时间间隔后,每个收集器将这些数据(在代码中称为吞吐量)写入存储后端。然后赢得领导选举的收集器从存储中读取给定时间范围内的所有吞吐量数据,聚合它,执行概率计算,并将所有服务的新概率摘要写回存储。其他收集器加载该摘要并使用它来处理来自 SDK 的采样策略请求。
需要注意的是:此模型中的领导者选举纯粹是一种优化,因为采样摘要是在所有收集器都知道的基于时间的稳定键下编写的,因此如果多个收集器碰巧执行概率计算,它们将直接覆盖彼此使用相同的数据写入。
Jaeger 自适应采样引擎架构
设置自适应采样
自适应采样要求 Jaeger SDK 向后端请求远程采样文档,我们可以通过环境变量进行配置。在进行配置之前,大家可以参阅客户端功能文档以确认所部署的 Jaeger 客户端是否支持此项功能。相关配置模版如下所示:
代码语言:javascript复制JAEGER_SAMPLER_TYPE=remote
JAEGER_SAMPLING_ENDPOINT=<sampling endpoint on the jaeger agent>
通常情况下,默认设置为与本地 Jaeger 代理一起使用,作为主机代理或 Sidecar 运行,Jaeger SDK 配置实际上默认为:
代码语言:javascript复制JAEGER_SAMPLER_TYPE=remote
JAEGER_SAMPLING_ENDPOINT=http://127.0.0.1:5778/sampling
配置客户端后,我们需要确保正确配置收集器以存储自适应采样信息。目前,Jaeger 使用与跨度存储相同的存储进行自适应采样,并且唯一支持的自适应采样存储选项是 Cassandra(自 V1.27 起)和内存(自 V1.28 起)。使用环境变量配置的收集器可参考如下参数:
代码语言:javascript复制SPAN_STORAGE_TYPE=cassandra
SAMPLING_CONFIG_TYPE=adaptive
接下来,我们来看一下简单的 jaeger-docker-compose.yml 示例,该 Demo 以支持自适应采样的配置启动 Jaeger。具体如下所示:
代码语言:javascript复制[leonli@192 ~] % less jaeger-docker-compose.yml
version: '2'
services:
hotrod:
image: jaegertracing/example-hotrod:latest
ports:
- '8080:8080'
- '8083:8083'
command: ["-m","prometheus","all"]
environment:
- JAEGER_AGENT_HOST=jaeger-agent
- JAEGER_AGENT_PORT=6831
- JAEGER_SAMPLER_TYPE=remote
- JAEGER_SAMPLING_ENDPOINT=http://jaeger-agent:5778/sampling
depends_on:
- jaeger-agent
jaeger-collector:
image: jaegertracing/jaeger-collector
command:
- "--cassandra.keyspace=jaeger_v1_dc1"
- "--cassandra.servers=cassandra"
- "--collector.zipkin.host-port=9411"
- "--sampling.initial-sampling-probability=.5"
- "--sampling.target-samples-per-second=.01"
environment:
- SAMPLING_CONFIG_TYPE=adaptive
ports:
- "14269:14269"
- "14268:14268"
- "14250"
- "9411:9411"
restart: on-failure
depends_on:
- cassandra-schema
jaeger-query:
image: jaegertracing/jaeger-query
command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra"]
ports:
- "16686:16686"
- "16687"
restart: on-failure
depends_on:
- cassandra-schema
jaeger-agent:
image: jaegertracing/jaeger-agent
command: ["--reporter.grpc.host-port=jaeger-collector:14250"]
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
restart: on-failure
depends_on:
- jaeger-collector
cassandra:
image: cassandra:4.0
cassandra-schema:
image: jaegertracing/jaeger-cassandra-schema
depends_on:
- cassandra
当然,自适应采样算法可以使用一些官方给定的相关参数来定义基于当前的业务场景需求,我们可以通过 “help” 命令进行自定义配置及启动,具体如下所示:
代码语言:javascript复制[leonli@192 ~] % docker run --rm
-e SAMPLING_CONFIG_TYPE=adaptive
jaegertracing/jaeger-collector:1.30
help | grep -e '--sampling.'
--sampling.aggregation-buckets int 要保存在内存中的历史数据量。(默认 10)
--sampling.buckets-for-calculation int 这决定了在计算加权 QPS 时使用了多少先前的数据,即。如果 BucketsForCalculation 为 1,则仅最新数据将用于计算加权 QPS。(默认 1)
--sampling.calculation-interval duration 计算新采样概率的频率。建议大于客户端的轮询间隔。(默认 1m0s)
--sampling.delay 持续时间确定最近的状态有多远。如果您想为聚合完成添加一些缓冲时间,请使用此选项。(默认 2m0s)
--sampling.delta-tolerance float 每秒观察到的样本与每秒所需(目标)样本之间可接受的偏差量,以比率表示。(默认 0.3)
--sampling.follower-lease-refresh-interval duration 如果此处理器是跟随者,则休眠的持续时间。(默认 1m0s)
--sampling.initial-sampling-probability float 所有新操作的初始采样概率。(默认 0.001)
--sampling.leader-lease-refresh-interval duration 如果此处理器被选为领导者,则在尝试更新领导者锁的租约之前休眠的持续时间。这应该小于 follower-lease-refresh-interval 以减少锁抖动。(默认 5s)
--sampling.min-samples-per-second float 每秒采样的最小跟踪数。(默认 0.016666666666666666)
--sampling.min-sampling-probability float 所有操作的最小采样概率。(默认 1e-05)
--sampling.target-samples-per-second float 每个操作的全局目标采样率。(默认 1)
其实,在实际的业务场景中,我们往往期望有一些功能可以使自适应采样效果更好。其一是能够计算跨度总数而非跟踪总数,不同的端点可能导致非常不同的迹线大小,甚至相差几个数量级。然而,当前的实现仅围绕跟踪计数而构建。它可以通过其他启发式方法进行扩展,例如离线计算每个端点的平均跟踪大小,并为自适应采样引擎提供权重矩阵,以便在计算实际吞吐量时加以考虑。
另一个不错的功能,实际上需要更改远程采样配置,是使用来自跟踪数据的其他维度,除了当前在模式中硬编码的服务名称和端点名称。
除此之外,还有一种配置机制,允许覆盖特定服务/端点的目标吞吐率 R,而不是使用单个全局参数,因为某些服务可能对我们的业务更重要,我们可能希望收集更多数据用于他们,或者可能是因一些查看而临时设置。
作为 Jaeger 社区发布第一个开源端到端的自适应采样实现版本,我们期望在后续的版本中,能够在以下几个方面获得改进:
1、支持 ElasticSearch / OpenSearch 作为存储自适应采样数据的后端。
2、解耦 Jaeger 存储配置,以便不同的存储后端可用于跨度存储和自适应采样。