美文网首页
BAM-SDK2.0 用户文档

BAM-SDK2.0 用户文档

作者: 耳边的火 | 来源:发表于2018-07-03 12:43 被阅读25次

该文档用于说明SDK2.0的核心类的构造和使用以及各接口的使用场景、使用方法。

引入SDK

在maven项目中添加如下依赖,从私服中获取sdk的jar包

<dependency>
            <groupId>com.cmiot.bam</groupId>
            <artifactId>sdk-java-core</artifactId>
            <version>1.0-SNAPSHOT</version>
</dependency>

若您项目中使用了grpc微服务,可以再额外添加如下依赖,增加grpc服务间的追踪能力。

<dependency>
            <groupId>com.cmiot.bam</groupId>
            <artifactId>sdk-java-instrument-grpc</artifactId>
            <version>1.0-SNAPSHOT</version>
</dependency>

核心类

您在使用BAM-SDK的过程中,常用的两个类为Tracer与Span。

Tracer类

Tracer类负责整个服务内的方法追踪,span生成与上报等功能。建议一个服务中只创建一个Tracer类。若添加了sdk-java-instrument-grpc依赖,则能够完成将该服务span信息传播到下一级服务的功能。

Tracer的常用创建方式

Tracer类的常见以及最简便的启动方式为:

Tracer tracer = new Tracer.Builder("youServiceName").build();

但是若您的应用为入口应用或根应用(如web场景中,处理网页请求的应用),则需要为Tracer添加DLN值,用于说明以该服务为起点所最终生成的追踪链所追踪的功能。

Tracer tracer = new Tracer.Builser("yourServiceName").withDLN("yourServiceFunction").build();

您也可以为所有服务中的tracer都设置DLN值,而不用去考虑该服务是否为根服务。但是只有由tracer创建的根span(root span即没有parent的span)才会被赋予设置到tracer中的DLN值。这也是建议您在根服务的tracer中添加DLN值的原因所在,因为根服务才会产生root span。

Span类

Span用于记录一个方法method的开始和结束,包括方法开始时间,结束时间,span名称(与方法相关的名称,便于定位问题),方法中重要节点或参数的记录等。Span类实现了Closeable接口,可以通过使用try-with-resource方式实现自动调用close()方法完成span信息的发送与销毁。

Span的常用创建方式

//不推荐
Span span = tracer.buildSpan("spanName").start();

或者

//推荐写法,同时该写法要配合try-with-resource形式
Span span = tracer.buildSpan("spanName").startActive();

两种创建方式的区别仅在于,startActive()方法会在创建完span后,将span激活。关于激活的概念后面会详细说明。

在项目中,一般一个span对应一个方法的追踪,书写方式如下:

示例1:不推荐的写法,用作后续推荐写法的对比

public void yourMethod(){
  Span span= tracer.buildSpan("spanName").start();
  tracer.active(span);//激活当前span
  try {
      //您的方法内容
  }catch(Exception e){
      span.setTag("error",e.getMessage);
      span.log("catch exception!!"+e.getMessage);
  }finally{
      span.finish;//结束并异步发送
      tracer.deActive(span);//使当前span失活
  }
}

或者

示例2:不推荐写法

public void yourMethod(){
  Span span= tracer.buildSpan("spanName").start();
  try (Span span = tracer.active(span)) {//try-with-resource方式会自动调用span.close()方法
      //您的方法内容
  }
}

或者

示例3:推荐写法,使用try-with-resource模式

public void yourMethod(){
  try (Span span = tracer.buildSpan("spanName").startActive()) {
      //您的方法内容
  }
}

示例1中的创建方式不推荐,非常繁琐,但是更能容易理解span的创建与span的激活。

使用try-with-resource方式创建并激活span,最为简便。且会自动调用span.close()方法,该方法等价于示例1中的span.finish()与trace.deActice(span)这两句。

SDK使用示例详解

示例场景介绍

该示例有四个服务(应用)分别为:MapServer,PointDataServer,LineDataServer,BackendServer。这四个服务使用gRPC实现远程方法调用。

其中MapServer为一个web应用,其controller层处理请求,并调用PointDataServer,LineDataServer的远程方法,而PointDataServer,LineDataServer又调用BackendServer的方法处理数据。其依赖拓扑如下图:

grpc-demo.png

