Fork me on GitHub

版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://vearne.cc

本文代码示例基于 skywalking-go v0.3.0

1. 引言

apache/skywalking-go 的内部实现比较复杂,本文针对几个核心的点进行展开,以便于大家更好的阅读和理解。

2.TracingID生成

详细参考代码 id.go

skywalking-go中的TraceID形如: “ab95670cae9d11ee87ca5626e1cdcfe2.14.39165238789840005”

一共由4部分组成:

  • 1)uuid 使用uuid version1
  • 2)goroutine ID,golang协程ID
  • 3)timestamp 自1900年以来的毫秒值
  • 4)sequence, 从0 ~ 10000

3. 跨协程传递Tracing信息

在没有skywalking-go的情况下,如果想在多个协程中传递Tracing信息, 开发者必须要用到context。使用skywalking-go之后,很多细节,开发者无需关心。

下面是一个在gin中使用多协程的示例,完整代码见参考资料2

    r.GET("/ping", func(c *gin.Context) {
        g, _ := errgroup.WithContext(context.Background())

        g.Go(func() error {
            val, err := rdb.Incr(context.Background(), "helloCounter2").Result()
            zlog.Info("ping", zap.Int64("val", val), zap.Error(err))
            return nil
        })
        g.Go(func() error {
            hsetRes, err := rdb.HSet(context.Background(), "xyz", "def", 0).Result()
            zlog.Info("ping", zap.Int64("setRes", hsetRes), zap.Error(err))
            return nil
        })
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })

Besides using g object as the in-goroutine context propagation, SkyWalking builds a mechanism to propagate context between Goroutines.

When a new goroutine is started on an existing goroutine, the runtime.newproc1 method is called to create a new goroutine based on the existing one. The agent would do context-copy from the previous goroutine to the newly created goroutine. The new context in the goroutine only shares limited information to help continues tracing.

skywalking-go 对协程G对应的数据结构进行了增强

type g struct {
    ...
    // Tracing信息保存在skywalking_tls中
    skywalking_tls interface{}
}

newproc1() 用于创建子协程

proc.go

func newproc1(fn *funcval, callergp *g, callerpc uintptr) (skywalking_result_0 *g) {
    defer func() {
            // 把父协程的tracing信息传递给子协程
        skywalking_result_0.skywalking_tls = goroutineChange(callergp.skywalking_tls)
    }()

    if fn == nil {
        fatal("go of nil func value")
    }
    ...
}
func goroutineChange(tls interface{}) interface{} {
   if tls == nil {
      return nil
   }
   if taker, ok := tls.(ContextSnapshoter); ok {
      return taker.TakeSnapShot(tls)
   }
   return tls
}

对应的skywalking-go代码位于runtime/instrument.go

4. 跨服务传递Tracing信息

跨服务传递Tracing信息都是在plugin中实现的,以net/http请求为例

Client端 plugins/http/client_intercepter.go

func (h *ClientInterceptor) BeforeInvoke(invocation operator.Invocation) error {
    request := invocation.Args()[0].(*http.Request)
    s, err := tracing.CreateExitSpan(fmt.Sprintf("%s:%s", request.Method, request.URL.Path), request.Host, 
        // 在Header中添加新的键值对,传递Tracing信息给其他微服务
    func(headerKey, headerValue string) error {
        request.Header.Add(headerKey, headerValue)
        return nil
    }, tracing.WithLayer(tracing.SpanLayerHTTP),
        tracing.WithTag(tracing.TagHTTPMethod, request.Method),
        tracing.WithTag(tracing.TagURL, request.Host+request.URL.Path),
        tracing.WithComponent(5005))
    ...
    return nil
}

Server端 plugins/http/server_intercepter.go

func (h *ServerInterceptor) BeforeInvoke(invocation operator.Invocation) error {
    request := invocation.Args()[1].(*http.Request)
    s, err := tracing.CreateEntrySpan(fmt.Sprintf("%s:%s", request.Method, request.URL.Path), 
        // 从Http Header中提取Tracing信息 
    func(headerKey string) (string, error) {
        return request.Header.Get(headerKey), nil
    }, tracing.WithLayer(tracing.SpanLayerHTTP),
        tracing.WithTag(tracing.TagHTTPMethod, request.Method),
        tracing.WithTag(tracing.TagURL, request.Host+request.URL.Path),
        tracing.WithComponent(5004))
    ...
    return nil
}

细心的读者可能留意到Skywalking-go中,

  • 所有需要向其他服务传递Tracing信息的情况 ,使用的都是
func CreateExitSpan(operationName, peer string, injector Injector, opts ...SpanOption) (s Span, err error)

injector is the injector to inject the context into the carrier.

  • 所有需要从请求中提取Tracing信息的情况,使用的都是
func CreateEntrySpan(operationName string, extractor Extractor, opts ...SpanOption) (s Span, err error)

extractor is the extractor to extract the context from the carrier.

server端收到的Header信息

User-Agent: go-resty/2.10.0 (https://github.com/go-resty/resty)
Sw8: 1-NjE2NTUzNGFhZjY1MTFlZWJmYzc1NjI2ZTFjZGNmZTIuMTMxLjM5MTY2MjAyNjQ2NzQwMDAz-NjE2NTUzNGFhZjY1MTFlZWJmYzc1NjI2ZTFjZGNmZTIuMTMxLjM5MTY2MjAyNjQ2NzQwMDA0-2-c3ctdGVzdA==-NjE2NTczNzBhZjY1MTFlZWJmYzc1NjI2ZTFjZGNmZTJAMTAuMi4xMzAuMTQ=-R0VUOi9zYXlIZWxsb0h0dHA=-bG9jYWxob3N0OjE4MDAx
Sw8-Correlation: 
Accept-Encoding: gzip

序列化和反序列化的代码在plugins/core/propagating.go

func (s *SpanContext) DecodeSW8(header string) error {
    ...
}
func (s *SpanContext) EncodeSW8() string {
    return strings.Join([]string{
        strconv.Itoa(int(s.Sample)),
        encodeBase64(s.TraceID),
        encodeBase64(s.ParentSegmentID),
        strconv.Itoa(int(s.ParentSpanID)),
        encodeBase64(s.ParentService),
        encodeBase64(s.ParentServiceInstance),
        encodeBase64(s.ParentEndpoint),
        encodeBase64(s.AddressUsedAtClient),
    }, "-")
}

下图是请求http://localhost:8000/sayHelloHttp 获取的Tracing图表

5. 参考资料

1.context-propagation-between-goroutines
2.vearne/sw-test


微信公众号

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据