Spring各个组件

Jaeger Tracing 采样算法分析

先看接口

public interface Sampler {
  /**
   * @param operation The operation name set on the span
   * @param id The traceId on the span
   * @return whether or not the new trace should be sampled
   */
  SamplingStatus sample(String operation, long id);

  /**
   * Release any resources used by the sampler.
   */
  void close();
}

两个方法,注释说的很清楚了,不废话。close 基本都是空实现,因为实在是没有用到啥需要关闭的资源。


官方6个实现类,一个一个来


ConstSampler


●一句话:要么全采样,要么不采样

  public static final String TYPE = "const";
  public ConstSampler(boolean decision) {
    this.decision = decision;
    ...
  }

构造 ConstSampler 时需要传入 decision 参数

  public SamplingStatus sample(String operation, long id) {
    return SamplingStatus.of(decision, tags);
  }

sample 方法中直接就返回这个 decision 了。是个 boolean,所以要么全采样,要么不采样。只会在测试环境用到。


RateLimitingSampler


●基于漏桶( LeakyBucket )算法的采样器

  public static final String TYPE = "ratelimiting";
  public RateLimitingSampler(double maxTracesPerSecond) {
    this.maxTracesPerSecond = maxTracesPerSecond;
    double maxBalance = maxTracesPerSecond < 1.0 ? 1.0 : maxTracesPerSecond;
    this.rateLimiter = new RateLimiter(maxTracesPerSecond, maxBalance);
    ...
  }

一个参数 maxTracesPerSecond,每秒最大采样数

  public SamplingStatus sample(String operation, long id) {
    return SamplingStatus.of(this.rateLimiter.checkCredit(1.0), tags);
  }

sample 方法是否采样取决于漏桶满没满。
●RateLimiter 的实现

  private final double creditsPerNanosecond;
  private final Clock clock;
  private double balance;
  private double maxBalance;
  private long lastTick;
  public RateLimiter(double creditsPerSecond, double maxBalance, Clock clock) {
    this.clock = clock;
    this.balance = maxBalance;
    this.maxBalance = maxBalance;
    this.creditsPerNanosecond = creditsPerSecond / 1.0e9;
  }

参数说明:

-- clock 封装 System 时间操作

-- balance 桶中资源(后续分析中已水代替资源,便于理解)数量

-- maxBalance 桶的最大容量

-- creditsPerNanosecond 放行速率

-- lastTick 上次加水时间

桶最大容量就是每秒采样数,然后以纳秒级的速率均匀放过请求(这里说的均匀并不是说严格按照每 creditsPerNanosecond 纳秒放过一个采样请求,见下面分析)
这个漏桶有点奇怪,反而有点像令牌桶。反复往桶里加水,加到足够漏出一次才漏出。。另外注意这个 RateLimiter 不是线程安全的,Jaeger 之所以直接用,是因为他在 synchronized 修饰的方法里。如果想拿出来另做他用的话,需要注意。


ProbabilisticSampler


随机采样,每个请求都有一定的概率被采样,掷硬币

  public ProbabilisticSampler(double samplingRate) {
    if (samplingRate < 0.0 || samplingRate > 1.0) {
      throw new IllegalArgumentException(
          "The sampling rate must be greater than 0.0 and less than 1.0");
    }

    this.samplingRate = samplingRate;
    this.positiveSamplingBoundary = (long) (((1L << 63) - 1) * samplingRate);
    this.negativeSamplingBoundary = (long) ((1L << 63) * samplingRate);
    ...
  }

构造参数为采样率,正边界为最大 long * 采样率,负边界为最小 long * 采样率,有什么用?

  public SamplingStatus sample(String operation, long id) {
    if (id > 0) {
      return SamplingStatus.of(id <= this.positiveSamplingBoundary, tags);
    } else {
      return SamplingStatus.of(id >= this.negativeSamplingBoundary, tags);
    }
  }

id 跟采样边界比较,决定是否采样。我们已经知道 sample 的入参 id 是 span 的 id,而它是通过 ThreadlocalRandom 生成的一个随机 long,所以这个比较就相当于掷硬币,也就实现了随机采样。