每个服务(应用)中都需要创建Tracer。而由于MapServer是根服务,由它产生root span。因此在创建MapServer的Tracer时需要添加DLN值,该DLN值可以沿着span的传播传递下去,标识由MapServer服务作为入口的请求最终形成的服务间的调用链。

项目引入sdk-java-core与sdk-java-instrument-grpc的依赖。

创建Tracer

MapServer中创建Tracer Tracer tracer = new Tracer.Builder("MapServer").withDLN("show_map").build();

其他服务中创建TracerTracer tracer = new Tracer.Builder("YourServiceName").build();

建议每个服务只创建一个Tracer。因为服务的功能应该是单一的,而DLN值描述的就是该服务的功能。可以将创建好的Tracer交由Spring IOC容器管理,或构建Tracer的单例模式来在服务中获取Tracer。

创建Span

这一部分会以MapServer为例,讲解如何创建Span,方法的嵌套是如何转为span间的parent-child关系以及span是如何在gRPC服务间传递的。

我们再细化下MapServer中的类与方法。其collector类中有showMap(param)方法用于接收请求并处理。有PointClient与LineClient类,两个类中分别有调用其各自grpc-server的方法叫做callPointServer()与callLineServer(),而在callPointServer()中需要提前处理一下数据,于是会在callPointServer()方法中先调用processPointData()方法,然后再请求grpc-server。如下图:

UML模型与方法伪代码.png

按照上述描述,我们预期的图形化如下图:

时间线与深度.png

由上图可以清晰看到方法执行时间的长度,先后顺序以及深度(即方法间的嵌套调用关系)。

那么如何构建span能够将span所记载的信息最终可以加工成上图的形式呢?下面我们在四个方法中创建span。

  • showMap方法
public void showMap(String param){
  try (Span span = tracer.buildSpan("showMapMethod").startActive()) {
      lineClient.callLineServer();
      pointClient.callPointServer();
  }
}

上述代码先构建一个Span,然后将Span设置为当前激活的span,以便该span可以作为后续创建span的parent。然后通过java的try-with-resource的方法在try语句块结束后,立即自动调用span的close方法,取消对该span的激活并发送span。

需要注意的是,构造该span时,并没有其他span为激活状态,因此该span为根span即root span。在创造root span的方法中会自动的从tracer中获取DLN值赋值到root span中。这样该root span的子span即child span也会继承该DLN值。使得该DLN内容在该请求所触发的所有服务的span中都存在。后续方便处理根据DLN将span进行分类。

  • callLineServer方法
public void callLineServer(){
  try (Span span= tracer.buildSpan("callLineServer").startActive()) {
      try{
        call();//请求grpc-server
      }catch(Exception ex){
          //设置tag可以在链路监控的前端页面中根据key进行搜索
          span.setTag("key","value");//tag以key-value形式存储信息,相同的key时,新的value会覆盖旧的value
          //设置log后,只进行记录,在前端页面不提供搜索log的功能
          span.log("log some thing...");//log用于记录更详细的内容,根据tag查找到某次调用的trace后,查看各个span的log能够更详细的了解该条trace的情况
      }
  }
}

上述代码也是先构建一个span,在构建该span时,会查看是否有当前处于激活状态的span。而callLineServer()方法在showMap方法的try语句块内,因此当前是有激活状态的span的,该callLineServer的span会以showMap的span为parent来构建。

然后同样使用try-with-resource方法,将当前span设置为新的激活span(showMap的span暂时进入缓存,不再是激活状态,等待稍后再次被激活)。

最后,在执行完try语句块,span调用close结束后,会将当前span从激活列表中删除,重新将之前被缓存的showMap的span激活,作为激活状态的span。

  • callPointServer方法
public void callPointServer(){
  try (Span span = tracer.buildSpan("callPointServer").startActive()) {
      processPointData();
      call();
  }
}

由于callLineServer()方法的span已经从激活的缓存列表中删除,并且将showMap的span重新激活,因此callPointServer的span会以showMap的span作为parent。这样callLineServer与callPointServer是同级的span,它们都以showMap的span为parent,这也符合showMap方法中调用callLineServer与callPointServer方法的真实情况。

  • processPointData方法
public void processPointData(){
  try (Span span = tracer.buildSpan("processPointData").startActive()) {
      //do something
  } 
}

Span在服务间的传播

下面以gRPC服务间的传播为例进行说明。

