trpc.group/trpc-go/trpc-go@v1.0.3/rpcz/README.zh_CN.md (about)

     1  [English](README.md) | 中文
     2  
     3  # RPCZ
     4  
     5  RPCZ 是一个监控 RPC 的工具,记录了一次  rpc 中发生的各种事件,如序列化/反序列,压缩解压缩和执行拦截器。
     6  RPCZ 可以帮助用户调试服务,它允许用户自行配置需要被记录的事件,用户可以通过 admin 工具可以查看记录的事件,能帮助用户快速准确的定位问题。
     7  除此之外,由于 RPCZ 记录了 RPC 中各种事件的持续时间和收发数据包的大小,因此可以帮助用户分析超时事件,优化服务的性能。
     8  
     9  ## 术语解释
    10  
    11  ### 事件(Event)
    12  
    13  事件(Event)[1, 2, 3] 用来描述某一特定时刻(`Event.Time`)发生了某件事情(`Event.Name`)。
    14  
    15  ```go
    16  type Event struct {
    17  	Name string
    18  	Time time.Time
    19  }
    20  ```
    21  在一个普通 RPC 调用中会发生一系列的事件,例如发送请求的 Client 端按照时间先后顺序,一般会发生如下一系列事件:
    22  
    23  1. 开始运行前置拦截器
    24  2. 结束运行前置拦截器
    25  3. 开始序列化
    26  4. 结束序列化
    27  5. 开始压缩
    28  6. 结束压缩
    29  7. 开始编码协议头部字段
    30  8. 结束编码协议头部字段
    31  9. 开始发送二进制文件到网络
    32  10. 结束发送二进制文件到网络
    33  11. 开始从网络中接收二进制文件
    34  12. 结束从网络中接收二进制文件
    35  13. 开始解码协议头部字段
    36  14. 结束解码协议头部字段
    37  15. 开始解压缩
    38  16. 结束解压缩
    39  17. 开始反序列化
    40  18. 结束反序列化
    41  19. 开始运行后置拦截器
    42  20. 结束运行后置拦截器
    43  
    44  而处理请求的 server 端,按照时间先后顺序,一般会发生如下一系列事件:
    45  
    46  1. 开始解码协议头部字段
    47  2. 结束解码协议头部字段
    48  3. 开始解压缩
    49  4. 结束解压缩
    50  5. 开始反序列化
    51  6. 结束反序列化
    52  7. 开始运行前置拦截器
    53  8. 结束运行前置拦截器
    54  9. 开始运行用户自定义处理函数
    55  10. 结束运行用户自定义处理函数
    56  11. 开始运行后置拦截器
    57  12. 结束运行后置拦截器
    58  13. 开始序列化
    59  14. 结束序列化
    60  15. 开始压缩
    61  16. 结束压缩
    62  17. 开始编码协议头部字段
    63  18. 结束解码协议头部字段
    64  19. 开始发送二进制文件到网络
    65  20. 结束发送二进制文件到网络
    66  
    67  ### Span
    68  
    69  Span[4, 5] 用来描述某段时间间隔(具有开始时间和结束时间)的单个操作,例如客户端发送远程请求,服务端处理请求或函数调用。
    70  根据划分的时间间隔大小不同,一个大的 Span 可以包含多个小的 Span,就像一个函数中可能调用多个其他函数一样,会形成树结构的层次关系。
    71  因此一个 Span 除了包含名字、内部标识 span-id[6],开始时间、结束时间和这段时间内发生的一系列事件(Event)外,还可能包含许多子 Span。
    72  
    73  rpcz 中存在两种类型的 Span。
    74  1. client-Span:描述 client 从开始发送请求到接收到回复这段时间间隔内的操作(涵盖上一节 Event 中描述的 client 端发生一系列事件)。
    75  
    76  2. server-Span:描述 server 从开始接收请求到发送完回复这段时间间隔内的操作(涵盖上一节 Event 中描述的 server 端发生一系列事件)。
    77     server-Span 运行用户自定义处理函数的时候,可能会创建 client 调用下游服务,此时 server-Span 会包含多个子 client-Span。
    78  
    79  ```
    80  server-Span
    81      client-Span-1
    82      client-Span-2
    83      ......
    84      client-Span-n
    85  ```
    86  
    87  Span 被存储在 context 中,rpcz 会自动调用 ContextWithSpan 往 context 中存 Span,在函数调用过程中需要保证 context 中的 Span 不会丢失。
    88  
    89  ## Span 的生命周期
    90  
    91  考察 Span 对象的生命周期,rpcz 中对 Span 的绝大多数操作,都需要考虑并发安全。
    92  除此之外采用了 sync.Pool 和 预先分配的循环数组来降低 Span 的内存分配时对性能的影响。
    93  
    94  ### Span 的构造
    95  
    96  rpcz 在启动时会初始化一个全局 GlobalRPCZ,用于生成和存储 Span。
    97  在框架内部 Span 只可能在两个位置被构造,
    98  第一个位置是在 server 端的 transport 层的 handle 函数刚开始处理接收到的请求时;
    99  第二个位置是在 client 端的桩代码中调用 Invoke 函数开始发起 rpc 请求时。
   100  虽然两个位置创建的 Span 类型是不同,但是代码逻辑是相似的,都会调用 rpczNewSpanContext,该函数实际上执行了三个操作
   101  1. 调用 SpanFromContext 函数,从 context 中获取 span。
   102  2. 调用 span.NewChild 方法,创建新的 child span。
   103  3. 调用 ContextWithSpan 函数,将新创建的 child span 设置到 context 中。
   104  
   105  ### Span 在 context 中传递
   106  
   107  被创建 Span 在提交前,会一直在存放在 context 中,沿着 rpc 调用的链路传递。
   108  在调用链路上使用 `rpcz.AddEvent` 往当前 context 中的 Span 中添加新的事件。
   109  
   110  ### Span 的提交
   111  
   112  在 server 端的 transport 层的 handle 函数处理完请求后,会调用 `ender.End()` 把 Span 提交到 GlobalRPCZ 当中。
   113  此后虽然 context 中仍然存放着 Span,但是从语义上来说,已经调用过的 End 函数的 Span 不允许再被继续操作,其行为是未定义的。
   114  
   115  ### 在 admin 中访问 Span
   116  
   117  admin 模块调用 `rpcz.Query` 和 `rpcz.BatchQuery` 从 GlobalRPCZ 中读取 Span。
   118  有一点需要注意的是,admin 获取的 Span 是只读类型的 Span(ReadOnlySpan),只读类型的 Span 是由可写入的 Span 导出得到的,这样做的原因是保证并发访问安全。
   119  
   120  ### 删除多余的 Span
   121  
   122  当哈希表中存储的 Span 过多时就需要按照某种淘汰规则,删除多余的 Span。
   123  目前的实现是当 GlobalRPCZ 中的 Span 个数超过最大容量上限时会删除最老的 Span。
   124  
   125  ## RPCZ 名字的由来
   126  
   127  关于 "RPCZ" 的这个名字的由来,后缀 -z 有在英文中一般有两种含义 [7]: 一是用于名词,实现单数变复数,如 Boy**z** are always trouble;二是用于动词实现动词形态的变化 He love**z** me。
   128  总的来说,在单词后面加 -z 的效果类似于加 -s。
   129  所以 "RPCZ" 就是指各种类型的 RPC,从一个分布式全局的调用链路视角来看的确是成立的,各种 RPC 调用存在树状的父子关系,组合成了 "RPCZ"。
   130  
   131  "RPCZ" 这一术语最早来源于 google 内部的 RPC 框架 Stubby,在此基础上 google 在开源的 grpc 实现了类似功能的 channelz[8],channelz 中除了包括各种 channel 的信息,也涵盖 trace 信息。
   132  之后,百度开源的 brpc 在 google 发表的分布式追踪系统 Dapper 论文 [9] 的基础上,实现了一个非分布式的 trace 工具,模仿 channelz 取名为 brpc-rpcz[10]。
   133  接着就是用户在使用 tRPC 中需要类似于 brpc-rpcz 的工具来进行调试和优化,所以 tRPC-Cpp 首先支持类似功能,仍然保留了 RPCZ 这个名字。
   134  
   135  最后就是在 tRPC-Go 支持类似 "RPCZ" 的功能,在实现过程中发现随着分布式追踪系统的发展,社区中出现了 opentracing[11] 和 opentelemetry[12] 的开源系统。
   136  tRPC-Go-RPCZ 在 span 和 event 设计上部分借鉴了 opentelemetry-trace 的 go 语言实现,可以认为是 tRPC-Go 框架内部的 trace 系统。
   137  严格来说,tRPC-Go-RPCZ 是非分布式,因为不同服务之间没有在协议层面实现通信。
   138  现在看来,brpc, tRPC-Cpp 和 tRPC-Go 实现的 rpcz,取名叫 spanz 或许更符合后缀 "-z" 本来的含义。
   139  
   140  ## 如何配置 rpcz
   141  
   142  rpcz 的配置包括基本配置,进阶配置和代码配置,更多配置例子见 `config_test.go`。
   143  
   144  ### 不同 tRPC-GO 版本的支持情况
   145  
   146  - v0.15.0:支持 tRPC 流式和 tnet。
   147  - v0.14.0:支持 http 协议。
   148  - v0.11.0:支持 tRPC 一元调用。
   149  - v0.11.0 之前的版本:不支持。
   150  
   151  ### 基本配置
   152  
   153  在 server 端配置 admin,同时在 admin 里面配置 rpcz :
   154  
   155  ```yaml
   156  server:
   157    admin:
   158      ip: 127.0.0.1
   159      port: 9028
   160      rpcz:
   161        fraction: 1.0
   162        capacity: 10000
   163  ```
   164  
   165  - `fraction` : 采样率,其取值范围为`[0.0, 1.0]`,默认值为 0.0 代表不采样,需要手动配置。
   166  - `capacity`:  rpcz 的存储容量,默认值为 10000,表示最多能存储的 span 数量。
   167  
   168  ### 进阶配置
   169  
   170  进阶配置允许你自行过滤感兴趣的 span,在使用进阶配置之前需要先了解 rpcz 的采样机制。
   171  
   172  ####  采样机制
   173  
   174  rpcz 使用采样机制来控制性能开销和过滤你不感兴趣的 Span。
   175  采样可能发生在 Span 的生命周期的不同阶段,最早的采样发生在 Span 创建之前,最晚的采样发生在 Span 提交之前。
   176  
   177  ##### 采样结果表
   178  
   179  只有创建和提交之前都被采样到的 Span 才会最终被收集到 GlobalRPCZ 中,供你通过 admin 接口查询。
   180  
   181  | 在 Span 创建之前采样?   | 在 Span 提交之前采样? | Span 最终是否会被收集? |
   182  |:-----------------|:--------------:|:--------------:|
   183  | true             |      true      |      true      |
   184  | true             |     false      |     false      |
   185  | false            |      true      |     false      |
   186  | false            |     false      |     false      |
   187  
   188  ##### 在 Span 创建之前采样
   189  
   190  只有当 Span 被采样到才会去创建 Span,否则就不需要创建 Span,也就避免了后续对 Span 的一系列操作,从而可以较大程度上减少性能开销。
   191  采用固定采样率 [13] 的采样策略,该策略只有一个可配置浮点参数 `rpcz.fraction`, 例如`rpcz.fraction` 为 0.0001,则表示每 10000(1/0.0001)个请求会采样一条请求。
   192  当 `rpcz.fraction` 小于 0 时,会向上取 0;当 `rpcz.fraction` 大于 1 时,会向下取 1。
   193  
   194  ##### 在 Span 提交之前采样
   195  
   196  已经创建好的 Span 会记录 rpc 中的各种信息,但是你可能只关心包含某些特定信息的 Span,例如出现 rpc 错误的 Span,高耗时的 Span 以及包含特定属性信息的 Span。
   197  这时,就需要在 Span 最终提交前只对你需要的 Span 进行采样。
   198  rpcz 提供了一个灵活的对外接口,允许你在服务在启动之前,通过配置文件设置 `rpcz.record_when` 字段来自定义 Span 提交之前采样逻辑。
   199  record_when 提供3种常见的布尔操作:`AND`, `OR` 和 `NOT`,7种返回值为布尔值的基本操作,`__min_request_size`, `__min_response_size`, `__error_code`, `__error_message`, `__rpc_name`, `__min_duration` 和 `__has_attribute`。
   200  **需要注意的是 record_when 本身是一个 `AND` 操作**。
   201  你可以通过对这些操作进行任意组合,灵活地过滤出感兴趣的 Span。
   202  
   203  ```yaml
   204  server:  # server configuration.
   205    admin:
   206      ip: 127.0.0.1  # ip.
   207      port: 9528  # default: 9028.
   208      rpcz:  # tool that monitors the running state of RPC, recording various things that happen in a rpc.
   209        fraction: 0.0  # sample rate, 0.0 <= fraction <= 1.0.
   210        record_when: #  record_when is actually an AND operation
   211          - AND:
   212              - __min_request_size: 30  # record span whose request_size is greater than__min_request_size in bytes.
   213              - __min_response_size: 40  # record span whose response_size is greater than __min_response_size in bytes.
   214          - OR:
   215              - __error_code: 1  # record span whose error codes is 1.
   216              - __error_code: 2  # record span whose error codes is  2.
   217              - __error_message: "unknown" # record span whose error messages contain  "unknown".
   218              - __error_message: "not found" # record span whose error messages contain  "not found".
   219          - NOT: {__rpc_name: "/trpc.app.server.service/method1"}  # record span whose RPCName doesn't contain __rpc_name.
   220          - NOT: # record span whose RPCName doesn't contain "/trpc.app.server.service/method2, or "/trpc.app.server.service/method3".
   221              OR:
   222                - __rpc_name: "/trpc.app.server.service/method2"
   223                - __rpc_name: "/trpc.app.server.service/method3"
   224          - __min_duration: 1000ms  # record span whose duration is greater than __min_duration.
   225          # record span that has the attribute: name1, and name1's value contains "value1"
   226          # valid attribute form: (key, value) only one space character after comma character, and key can't contain comma(',') character.
   227          - __has_attribute:  (name1, value1)
   228          # record span that has the attribute: name2, and name2's value contains "value2".
   229          - __has_attribute:  (name2, value2)
   230  ```
   231  
   232  #### 配置举例
   233  
   234  ##### 对包含错误码为 1(RetServerDecodeFail),且错误信息中包含 “unknown” 字符串,且持续时间大于 1s 的 span 进行提交
   235  
   236  ```yaml
   237  server:
   238    admin:
   239      ip: 127.0.0.1
   240      port: 9028
   241      rpcz:
   242        fraction: 1.0
   243        capacity: 10000
   244        record_when:
   245          - __error_code: 1       
   246          - __min_duration: 1000ms
   247          - __sampling_fraction: 1         
   248  ```
   249  
   250  注意: record_when 本身是一个 AND 节点,还可以有以下写法:写法1, 写法2
   251  
   252  写法1:
   253  
   254  ```yaml
   255  server:
   256    admin:
   257      ip: 127.0.0.1
   258      port: 9028
   259      rpcz:
   260        fraction: 1.0
   261        capacity: 10000
   262        record_when:
   263          - AND:
   264              - __error_code: 1       
   265              - __min_duration: 1000ms
   266              - __sampling_fraction: 1         
   267  ```
   268  
   269  写法2:
   270  
   271  ```yaml
   272  server:
   273    admin:
   274      ip: 127.0.0.1
   275      port: 9028
   276      rpcz:
   277        fraction: 1.0
   278        capacity: 10000
   279        record_when:
   280          - AND:
   281              - __error_code: 1
   282          - AND:
   283              - __min_duration: 1000ms
   284              - __sampling_fraction: 1         
   285  ```
   286  
   287  写法3:
   288  
   289  ```yaml
   290  server:
   291    admin:
   292      ip: 127.0.0.1
   293      port: 9028
   294      rpcz:
   295        fraction: 1.0
   296        capacity: 10000
   297        record_when:
   298          - AND:
   299              - __error_code: 1
   300          - AND:
   301              - __min_duration: 1000ms
   302          - AND:
   303              - __sampling_fraction: 1         
   304  ```
   305  
   306  还有其他写法,你可以自行尝试。
   307  
   308  ##### 对包含错误码为 1(RetServerDecodeFail) 或 21(RetServerTimeout) 的或持续时间大于 2s 的 span 以 1/2 的概率进行提交
   309  
   310  ```yaml
   311  server:
   312    admin:
   313      ip: 127.0.0.1
   314      port: 9028
   315      rpcz:
   316        fraction: 1.0
   317        capacity: 10000
   318        record_when:
   319          - OR:
   320            - error_code: 1
   321            - error_code: 21
   322            - min_duration: 2s
   323          - __sampling_fraction: 0.5      
   324  ```
   325  
   326  ##### 对持续时间大于 10s, 且 rpc name 包含 "TDXA/Transfer" 字样,且 error message 里不能包含 "pseudo" 字样的 span 进行提交
   327  
   328  ```yaml
   329  server:
   330    admin:
   331      ip: 127.0.0.1
   332      port: 9028
   333      rpcz:
   334        fraction: 1.0
   335        capacity: 10000
   336        record_when:
   337          - min_duration: 2s
   338          - __rpc_name: "TDXA/Transfer"
   339          - NOT:
   340              __error_message: "pseudo" 
   341          - __sampling_fraction: 1     
   342  ```
   343  
   344  ### 代码配置
   345  
   346  在读取配置文件之后且在服务启动前,可以通过 `rpcz.GlobalRPCZ` 来配置 rpcz,此时的提交采样逻辑需要实现 `ShouldRecord` 函数。
   347  
   348  ```go
   349  // ShouldRecord determines if the Span should be recorded.
   350  type ShouldRecord = func(Span) bool
   351  ```
   352  
   353  ##### 只对包含 "SpecialAttribute" 属性的 Span 进行提交
   354  
   355  ```go
   356  const attributeName = "SpecialAttribute"
   357  rpcz.GlobalRPCZ = rpcz.NewRPCZ(&rpcz.Config{
   358  Fraction: 1.0,
   359  Capacity: 1000,
   360  ShouldRecord: func(s rpcz.Span) bool {
   361  _, ok = s.Attribute(attributeName)
   362  return ok
   363  },
   364  })
   365  ```
   366  
   367  ### 查询最近提交的多条 span 的概要信息
   368  
   369  查询最近 num 个 span 的概要信息,可以访问如下的 url:
   370  
   371  ```html
   372  http://ip:port/cmds/rpcz/spans?num=xxx
   373  ```
   374  
   375  例如执行 `curl http://ip:port/cmds/rpcz/spans?num=2` ,则会返回如下 2 个 span 的概要信息:
   376  
   377  ```html
   378  1:
   379  span: (client, 65744150616107367)
   380  time: (Dec  1 20:57:43.946627, Dec  1 20:57:43.946947)
   381  duration: (0, 319.792µs, 0)
   382  attributes: (RPCName, /trpc.testing.end2end.TestTRPC/EmptyCall),(Error, <nil>)
   383    2:
   384    span: (server, 1844470940819923952)
   385    time: (Dec  1 20:57:43.946677, Dec  1 20:57:43.946912)
   386    duration: (0, 235.5µs, 0)
   387    attributes: (RequestSize, 125),(ResponseSize, 18),(RPCName, /trpc.testing.end2end.TestTRPC/EmptyCall),(Error, success)
   388  ```
   389  
   390  每个 span 的概要信息和如下的模版匹配:
   391  
   392  ```html
   393  span: (name, id)
   394  time: (startTime, endTime)
   395  duration: (preDur, middleDur, postDur)
   396  attributes: (name1, value1) (name2, value2)
   397  ```
   398  
   399  其中每个字段的含义解释如下:
   400  
   401  - name:span 的名字
   402  - id:span 唯一标识,可通过它查询具体的某个 span 详细信息
   403  - startTime:span 的创建时间
   404  - endTime:span 的提交时间,当 span 未被提交成功时,该字段值为 "unknown"
   405  - duration:包含个时间段来描述 currentSpan 和 parentSpan 的耗时
   406    - preDur: currentSpan.startTime - parentSpan.startTime
   407    - middleDur:currentSpan.endTime - currentSpan.startTime,当 currentSpan.endTime 为 "unknown" 时,middleDur 的值也为 "unknown"
   408    - postDur:parentSpan.endTime - currentSpan.endTime,当 parentSpan.endTime 或 currentSpan.endTime 为 "unknown" 时,postDur 的值也为 "unknown"
   409  - attributes:span 的属性值,每一个属性由(属性名,属性值)组成,通常会显示下面三个属性
   410    - RequestSize:请求包大小(byte)
   411    - ResponseSize:响应包大小(byte)
   412    - RPCName:对端的服务名 + 接口名 (/trpc.app.server.service/method)
   413    - Error:错误信息,根据框架返回码判断请求是否成功,success 或 nil 表示成功
   414  
   415  如果不指定查询的个数,则下列查询将会默认返回最近提交成功的 [^1] 10 个 span 的概要信息:
   416  
   417  ```html
   418  http://ip:port/cmds/rpcz/spans
   419  ```
   420  
   421  [^1]: **最近提交的 span 并不是严格按照时间来排序的,可能存在多个 goroutine 同时提交 span,是按照最近提交成功的 span 来排序。**
   422  
   423  ### 查询某个 span 的详细信息
   424  
   425  查询包含 id  的 span 的详细信息,可以访问如下的 url:
   426  
   427  ```html
   428  http://ip:port/cmds/rpcz/spans/{id}
   429  ```
   430  
   431  例如执行 `curl http://ip:port/cmds/rpcz/spans/6673650005084645130` 可查询 span id 为 6673650005084645130 的 span 的详细信息:
   432  
   433  ```
   434  span: (server, 6673650005084645130)
   435    time: (Dec  2 10:43:55.295935, Dec  2 10:43:55.399262)
   436    duration: (0, 103.326ms, 0)
   437    attributes: (RequestSize, 125),(ResponseSize, 18),(RPCName, /trpc.testing.end2end.TestTRPC/EmptyCall),(Error, success)
   438    span: (DecodeProtocolHead, 6673650005084645130)
   439      time: (Dec  2 10:43:55.295940, Dec  2 10:43:55.295952)
   440      duration: (4.375µs, 12.375µs, 103.30925ms)
   441    span: (Decompress, 6673650005084645130)
   442      time: (Dec  2 10:43:55.295981, Dec  2 10:43:55.295982)
   443      duration: (45.875µs, 791ns, 103.279334ms)
   444    span: (Unmarshal, 6673650005084645130)
   445      time: (Dec  2 10:43:55.295982, Dec  2 10:43:55.295983)
   446      duration: (47.041µs, 334ns, 103.278625ms)
   447    span: (filter1, 6673650005084645130)
   448      time: (Dec  2 10:43:55.296161, Dec  2 10:43:55.399249)
   449      duration: (225.708µs, 103.088ms, 12.292µs)
   450      event: (your annotation at pre-filter, Dec  2 10:43:55.296163)
   451      span: (filter2, 6673650005084645130)
   452        time: (Dec  2 10:43:55.296164, Dec  2 10:43:55.399249)
   453        duration: (2.75µs, 103.085ms, 250ns)
   454        event: (your annotation at pre-filter, Dec  2 10:43:55.296165)
   455        span: (server.WithFilter, 6673650005084645130)
   456          time: (Dec  2 10:43:55.296165, Dec  2 10:43:55.399249)
   457          duration: (1.208µs, 103.083625ms, 167ns)
   458          event: (your annotation at pre-filter, Dec  2 10:43:55.296165)
   459          span: (, 6673650005084645130)
   460            time: (Dec  2 10:43:55.296166, Dec  2 10:43:55.399249)
   461            duration: (792ns, 103.082583ms, 250ns)
   462            span: (HandleFunc, 6673650005084645130)
   463              time: (Dec  2 10:43:55.296177, Dec  2 10:43:55.399249)
   464              duration: (11.583µs, 103.070917ms, 83ns)
   465              event: (handling EmptyCallF, Dec  2 10:43:55.296179)
   466              span: (client, 6673650005084645130)
   467                time: (Dec  2 10:43:55.296187, Dec  2 10:43:55.297871)
   468                duration: (9.125µs, 1.684625ms, 101.377167ms)
   469                attributes: (RPCName, /trpc.testing.end2end.TestTRPC/UnaryCall),(Error, <nil>)
   470                span: (filter1, 6673650005084645130)
   471                  time: (Dec  2 10:43:55.296192, Dec  2 10:43:55.297870)
   472                  duration: (5.292µs, 1.678542ms, 791ns)
   473                  span: (client.WithFilter, 6673650005084645130)
   474                    time: (Dec  2 10:43:55.296192, Dec  2 10:43:55.297870)
   475                    duration: (542ns, 1.677875ms, 125ns)
   476                    span: (selector, 6673650005084645130)
   477                      time: (Dec  2 10:43:55.296193, Dec  2 10:43:55.297870)
   478                      duration: (541ns, 1.677209ms, 125ns)
   479                      span: (CallFunc, 6673650005084645130)
   480                        time: (Dec  2 10:43:55.296200, Dec  2 10:43:55.297869)
   481                        duration: (7.459µs, 1.668541ms, 1.209µs)
   482                        attributes: (RequestSize, 405),(ResponseSize, 338)
   483                        span: (Marshal, 6673650005084645130)
   484                          time: (Dec  2 10:43:55.296202, Dec  2 10:43:55.296341)
   485                          duration: (1.375µs, 138.875µs, 1.528291ms)
   486                        span: (Compress, 6673650005084645130)
   487                          time: (Dec  2 10:43:55.296341, Dec  2 10:43:55.296341)
   488                          duration: (140.708µs, 333ns, 1.5275ms)
   489                        span: (EncodeProtocolHead, 6673650005084645130)
   490                          time: (Dec  2 10:43:55.296342, Dec  2 10:43:55.296345)
   491                          duration: (141.458µs, 3.333µs, 1.52375ms)
   492                        span: (SendMessage, 6673650005084645130)
   493                          time: (Dec  2 10:43:55.297540, Dec  2 10:43:55.297555)
   494                          duration: (1.339375ms, 15.708µs, 313.458µs)
   495                        span: (ReceiveMessage, 6673650005084645130)
   496                          time: (Dec  2 10:43:55.297556, Dec  2 10:43:55.297860)
   497                          duration: (1.355666ms, 303.75µs, 9.125µs)
   498                        span: (DecodeProtocolHead, 6673650005084645130)
   499                          time: (Dec  2 10:43:55.297862, Dec  2 10:43:55.297865)
   500                          duration: (1.661916ms, 2.5µs, 4.125µs)
   501                        span: (Decompress, 6673650005084645130)
   502                          time: (Dec  2 10:43:55.297866, Dec  2 10:43:55.297866)
   503                          duration: (1.665583ms, 167ns, 2.791µs)
   504                        span: (Unmarshal, 6673650005084645130)
   505                          time: (Dec  2 10:43:55.297866, Dec  2 10:43:55.297868)
   506                          duration: (1.666041ms, 1.709µs, 791ns)
   507              span: (sleep, 6673650005084645130)
   508                time: (Dec  2 10:43:55.297876, unknown)
   509                duration: (1.698709ms, unknown, unknown)
   510                event: (awake, Dec  2 10:43:55.398954)
   511              span: (client, 6673650005084645130)
   512                time: (Dec  2 10:43:55.398979, Dec  2 10:43:55.399244)
   513                duration: (102.80125ms, 265.417µs, 4.25µs)
   514                attributes: (RPCName, /trpc.testing.end2end.TestTRPC/UnaryCall),(Error, <nil>)
   515                span: (filter2, 6673650005084645130)
   516                  time: (Dec  2 10:43:55.398986, Dec  2 10:43:55.399244)
   517                  duration: (6.834µs, 258.25µs, 333ns)
   518                  span: (client.WithFilter, 6673650005084645130)
   519                    time: (Dec  2 10:43:55.398987, Dec  2 10:43:55.399244)
   520                    duration: (1.708µs, 256.458µs, 84ns)
   521                    span: (selector, 6673650005084645130)
   522                      time: (Dec  2 10:43:55.398988, Dec  2 10:43:55.399244)
   523                      duration: (417ns, 255.916µs, 125ns)
   524                      span: (CallFunc, 6673650005084645130)
   525                        time: (Dec  2 10:43:55.399005, Dec  2 10:43:55.399243)
   526                        duration: (16.833µs, 238.375µs, 708ns)
   527                        attributes: (RequestSize, 405),(ResponseSize, 338)
   528                        span: (Marshal, 6673650005084645130)
   529                          time: (Dec  2 10:43:55.399006, Dec  2 10:43:55.399017)
   530                          duration: (1.792µs, 10.458µs, 226.125µs)
   531                        span: (Compress, 6673650005084645130)
   532                          time: (Dec  2 10:43:55.399017, Dec  2 10:43:55.399017)
   533                          duration: (12.583µs, 167ns, 225.625µs)
   534                        span: (EncodeProtocolHead, 6673650005084645130)
   535                          time: (Dec  2 10:43:55.399018, Dec  2 10:43:55.399023)
   536                          duration: (12.958µs, 4.917µs, 220.5µs)
   537                        span: (SendMessage, 6673650005084645130)
   538                          time: (Dec  2 10:43:55.399041, Dec  2 10:43:55.399070)
   539                          duration: (36.375µs, 29.083µs, 172.917µs)
   540                        span: (ReceiveMessage, 6673650005084645130)
   541                          time: (Dec  2 10:43:55.399070, Dec  2 10:43:55.399239)
   542                          duration: (65.75µs, 168.25µs, 4.375µs)
   543                        span: (DecodeProtocolHead, 6673650005084645130)
   544                          time: (Dec  2 10:43:55.399240, Dec  2 10:43:55.399241)
   545                          duration: (235.417µs, 1.375µs, 1.583µs)
   546                        span: (Decompress, 6673650005084645130)
   547                          time: (Dec  2 10:43:55.399242, Dec  2 10:43:55.399242)
   548                          duration: (237µs, 125ns, 1.25µs)
   549                        span: (Unmarshal, 6673650005084645130)
   550                          time: (Dec  2 10:43:55.399242, Dec  2 10:43:55.399243)
   551                          duration: (237.292µs, 750ns, 333ns)
   552          event: (your annotation at post-filter, Dec  2 10:43:55.399249)
   553        event: (your annotation at post-filter, Dec  2 10:43:55.399249)
   554      event: (your annotation at post-filter, Dec  2 10:43:55.399249)
   555    span: (Marshal, 6673650005084645130)
   556      time: (Dec  2 10:43:55.399250, Dec  2 10:43:55.399251)
   557      duration: (103.314625ms, 1.208µs, 10.167µs)
   558    span: (Compress, 6673650005084645130)
   559      time: (Dec  2 10:43:55.399252, Dec  2 10:43:55.399252)
   560      duration: (103.315958ms, 125ns, 9.917µs)
   561    span: (EncodeProtocolHead, 6673650005084645130)
   562      time: (Dec  2 10:43:55.399252, Dec  2 10:43:55.399253)
   563      duration: (103.316208ms, 750ns, 9.042µs)
   564    span: (SendMessage, 6673650005084645130)
   565      time: (Dec  2 10:43:55.399253, Dec  2 10:43:55.399261)
   566      duration: (103.317333ms, 8.333µs, 334ns)
   567  ```
   568  
   569  在 span 的详细信息中新增了 `event` 字段,以及内嵌的子 span。
   570  
   571  - event:  描述了在某一时刻发生的事情,类似于日志。
   572    可以由你自行插入的事件,如上面例子中的 `Nov  4 14:39:23.594147: your annotation at pre-filter`。
   573  - span:在 server 处理你自定义函数时,可能会创建新的 client 调用下游服务,此时会创建子 span
   574    可以看到,所有的 子 span 都是在 `HandleFunc` 内发生的。
   575  
   576  需要注意的是,endTime、duration 中的 middleDur 和 postDur 的值可能为 "unknown",例如上面的 span 中包含如下的子 span:
   577  
   578  ```
   579  span: (sleep, 6673650005084645130)
   580  time: (Dec  2 10:43:55.297876, unknown)
   581  duration: (1.698709ms, unknown, unknown)
   582  event: (awake, Dec  2 10:43:55.398954)
   583  ```
   584  
   585  ## Span 接口
   586  
   587  你可以先调用 `rpcz.SpanFromContext`[^2] 获取当前 `context` 中的 `Span`,然后使用下面的接口来操作 Span。
   588  
   589  ```go
   590  type Span interface {
   591  // AddEvent adds a event.
   592  AddEvent(name string)
   593  
   594  // SetAttribute sets Attribute with (name, value).
   595  SetAttribute(name string, value interface{})
   596  
   597  // ID returns SpanID.
   598  ID() SpanID
   599  
   600  // NewChild creates a child span from current span.
   601  // Ender ends this span if related operation is completed. 
   602  NewChild(name string) (Span, Ender)
   603  }
   604  ```
   605  
   606  [^2]: 当 `context` 中不含有任何 `span` 的时候会返回一个 `noopSpan`,对 `noopSpan` 的任何操作都是空操作,不会生效。
   607  
   608  ### 使用 AddEvent 来添加事件
   609  
   610  ```go
   611  // If no Span is currently set in ctx an implementation of a Span that performs no operations is returned.
   612  span := SpanFromContext(ctx context.Context)
   613  
   614  span.AddEvent("Acquiring lock")
   615  mutex.Lock()
   616  
   617  span.AddEvent("Got lock, doing work...")
   618  // do some stuff ...
   619  
   620  span.AddEvent("Unlocking")
   621  mutex.Unlock()
   622  ```
   623  
   624  ### 使用 SetAttribute 来设置属性
   625  
   626  ```go
   627  ctx, msg := codec.EnsureMessage(ctx)
   628  span := SpanFromContext(ctx context.Context)
   629  span.SetAttribute("RPCName", msg.ClientRPCName())
   630  span.SetAttribute("Error", msg.ClientRspErr())
   631  ```
   632  
   633  ### 创建新的子 Span
   634  
   635  **特别需要注意的是:创建的子 Span 应该由调用者只调用一次 end.End() 函数来结束子 Span 的生命周期,未调用 End 和 多次调用 End 的行为是未定义的**
   636  
   637  ```go
   638  span := SpanFromContext(ctx context.Context)
   639  cs, end := span.NewChild("Decompress")
   640  reqBodyBuf, err := codec.Decompress(compressType, reqBodyBuf)
   641  end.End()
   642  ```
   643  
   644  ## 参考
   645  
   646  - [1] https://en.wikipedia.org/wiki/Event_(UML)
   647  - [2] https://en.wikipedia.org/wiki/Event_(computing)
   648  - [3] https://opentelemetry.io/docs/instrumentation/go/manual/#events
   649  - [4] https://opentelemetry.io/docs/instrumentation/go/api/tracing/#starting-and-ending-a-span
   650  - [5] https://opentelemetry.io/docs/concepts/observability-primer/#spans
   651  - [6] span-id 用 8 字节的数组表示,满足 w3c trace-context specification. https://www.w3.org/TR/trace-context/#parent-id
   652  - [7] https://en.wiktionary.org/wiki/-z#English
   653  - [8] https://github.com/grpc/proposal/blob/master/A14-channelz.md
   654  - [9] Dapper, a Large-Scale Distributed Systems Tracing Infrastructure: http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/36356.pdf
   655  - [10] brpc-rpcz: https://github.com/apache/incubator-brpc/blob/master/docs/cn/rpcz.md
   656  - [11] opentracing: https://opentracing.io/
   657  - [12] opentelemetry: https://opentelemetry.io/
   658  - [13] open-telemetry-sdk-go-traceIDRatioSampler: https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/trace/sampling.go