skywalking-go 原理剖析
版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | 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 docontext-copy
from the previous goroutine to the newly created goroutine. The new context in thegoroutine
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