上面介绍了在MapServer中如何创建span,并解释了创建的span是如何建立关系的。但是MapServer调用LineDataServer时,在LineDataServer中也会创建span,这时,如何保证LineDataServer中的span会作为MapServer中span的child呢?或者服务间的span是如何建立关系的呢?

这时就需要您之前引入的sdk-java-instrument-grpc依赖了。

sdk-java-instrument-grpc所提供的功能为:将本服务内的span传递到下一级服务,作为下一级服务的parent;或者从上一级服务中提取出span,作为本服务span的parent。这些功能对您来说是透明的,您不会感知到这个过程。您要做的只是如下所示,在创建gRPC的client或server时添加sdk-java-instrument-grpc提供的拦截器即可。

//gRPC client端添加拦截器,将tracer注入
channel = ManagedChannelBuilder
                .forAddress(host, port)
                .intercept(new BamClientInterceptor(tracer))
                .build();
//gRPC server端添加拦截器,将tracr注入
server = ServerBuilder.forPort(port)
                .addService(
                        ServerInterceptors.intercept(
                                new GreeterImpl(tracer,client).bindService(),
                                new BamServerInterceptor(tracer)
                        )
                )
                .build()
                .start();

sdk-java-instrument-grpc中提供的拦截器会完成上述功能,您只要按照之前示例中的span的创建方法创建span即可。

同样地,其他服务间的span的传播原理相同。

启动

BAM-SDK作为您应用程序的依赖,可以在启动您的应用程序时读取一些参数。目前可以在启动您的应用程序时,通过设置环境变量来配置BAM-SDK所依赖的Agent的地址。如启动MapServer.jar:

BAM_AGENT_HOST=localhost BAM_AGENT_PORT=15006 java -jar MapServer.jar

可设置的环境变量有:

环境变量 含义
BAM_AGENT_HOST Agent的地址
BAM_AGENT_PORT Agent的端口
MAX_QUEUE_SIZE 异步发送的缓存队列的大小,用于应对在FLUSH_INTERVAL_MS周期内,突发流量,默认为1000
FLUSH_INTERVAL_MS 异步发送时间间隔,单位毫秒,默认500ms

关于Agent地址设置的优先级为:

  • 代码设置优先级最高

Tracer tracer = new Tracer.Builder("yourServiceName").withAgentIpAndPort("localhost:15006").build()

  • 环境变量第二优先级
  • 配置文件conf.properties第三优先级

同样对于您应用程序所在机器的Ip与Port的设置,其优先级为:

  • span中设置的优先级最高

span.setStartEndpoint() span.setFinishEndpoint()

  • 构造tracer时设置 优先级第二

Tracer tracer = new Tracer.Builder("yourServiceName").withLocalIpAndPort("172.20.192.234:50051").build()

  • SDK自动获取本机地址优先级最低

SDK异常保护机制

您在使用SDK时,若由于误操作如ip设置错误或其他错误,导致SDK出现异常,SDK不会报运行时异常,中断您的应用程序,而是事实上的关掉自己。这一般出现在两处,一处为构造Tracer时出现问题,第二处为SDK与Agent的连接问题。

构造Tracer错误

  • serviceName为null或空
  • withLocalIpAndPort设置的ip不合法
  • withAgentIpAndPort设置的ip不合法

当上述三种情况发生时,不会构造一个有具体功能的Tracer而是给您返回一个NoopTracer。NoopTracer有Tracer的所有方法,但是不会执行任何操作。

  • withSampleRate(double rate)设置错误

采样率应在[0,1]闭区间取值,若超过该区间,默认使用采样率为1.0。

SDK与Agent连接问题

以下两种情况时:

  • 未通过withAgentIpAndPort设置Agent地址,且未设置环境变量或配置文件

  • 解析地址异常如ip不合法

构建NoopReport,所有span都不采样(因为无法发送到agent)。返回的Tracer所有接口都可用,只是span未采样,因此不会发送span的采集信息。

若出现以下情况:

  • 配置了Agent地址(三种方式其一即可),但连接不上,或连接突然中断

Tracer正常,所用功能均可使用。只是所有span都不采样(连接中断,无法发送span到agent),若有span已经采集会先缓存等待连接重新建立再执行发送。

SDK的发送

