Appdash原始碼閱讀——RecentStore和LimitStore
Appdash 除了提供 PersistentStore 可持久化檔案儲存外,還提供了基於記憶體的限制儲存兩種,RecentStore 和 LimitStore, 這兩種的 Span Recorder 都有生命週期,到了一定時間或者大小,Appdash 認為這些 Recorder 不會再使用,刪除它們。
當然基於記憶體的 Span Recorder 儲存肯定有很多弊端,比如:報表統計做不到全域性,只能看近期;針對第一種 RecentStore 儲存,它是以時間為維度,進行 Span Recorder 的過期刪除工作。缺點:1. 延展性差;2. 報表具有區域性性等;延展性差是指,當業務量小時,Span Recorder 本身就不多,則生命週期內的 Recorder 就少,不聚集。另一個,當業務量猛增時,生命週期長,則週期內的 Span Recorder 就非常多,容易造成記憶體瓶頸;對於第二種 LimitStore 儲存,它是以記憶體佔用大小為維度,進行 Span Recorder 刪除。它的缺點是當業務量猛增時,Span Recorder 很容易突破設定的記憶體限制,則資料量不全,或者搜尋 traceid 時,無法搜尋到。
因為記憶體儲存的 Span Recorder 都是有限制的,所以肯定會有 Span Recorder 刪除行為,則 Appdash 提供了相關刪除介面
type DeleteStore interface{
// 因為是基於Store interface操作
Store
// 提供了Delete刪除Span Recorder操作
Delete(...ID) error
}
RecentStore
type RecentStore struct {
// 基於時間週期的SpanRecorder刪除
MinEvictAge time.Duration
DeleteStore
// 帶有Span Recorder的生命週期管理
created map[ID]int64
// 上一次刪除的時間
lastEvicted time.Time
mu sync.Mutex
}
// 由上可以看出,RecentStore是利用MinEvictAge和lastEvicted兩個時間,來對Span Recorder進行生命週期的管理。
func (rs *RecentStore) Collect(id SpanID, anns ...Annotation) error) {
... // 併發
// 儲存新來的Span Recorder
if _, present := rs.created[id.Trace]; !present {
rs.created[id.Trace] = time.Now().UnixNano()
}
// 刪除過期的Span Recorder列表
if time.Since(rs.lastEvicted) > rs.MinEvictAge {
rs.evictBefore(time.Now().Add(-1*rs.MinEvictAge))
}
return rs.DeleteStore.Collect(id, anns...)
}
// 刪除小於t時間的Span Recorder。
func (rs *RecentStore) evictBefore(t time.Time) {
evictStart := time.Now()
rs.lastEvicted = evictStart
tnano := t.UnixNano()
var toEvict []ID
for id, ct := range rs.created{
if ct < tnano {
toEvict = append(toEvict, id)
delete(rs.created, id)
}
}
if len(toEvict) ==0 {
return
}
// 刪除記憶體中的Span Recorder列表
go func(){
rs.DeleteStore.Delete(toEvict...)
}()
return
}
由 RecentStore 的 Collect 可以看出,雖然刪除和儲存 Span Recorder 是非同步操作。理論上應該是單獨的 goroutine 去對所有收集到的 Span Recorder 的生命週期進行管理,否則,缺點兩個:
- 如果沒有新的 Span Recorder 到來,則無法觸發過期的 Span Recorder 刪除。
- 如果過期時間 MinEvictAge 設定得很小,則不斷新到來的 Span Recorder 會不斷觸發 goroutine 操作,這個也是不合理的。
LimitStore
type LimitStore struct {
// 儲存的Span Recorder最大數量
Max int
DeleteStore
mu sync.Mutex
// 帶Max的Span Recorder生命週期管理
traces map[ID]struct{}
// 通過環狀儲存管理
ring []int64
// 下一個插入Span Recorder位置
nextInsertIdx int
}
// 對於LimitStore儲存,它是基於儲存Span Recorder最大數量的維度來維護所有的Span Recorder。它是通過traces、ring和nextInsertIdx兩個儲存來維護的。traces用來表示trace是否存在;ring和nextInsertIdx用來表示環插入trace維護
其實,環traces的維護只需要ring陣列和nextInsertIdx兩個變數來維護,但是在ring陣列中查詢traceid太慢,所以引入了traces map結構儲存,時間複雜度為o(1)
func (ls *LimitStore) Collect(id SpanID, anns ...Annotation) error {
... // 併發
if ls.ring ==nil {
ls.ring = make([]int64, ls.Max)
ls.traces = make(map[ID]struct{}, ls.Max)
}
// 獲取traceid是否存在, 存在即更新
if _, ok := ls.traces[id.Trace]; ok {
return ls.DeleteStore.Collect(id, anns...)
}
// 如果ring環的下個插入位置不為0,則表示ring環滿了,需要覆蓋(刪除並插入)
if nextInsert := ls.ring[ls.nextInsertIdx]; nextInsert !=0 {
old := ID(ls.ring[ls.nextInsertIdx])
delete(r.traces, old)
ls.DeleteStore.Delete(old)
}
// 插入
ls.traces[id.Trace] = struct{}{}
ls.ring[ls.nextInsertIdx] = int64(id.Trace)
ls.nextInsertIdx = (ls.nextInsertIdx+1)%ls.Max
return ls.DeleteStore.Collect(id, anns...)
}
由此可看,RecentStore 和 LimitStore 都是基於 DeleteStore 實現,且 DeleteStore interface 都是在 Store 上新增了 Delete 方法實現。而 MemoryStore 持久化儲存則不僅實現了本地檔案儲存,還實現了 DeleteStore interface。所以 RecentStore 和 LimitStore 都是基於 MemoryStore 實現的。
MemoryStore 的定期本地檔案儲存,策略不夠豐富,目前只支援一種策略:記憶體全域性寫入檔案,因為 MemoryStore 限制有寫入磁碟的有效時間,一旦過期,則會導致有些 trace 無法落地磁碟;而且每次全域性寫入,持久化需要的時間過長。
其中 RecentStore 的設計和實現是存在缺陷的。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Appdash原始碼閱讀——reflectAPP原始碼
- Appdash原始碼閱讀——Annotations與EventAPP原始碼
- Appdash原始碼閱讀——Recorder與CollectorAPP原始碼
- Appdash原始碼閱讀——Store儲存APP原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- Appdash原始碼閱讀——部分opentracing支援APP原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- ReactorKit原始碼閱讀React原始碼
- AQS原始碼閱讀AQS原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- NGINX原始碼閱讀Nginx原始碼
- Mux 原始碼閱讀UX原始碼
- HashMap原始碼閱讀HashMap原始碼
- fuzz原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- express 原始碼閱讀Express原始碼
- muduo原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- redux 和 react-redux 部分原始碼閱讀ReduxReact原始碼
- PHP原始碼閱讀:strtolower 和 strtoupper 函式PHP原始碼函式
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- Laravel 原始碼閱讀 - QueueLaravel原始碼
- Vollery原始碼閱讀(—)原始碼
- 使用OpenGrok閱讀原始碼原始碼
- 如何閱讀Java原始碼?Java原始碼
- buffer 原始碼包閱讀原始碼
- 原始碼閱讀技巧篇原始碼
- 如何閱讀框架原始碼框架原始碼
- 再談原始碼閱讀原始碼
- Laravel 原始碼閱讀 - EloquentLaravel原始碼