GuaranteedThroughputSampler


从名字可以看出来,它会保证 Throughput 并采样,这是什么东西?继续看

  public static final String TYPE = "lowerbound";

  private ProbabilisticSampler probabilisticSampler;
  private RateLimitingSampler lowerBoundSampler;
  private Map<String, Object> tags;

  public GuaranteedThroughputSampler(double samplingRate, double lowerBound) {
    ....
    probabilisticSampler = new ProbabilisticSampler(samplingRate);
    lowerBoundSampler = new RateLimitingSampler(lowerBound);
  }

很明显,这是一个复合采样器。内部同时持有概率采样器和漏桶采样器,他们的特性前文已经说过了

  public synchronized SamplingStatus sample(String operation, long id) {
    SamplingStatus probabilisticSamplingStatus = probabilisticSampler.sample(operation, id);
    SamplingStatus lowerBoundSamplingStatus = lowerBoundSampler.sample(operation, id);

    if (probabilisticSamplingStatus.isSampled()) {
      return probabilisticSamplingStatus;
    }

    return SamplingStatus.of(lowerBoundSamplingStatus.isSampled(), tags);
  }

看 sample 方法,优先使用概率采样器采集,概率采样器没采集到的会漏到漏桶采样器,然后再由漏桶采样器来控制采样比率,这样基本上可以保证每种 operation(即一类调用,一般是接口/方法名) 都可以被采样到。


PerOperationSampler


还是顾名思义,每种操作一个 sampler ?

  private final int maxOperations;
  private final HashMap<String, GuaranteedThroughputSampler> operationNameToSampler;
  private ProbabilisticSampler defaultSampler;
  private double lowerBound;

  public PerOperationSampler(int maxOperations, OperationSamplingParameters strategies) {
    this(maxOperations,
         new HashMap<String, GuaranteedThroughputSampler>(),
         new ProbabilisticSampler(strategies.getDefaultSamplingProbability()),
         strategies.getDefaultLowerBoundTracesPerSecond());
    update(strategies);
  }

属性说明:

-- maxOperations 最大支持的 operation 数

-- operationNameToSampler operation to simpler 的缓存 map,可以看出最终都是GuaranteedThroughputSampler 这个复合采样器

-- defaultSampler 默认采样器