为尽可能少的影响您的应用,SDK在您的应用线程采集完数据后,通过span的finish方法会将span采集的信息上报给Agent(span的close()方法会自动调用finish()方法,若您采用try-with-resource方式,则自动调用close()方法))。SDK与Agent的通信方式采用gRPC通信,采用异步发送设计,由线程安全的容器做缓存,交由异步线程从容器中取出span信息通过gRPC双向流发送给Agent。

SDK与Agent实现了断线重连机制。当连接不可用时,新产生的span均不会被采样,不采样的span不会在调用finish时发送到Agent。而在断线之前已经采样的span会暂时缓存到容器中,等待连接重新建立。

重连过程对您的应用是透明的。您可以在日志中查看重连情况。

SDK日志

SDK使用logback采集日志。若您的应用程序也使用了logback来采集日志,则SDK的日志内容会打印在您所使用的logback定义的日志文件中。SDK的日志符合“OneNET日志规范”。

SDK采样

SDK支持采样的远程配置。可通过Agent远程下发全局的采样率以及临时的过滤器。

SDK的采样逻辑为:

  • 在span创建时(调用start()或startActive()方法时),便已经决定该span是否会被采样,被采样的span才会发送,不采样的span不会发送,为了应用的性能,未采样的span调用span.setTag与span.log方法时不会记录信息(因为不会发送)。

  • 对span的采样控制,只对root span生效,其余的span作为child,会根据其parent是否采样来决定自己是否被采样,这样是为了保证采样的trace整条数据都能够上传,未采样的trace不会上传span,造成管理混乱。

因此,下面的采样的远程配置只对root span有效。

全局采样率

SDK在Tracer初始化时,会构建采样器,默认使用可远程控制的采样器,根据构建tracer时传入的采样率来配置采样器的全局采样率,若未传入,采样率默认为1.0。

临时过滤器

可以通过Agent下发临时过滤器,如:想要将pid=10000的数据过滤出来,采样并上报。则可以指定pid的值以及持续时间(单位:秒),在持续时间内,若有pid=10000的链路产生,即便被全局采样器认定为不采样,最终也会由临时过滤器认定为需要采样并上报。超过持续时间后,临时过滤器会自动失效并删除。

临时过滤器的过滤原理为:在span的tag中查找key=pid,value=10000是否存在,若存在,则采样。

结合采样的逻辑,临时过滤器要想达到预期目标,需要以下几个条件:

  • 在调用startActive()方法前,就在span中设置好tag的值

  • 将要过滤的值可以在root span创建时获取到

如:还是以过滤pid=1000为例,需要如下操作:

public void getPid(String pid){
  try(Span rootSpan = tracer.buildSpan("getPidMethod").withTag("pid",pid).startActive()){
      //do something
      rootSpan .setTag("did",20000);
  }
}

由于在创建rootSpan时可以获取到pid,且在构建完成span前,通过withTag方法提前将key=pid,value=MethodParam 写入到span的build中,在调用startActive方法时,该方法内部会根据过滤器到tag中判断该span是否采样。因此这种情况下,若有pid=10000的数据传入,则这条追踪链一定会采样并上报。

若过滤器修改为did=20000,则上面的例子中,即便已经写了rootSpan.setTag("did",20000),也不会被过滤器通过(有可能被全局采样通过),这是因为在startActive()方法之前,没有在tag中写入相关信息。

最终,需要说明一点。若只有全局采样器,则最终采样率即为配置的全局采样率;若采样器配置了全局采样率如0.8后,再配置临时过滤器时,则在临时过滤器生效的时间段内,采样率有可能大于0.8,这是因为未被全局采样器采样的trace,仍然有机会被临时过滤器认定为需要采样。

SDK接口介绍

标志为 * 星号的接口为必填,其他为选填。

Tracer.Builder相关的API

Builder(String serviceName) *

Tracer.Builder内部类的构造函数。入参为serviceName,返回Tracer.Builder对象。

Builder withDLN(String DLN) *(根服务)

设置DLN值,用于追踪不同服务间的同一条调用链的span。若该服务为根服务,或使用该Tracer.Builder构造出的tracer生成了root span,则为root span赋值该DLN值。

Builder withLocalIpAndPort(String ipAndPort)

设置您的应用所在机器的ip与port,格式为:"172.20.192.222:15001"或"localhost:1600"。若只知道ip而port不确定或没有可以写为:"172.20.192.222"或"172.20.192.222:"。

