basictracer-go原始碼閱讀二——Span
Span
span interface擴充套件了opentracing-go最小子集,新增了兩個方法
type Span interface{
opentracing.Span
// 獲取operation name
Operation() string
// 建立span的時間
Start() time.Time
}
Span Impl
spanImpl實現了Span interface, span的建立在《basictracer原始碼閱讀——TracerImpl》最後一說明,通過sync.Pool臨時物件池建立小物件;
大家如果想對sync.Pool有更深入的理解,可以看看《真有趣達達寫的slab》, 非常有意思,特別是無鎖的小物件池。
type spanImpl struct {
tracer *tracerImpl
event func(SpanEvent)
// span是否存在併發,取決於執行單元的粗細粒度,如果跨goroutine使用,則會存在併發
sync.Mutex
// 記錄了OpenTracing標準中Span最小子集的所有資訊
raw RawSpan
// 這個span因為在tracerImpl中設定了MaxLogsPerSpan,儲存被丟棄的日誌記錄數
numDroppedLogs int
}
type RawSpan struct {
// Baggage資訊,並且還攜帶了TraceID、SpanID和Sampled
Context SpanContext
// 父SpanID,如果當前Span為tracer的頭結點,則ParentSpanID=0
ParentSpanID uint64
// Span的Operation name
Operation string
// 建立Span的時間
Start time.Time
// Span執行單元的時間長度
Duration time.Duration
// 本身Tags資訊
Tags opentracing.Tags
// 日誌事件記錄列表
Logs []opentracing.LogRecord
}
// 設定span的操作名稱
func (s *spanImpl) SetOperationName(operationName string) opentracing.Span {
... // 注意:併發上鎖
}
// 當span不需要被記錄時,判定是否要對span的tags進行本地儲存
func (s *spanImpl) trim() bool{
return !s.row.Context.Sampled && s.tracer.options.TrimUnsampledSpans
}
// 為span設定tags
// 注意:當為span儲存sampling.priority時,這個值是儲存在spanImpl的屬性rawspan中的sampled值,所以它在basictracer實現中並沒有寫入tags;
// 另外對於basictracer的實現,值得說的一點是:
// 1. 在tracerImpl中的Options中已經通過ShouldSample函式指定該tracer是否被跟蹤;
// 2. 同時, 無論是否有tracer前置條件,span本身都可以選擇是否記錄並儲存
func (s *spanImpl) SetTag(key string, value interface{}) opentracing.Span{
defer s.onTag(key, value)
... // 注意:併發上鎖
}
// 通過key-value鍵值對列表轉換為[]log.Field, 並儲存到spanImpl Logs中
func (s *spanImpl) LogKV(keyValues ...interface{}) {
...
// 當發生錯誤時,記錄錯誤日誌的錯誤資訊和錯誤位置
s.LogFields(log.Error(err), log.String("function", "LogKV")
}
// Span當發生日誌記錄時,格式如下:
log:
time: 2006-01-02 15:04:05 log.Fields:
[
{key: function, value: LogKV}
{key: error, value: "non-even keyValues len: 3"}
]
time: 2006-01-02 15:04:08 log.Fields:
[
{key: update, value: record not found}
]
// 當span.raw.Logs列表長度大於等於tracer.Options.MaxLogsPerSpan(非零)時,這需要丟棄日誌
// 這裡丟棄日誌的做法第一次見到,感覺還不錯:
// 它是把一個列表中上半部分的日誌反覆覆蓋寫,這樣的話,有個問題日誌出現開頭部分連續和結尾部分連續,中間斷層。你可以通過問題發生的地方和結尾地方開始追蹤,並在分散式日誌管理系統中,根據trace的相關開始和結束日誌,定位時間區域,和問題開始和結束的位置和原因等, 中間斷層的日誌在具體的日誌系統中查詢定位。
func (s *spanImpl) appendLog(lr opentracing.LogRecord) {
...
numOld:=(maxLogs -1 ) /2
numNew = maxLogs - numOld
s.raw.Logs[numOld+(s.numDroppedLogs%numNew)]=lr
s.numDroppedLogs++
}
// 該方法記錄一個錯誤發生時,相關的呼叫棧具體日誌錯誤位置和錯誤資訊
func (s *spanImpl) LogFields(fields ...log.Field) {
...
}
func (s *spanImpl) Finish() {
s.FinishWithOptions(opentracing.FinishOptions{}
}
func (s *spanImpl)FinishWithOptions(opts opentracing.FinishOptions) {
...
// 這裡涉及到一個列表旋轉的演算法, 本節下面有旋轉介紹和理解
rotateLogBuffer(s.raw.Logs[numOld:], s.numDroppedLogs%numNew)
// 做完之後,還要把span釋放到sync.Pool臨時物件池中複用 spanPool.Put(s)
}
乍一看,臥槽,這個旋轉有毛線用,再仔細理解理解appendLog方法;會再來一句,這想法牛逼;
為啥牛逼呢?
因為appendLog方法在span.RawSpan.Logs溢位後,以後都會產生日誌丟棄行為,而且是從後半段丟棄和重寫,這樣後半段反覆覆蓋重寫,注意一點,我們在LogRecord儲存時,需要保證日誌輸出的時間有序性,但是後半段反覆覆蓋寫,則會導致一種情況:
當(s.numDroppedLogs)/numNew>1時,則會出現後半段的前N個日誌時間戳大於後面的日誌時間戳,這個分界線的位置則是:s.numDroppedLogs%numNew,所以需要這個左邊位置向左旋轉N。
這樣的話,後半段日誌記錄時間戳就是有序遞增的,那麼輸出也就是有序的
spanImpl中的LogEvent, LogEventWithPayload, Log需要廢棄,因為標準中opentracing-go的LogData已廢棄,只保留LogKVs和LogFields方法。
對於陣列/列表旋轉演算法,可以採用C++中的官方演算法庫rotate left,《STL 原始碼分析》有助於這個演算法的理解
這個演算法是採用前向移動, 且每次移動[first, pos]大小固定資料單元
一維旋轉,向(左|右)旋轉N位的理解:
把一維陣列分為兩部分,則它當前由三部分構成:左半部分和右半部分;
記住一點:左半部分是整體、右半部分也是整體。
例如:字串abcdefg ,向(左|右)旋轉3位:
1. 向左旋轉3位:
整體可以看成:A'C'。其中:A'表示abc, C'表示defg, 則左旋轉結果為:C'A', 展開結果為:efgdabc
2. 向右旋轉3位:
整體可以看成:A'C'。其中:A'表示abcd,C'表示efg,則右旋轉結果為:C'A', 展開結果為:efgabcd
參考資料
相關文章
- basictracer-go原始碼閱讀——SpanRecorder與wireGo原始碼
- basictracer-go原始碼閱讀——examples(完結)Go原始碼
- basictracer-go原始碼閱讀——event&propagationGo原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- Vollery原始碼閱讀(二)原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 原始碼閱讀:SDWebImage(二)——SDWebImageCompat原始碼Web
- RIPS原始碼閱讀記錄(二)原始碼
- 原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization原始碼
- 【詳解】ThreadPoolExecutor原始碼閱讀(二)thread原始碼
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- Spring原始碼閱讀——ClassPathXmlApplicationContext(二)Spring原始碼XMLAPPContext
- 逐行閱讀redux原始碼(二)combineReducersRedux原始碼
- Koa原始碼閱讀(二)上下文ctx原始碼
- ReactorKit原始碼閱讀React原始碼
- AQS原始碼閱讀AQS原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- NGINX原始碼閱讀Nginx原始碼
- Mux 原始碼閱讀UX原始碼
- HashMap原始碼閱讀HashMap原始碼
- fuzz原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- express 原始碼閱讀Express原始碼
- muduo原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- TiCDC 原始碼閱讀(二)TiKV CDC 模組介紹原始碼
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- Laravel 原始碼閱讀 - QueueLaravel原始碼
- Vollery原始碼閱讀(—)原始碼
- 使用OpenGrok閱讀原始碼原始碼
- 如何閱讀Java原始碼?Java原始碼