-- lowerBound GuaranteedThroughputSampler 的 lowerBound((ノ`Д)ノ 实在不知道这个翻成什么合适,反正就是复合采样器里漏桶采样器的每秒采样数)

构造参数说明:

-- maxOperations 最大支持的 operation 数

-- strategies 需要提前构造好的一堆采样器

构造方法中,首先初始化采样器 operation - sampler 的映射 map 和 默认的概率采样器

然后在 update 方法中按照 strategies 传入的数据统一初始化,最终都是GuaranteedThroughput复合采样器,并放入 map,这个不详说了

  public synchronized SamplingStatus sample(String operation, long id) {
    GuaranteedThroughputSampler sampler = operationNameToSampler.get(operation);
    if (sampler != null) {
      return sampler.sample(operation, id);
    }

    if (operationNameToSampler.size() < maxOperations) {
      sampler = new GuaranteedThroughputSampler(defaultSampler.getSamplingRate(), lowerBound);
      operationNameToSampler.put(operation, sampler);
      return sampler.sample(operation, id);
    }

    return defaultSampler.sample(operation, id);
  }

按照 operation 从映射 map 取采样器并由它判断是否采样,没有提前创建采样器的就新建并放入缓存,但不得超过最大值,否则不缓存,不抛异常。
这种采样器自己编码用不到。

RemoteControlledSampler

远程控制的采样器,这个是默认采样器,由 jaeger-controller 的 strategies.json 配置,并下发到 jaeger-agent,然后 jaeger-client 会从 jaeger-agent 拉取。

  void updateSampler() {
    SamplingStrategyResponse response;
    try {
      response = manager.getSamplingStrategy(serviceName);
      metrics.samplerRetrieved.inc(1);
    } catch (SamplingStrategyErrorException e) {
      metrics.samplerQueryFailure.inc(1);
      return;
    }

    if (response.getOperationSampling() != null) {
      updatePerOperationSampler(response.getOperationSampling());
    } else {
      updateRateLimitingOrProbabilisticSampler(response);
    }
  }

主要看一下 updateSampler 方法就行了,启动一个 RemoteControlledSampler 后,他会定时去 jaeger-agent 拉取配置,更新采样器,然后由配置的采样器规则决定是否采样。

Jaeger链路追踪

背景

当系统架构变得越来越复杂后,我们一次前端请求,有可能要经历跨多个线程/跨多个协程/跨多个进程处理后,才会最终响应到客户端,如果请求按照预期正确执行还好,万一在某个调用链的某一环节出现了问题,排查起来非常的麻烦,但如果有链路跟踪的话,就会大大降低排查的困难度。

可以通过在关键点设置链路埋点,记录下重要的步骤,例如,请求接收处,逻辑处理处,数据库交互处,调用了外部服务等。

前言

Jaeger是一款广受欢迎的开源分布式链路跟踪系统,兼容OpenTracing API,且已加入CNCF开源组织。其主要功能是聚合来自各个异构系统的实时监控数据。对一些常用的框架通过插件可以达到无侵入式跟踪,比如Apache HttpClient,Elasticsearch,JDBC,Kafka,Memcached,Mongo,OkHttp,Redis,Spring Boot,Spring Cloud,要通过Jaeger将Java应用数据上报至链路追踪控制台,首先需要完成埋点工作。您可以手动埋点,也可以利用各种现有插件实现埋点的目的。本文介绍以下三种埋点方法的其中一种手动埋点。

手动埋点

通过Spring Cloud组件埋点

通过gRPC组件埋点

一、Jaeger 是什么?

由于 Uber 的业务增长迅猛,其软件架构也越来越复杂,截止 2015 年下半年,Uber 内部已经有 500 多个微服务在运行,给问题排查和性能分析带来巨大困难。2016 年 4 月,Uber 启动 Jaeger 项目,并逐渐在内部推行分布式追踪系统,一年之后(2017 年 4 月),Uber 宣布正式将 Jaeger 开源。Uber Engineering Blog 有一篇文章介绍了分布式追踪系统在 Uber 的演进过程,建议阅读,《Evolving Distributed Tracing at Uber Engineering》。Uber开源的Jaeger(发音为ˈyā-gər ),因为它对OpenTracing支持的比较好,而且部署使用也非常简单。另外Jaeger的作者就是Yurishkuro。这里就不介绍Jaeger的细节了,有兴趣的可以去官网了解:Jaeger官网

照数据流向,整体可以分为四个部分:

  • jaeger-client:Jaeger 的客户端,实现了 OpenTracing 的 API,支持主流编程语言。客户端直接集成在目标 Application 中,其作用是记录和发送 Span 到 Jaeger Agent。在 Application 中调用 Jaeger Client Library 记录 Span 的过程通常被称为埋点。
  • jaeger-agent:暂存 Jaeger Client 发来的 Span,并批量向 Jaeger Collector 发送 Span,一般每台机器上都会部署一个 Jaeger Agent。官方的介绍中还强调了 Jaeger Agent 可以将服务发现的功能从 Client 中抽离出来,不过从架构角度讲,如果是部署在 Kubernetes 或者是 Nomad 中,Jaeger Agent 存在的意义并不大。
  • jaeger-collector:接受 Jaeger Agent 发来的数据,并将其写入存储后端,目前支持采用 Cassandra 和 Elasticsearch 作为存储后端。个人还是比较推荐用 Elasticsearch,既可以和日志服务共用同一个 ES,又可以使用 Kibana 对 Trace 数据进行额外的分析。架构图中的存储后端是 Cassandra,旁边还有一个 Spark,讲的就是可以用 Spark 等其他工具对存储后端中的 Span 进行直接分析。
  • jaeger-query & jaeger-ui:读取存储后端中的数据,以直观的形式呈现。

Jaeger 的架构非常清晰,部署起来也很轻松,Docker Hub 中有官方打好的 Image,可以拿来直接用,https://hub.docker.com/u/jaegertracing/。如果是本地测试,可以直接用 Jaeger 的 all-in-one Image,


上部分 Agent :负责从应用中,收集链路信息,发送给 SkyWalking OAP 服务器。目前支持 SkyWalking、Zikpin、Jaeger 等提供的 Tracing 数据信息。而我们目前采用的是,SkyWalking Agent 收集 SkyWalking Tracing 数据,传递给服务器。

Jaeger receiver

Jaeger receiver 目前只在跟踪模式下工作,不支持分析模式。Jaeger receiver提供额外的GRPC主机/端口,如果没有,将使用共享服务器主机/端口,还没有就使用核心GRPC主机/端口。Receiver需要激活Jaeger ElasticSearch存储实现。阅读此内容了解如何激活。right now only works in Tracing Mode, and no analysis. Jaeger receiver provides extra gRPC host/port, if absent, sharing-server host/port will be used, then core gRPC host/port. Receiver requires jaeger-elasticsearch storage implementation active. Read this to know how to active.

现在,你需要jaeger agent 来批量发送spans到 SkyWalking的oap服务器。 阅读Jaeger Architecture获取更多详情。

激活这个receiver。

下部分 SkyWalking OAP :负责接收 Agent 发送的 Tracing 数据信息,然后进行分析(Analysis Core) ,存储到外部存储器( Storage ),最终提供查询( Query )功能。

右部分 Storage :Tracing 数据存储。目前支持 ES、MySQL、Sharding Sphere、TiDB、H2 多种存储器。而我们目前采用的是 ES ,主要考虑是 SkyWalking 开发团队自己的生产环境采用 ES 为主。

左部分 SkyWalking UI :负责提供控台,查看链路等等。

二、使用步骤
1.引入库

2.配置初始化参数

如何设置埋点?

//埋点

批量上报的线程如下:

上报的类 :io.jaegertracing.internal.reporters.RemoteReporter

上报的方法在io.jaegertracing.internal.reporters.RemoteReporter.QueueProcessor#run

如果要把上报的日志打开

则要做如下设置:

new ReporterConfiguration().withLogSpans(true)

则会有如下的日志输出

3.读入数据

4.对外端口

查看运行中的容器实例:

docker ps

查看所有容器实例,包括没有运行的:

你可以通过访问 http://localhost:16686 展示 Jaeger UI.

采样速率

支持设置采样率是 Jaeger 的一个亮点,生产环境系统性能很重要,所以对于所有的请求都开启 Trace 显然会带来比较大的压力,另外,大量的数据也会带来很大存储压力。为了尽量消除分布式追踪采样对系统带来的影响,设置采样率是一个很好的办法。Jaeger官方 支持四种采样类别,分别是 const、probabilistic、rateLimiting 和 remote

const:全量采集,采样率设置0,1 分别对应打开和关闭

probabilistic:概率采集,默认万份之一,取值可在 0 至 1 之间,例如设置为 0.5 的话意为只对 50% 的请求采样

rateLimiting:限速采集,每秒只能采集一定量的数据,如设置2的话,就是每秒采集2个链路数据

remote :是遵循远程设置,取值的含义和 probabilistic 相同,都意为采样的概率,只不过设置为 remote 后,Client 会从 Jaeger Agent 中动态获取采样率设置。

guaranteedThroughput:复合采样,至少每秒采样lowerBound次(rateLimiting),超过lowerBound次的话,按照samplingRate概率来采样(probabilistic),

你可以通过访问 http://localhost:16686 展示 Jaeger UI.

采样速率

支持设置采样率是 Jaeger 的一个亮点,生产环境系统性能很重要,所以对于所有的请求都开启 Trace 显然会带来比较大的压力,另外,大量的数据也会带来很大存储压力。为了尽量消除分布式追踪采样对系统带来的影响,设置采样率是一个很好的办法。Jaeger官方 支持四种采样类别,分别是 const、probabilistic、rateLimiting 和 remote

  • const:全量采集,采样率设置0,1 分别对应打开和关闭
  • probabilistic:概率采集,默认万份之一,取值可在 0 至 1 之间,例如设置为 0.5 的话意为只对 50% 的请求采样
  • rateLimiting:限速采集,每秒只能采集一定量的数据,如设置2的话,就是每秒采集2个链路数据
  • remote :是遵循远程设置,取值的含义和 probabilistic 相同,都意为采样的概率,只不过设置为 remote 后,Client 会从 Jaeger Agent 中动态获取采样率设置。
  • guaranteedThroughput:复合采样,至少每秒采样lowerBound次(rateLimiting),超过lowerBound次的话,按照samplingRate概率来采样(probabilistic)

看了代码,还有两个复合采样器,GuaranteedThroughputSampler 和 PerOperationSampler

Spring Security和多个过滤器链

Spring Security是一项非常有用的技术。 它使您可以保护应用程序而不会过于侵入,并允许插入许多不同的身份验证机制。 另一方面,要使用它并不是那么容易,并且每次接触它时我都必须重新学习这些工具之一。 在这篇文章中,我将介绍Spring安全性的一些基础知识,以及如何使用它以不同的方式保护应用程序的不同部分。

Spring安全配置

让我们看一下Spring Security的一部分配置,您可以在Github上找到完整的源代码 。 我正在使用Spring Boot,但是对于所有Spring应用程序,大多数部分应该是相同的。

在最简单的情况下,您只需使用Spring Security中常见的方法链接来配置HttpSecurity 。 在这种情况下,我们启用HTTP基本身份验证,并要求对一个端点进行身份验证( /secure/以下的所有内容)。 允许所有其他请求(以/**表示)。 此处使用的模式是Ant路径语法,但是您也可以使用不同的RequestMatcher来决定应用程序的哪些部分需要哪种身份验证。

Spring boot的所有功能都在过滤器链中实现。 上面对httpBasic()的调用实际上只是确保将相关过滤器添加到过滤器链中。 在这种情况下, BasicAuthenticationFilter将检查是否存在一个Authorization标头并对其进行评估。 如果找到一个,它将在上下文中添加一个Authentication对象,并执行其余的过滤器链。 在该链的末尾是FilterSecurityInterceptor ,它检查所请求的资源是否需要身份验证,以及所设置的资源是否符合所请求的角色。

您还可以通过配置WebSecurity将应用程序的某些部分从身份验证中WebSecurity 。 以下方法确保对/resources/所有请求都跳过上面的配置。

在幕后,这将添加一个附加的过滤器链,该过滤器链针对配置的路径触发,但不执行任何操作。

多个过滤链

有时可能需要对应用程序的不同部分使用不同的身份验证机制。 为此,Spring Security允许您添加几个配置对象。 为此通常使用内部配置类,这些内部配置类也可以共享封闭应用程序的某些部分。 下列类添加了两个不同的Spring Security过滤器链。

这两个类都继承自适配器配置类,并配置其HttpSecurity 。 这些类中的每一个都添加一个过滤器链,并执行第一个匹配的链。 @Order批注可用于影响过滤器链的顺序,以确保首先执行正确的过滤器链。

也可能有必要将过滤器链限制为仅应用程序的特定部分,以免其他部分触发该过滤器链。 ActuatorConfiguration被限制为仅将请求匹配到/management/ 。 请注意,配置中有两个不同的地方可以接受RequestMatcher 。 开头的一个限制了触发过滤链的网址。 authorizeRequests()之后的请求用于定义哪些请求需要哪种身份验证。

请注意,配置WebSecurity并不与HttpSecurity配置之一绑定,因为它们添加了自己的过滤器链,只是顺序可能有所不同。 如果在两种配置中都添加了模式,它甚至可以在WebSecurity的同一实例上WebSecurity 。

最后一件事:如果您使用的是自定义身份验证过滤器(例如,基于令牌的身份验证),则可能需要注意不要将过滤器也注册为Servlet过滤器。 您可以通过配置一个返回FilterRegistrationBean的方法并接受Filter的实例来影响它。 只要创建一个新的FilterRegistrationBean为您的过滤器,并设置enabled以false 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值