Builder withAgentIpAndPort(String ipAndPort)

设置BAM Agent的地址,格式同上。但必须填写port。

Builder withSampleRate(double rate)

设置初始化采样率,取值在[0,1]闭区间。

Tracer build()

根据调用的Builder的API,构造Tracer类,选填的API若没有调用,则使用默认配置构造Tracer。

Tracer相关的API

String getCurrentTraceId()

获取当前线程中Span的TraceId的16进制的字符串表示,若当前线程无span,则返回空字符串

Endpoint getLocalEndpoint()

获取您的应用所在机器的ip与port。若您在构造tracer中调用了withLocalIpAndPort方法,则返回您设置的值,否则返回BAM-SDK自动获取的值。

String getServiceName()

返回您在构造tracer时,设置的服务名。

Span activeSpan()

获取当前被激活的span。一个线程中只能有一个处于激活状态的span,除非特别指定,该span都会作为其他span的parent。

Span active(Span)

将传入的span设置为当前线程中的激活span,以便可以自动的作为后续创建的span的parent。

void deActive()

将当前线程的激活状态的span设置为失活。慎用,若误用该操作会导致span间的关系混乱。建议使用推荐的try-with-resource方式,自动管理span的激活与失活。

SpanBuilder buildSpan(String operationName)

返回Tracer.SpanBuilder内部类,用于构造Span类。入参为span名称,建议与method名称相关,便于定位问题。

void close() *

关闭tracer的方法。该方法安全的关闭SDK中各个业务线程。

SpanBuilder相关API

SpanBuilder kind(SpanKind kind)

用于设置所要构造的span的类型,默认为LOCAL。建议您不调用该API。因为您构造的span一般都为LOCAL类型,其他类型如CLIENT,SERVER,PRODUCER,CONSUMER,DB等的span由BAM-SDK帮您封装,用于服务间的追踪。

SpanBuilder withSpanContext(SpanContext spanContext)

功能为使用入参的spanContext构造span。

SpanBuilder withStartEndpoint(Endpoint startEndpoint)

设置该span生成时所在服务的ip与port。若未设置,将使用tracer中的localEndpoint。

SpanBuilder withFinishEndpoint(Endpoint finishEndpoint)

设置该span在结束时所在服务的ip与port。若未设置,将使用tracer中的localEndpoint。一般来说,您构造的类型为LOCAL的span,其startEndpoint与finishEndpoint均在同一个服务中,因此ip与port是相同的。一般情况下,您在tracer中统一设置应用所在机器的信息后,可以不使用该API。

SpanBuilder withTag(String key, String value)

在构造span时,写入tag信息,用于记录方法中的关键信息。您也可以在构造完span后,调用Span类的API实现同样的功能。

SpanBuilder withTag(String key, boolean value)

同上

SpanBuilder withTag(String key, Number value)

同上

Span start()

根据前面的设置,构建并返回Span对象。

Span startActive()

根据前面的设置,构建Span对象并激活。

SpanBuilder ignoreActiveSpan()

构建span时,忽略当前线程中激活状态的span。即将要创建的span不会以当前激活状态的span为parent。

Span相关的API

get方法不再详述。

Span setStartEndpoint(Endpoint startEndpoint)

若通过build方式构造span时没有设置startEndpoint,也可以在构造完span后通过该方法设置。

Span setFinishEndpoint(Endpoint finishEndpoint)

同上。

Span setOperationName(String operationName)

同上。

Span setBaggageItem(String key, String value)

以key-value形式存储在span的baggage中,该内容不会随着span的结束而消失,也不会被发送,而是会随着调用链传递下去。

void finish()

记录当前结束时间并发送span所记录的信息。若您使用上面推荐的写法,一般无需手动调用该方法。

void finish(long finishMicros)

为span的结束赋值给定时间。

Span setTag(String key, String value)

用于记录该span所在方法的一些重要信息,key-value形式。

Span setTag(String key, boolean value)

同上

Span setTag(String key, Number value)

同上

Span log(String event)

用于记录该span所在方法的一些重要参数。会自动记录时间戳。

Span clearTags()

清空span中的所有tag内容

Span setSpanContext(SpanContext context)

设置spanContext

相关文章

网友评论

      本文标题:BAM-SDK2.0 用户文档

      本文链接:https://www.haomeiwen.com/subject/sphluftx.html