idou老師教你學Istio 27:解讀Mixer Report流程
1、概述
Mixer是Istio的核心元件,提供了遙測資料收集的功能,能夠實時採集服務的請求狀態等資訊,以達到監控服務狀態目的。
1.1 核心功能
•前置檢查(Check):某服務接收並響應外部請求前,先透過Envoy向Mixer(Policy元件)傳送Check請求,做一些access檢查,同時確認adaptor所需cache欄位,供之後Report介面使用;
•配額管理(Quota):透過配額管理機制,處理多請求時發生的資源競爭;
•遙測資料上報(Report):該服務請求處理結束後,將請求相關的日誌,監控等資料,透過Envoy上報給Mixer(telemetry)
1.2 示例圖
2、程式碼分析
2.1 Report程式碼分析
本節主要介紹Report的詳細流程(基於Istio release1.0.0版本,commit id為3a136c90)。Report是mixer server的一個接
口,供Envoy透過grpc呼叫。首先,我們從mixer server的啟動入口main函式看起:
func main() { rootCmd := cmd.GetRootCmd(os.Args[1:], supportedTemplates(), supportedAdapters(), shared.Printf, shared. Fatalf) if err := rootCmd.Execute(); err != nil { os.Exit(-1) } }
在rootCmd中,mixs透過server命令啟動了mixer server,從而觸發了runserver函式,在runserver中初始化(New)了一
個server,我們一起看下newServer的函式,在這個函式中,與我們本節相關的內容就是Mixer初始化了一個grpc伺服器
NewGRPCServer。
rootCmd.AddCommand(serverCmd(info, adapters, printf, fatalf)) func serverCmd(info map[string]template.Info, adapters []adapter.InfoFn, printf, fatalf shared.FormatFn) * cobra. Command { sa := server.DefaultArgs() sa.Templates = info sa.Adapters = adapters serverCmd := &cobra.Command{ Use: "server", Short: "Starts Mixer as a server", Run: func(cmd *cobra.Command, args []string) { runServer(sa, printf, fatalf) }, }… … } func newServer(a *Args, p *patchTable) (*Server, error) { grpc.EnableTracing = a.EnableGRPCTracing s.server = grpc.NewServer(grpcOptions...) mixerpb.RegisterMixerServer(s.server, api.NewGRPCServer(s.dispatcher, s.gp, s.checkCache)) }
在這個grpc的服務端中,定義了一個Report介面,這就是我們這節課主要關注的內容(可以看到Check介面也在此定義,我
們下節再講)
func (s *grpcServer) Report(ctx context.Context, req *mixerpb.ReportRequest) (*mixerpb.ReportResponse, error) { lg.Debugf("Report (Count: %d)", len(req.Attributes)) // 校驗attribute是否為空,空則直接return if len(req.Attributes) == 0 { return reportResp, nil } // 若屬性word為空,賦為預設值 for i := 0; i < len(req.Attributes); i++ { iflen(req.Attributes[i].Words) == 0 { req.Attributes[i].Words = req.DefaultWords } } // 根據第一條attribute,生成proto包,proto包能跟蹤一組attributes protoBag := attribute.NewProtoBag(&req.Attributes[0], s.globalDict, s.globalWordList) // 初始化,開始跟蹤attributes各個條目中屬性 accumBag := attribute.GetMutableBag(protoBag) // 儲存accumBag的增量狀態 reportBag := attribute.GetMutableBag(accumBag) reportSpan, reportCtx := opentracing.StartSpanFromContext(ctx, "Report") reporter := s.dispatcher.GetReporter(reportCtx) var errors *multierror.Error for i := 0; i < len(req.Attributes); i++ { span, newctx := opentracing.StartSpanFromContext(reportCtx, fmt.Sprintf("attribute bag %d", i)) // 第一個屬性已經在建立proto包時建立,在此追蹤所有attributes if i > 0 { if err := accumBag.UpdateBagFromProto(&req.Attributes[i], s.globalWordList); err != nil { err = fmt.Errorf("request could not be processed due to invalid attributes: %v", err) span.LogFields(otlog.String("error", err.Error())) span.Finish() errors = multierror.Append(errors, err) break } } lg.Debug("Dispatching Preprocess") // 真正開始分發,預處理階段 if err := s.dispatcher.Preprocess(newctx, accumBag, reportBag); err != nil { err = fmt.Errorf("preprocessing attributes failed: %v", err) span.LogFields(otlog.String("error", err.Error())) span.Finish() errors = multierror.Append(errors, err) continue } lg.Debug("Dispatching to main adapters after running preprocessors") lg.Debuga("Attribute Bag: \n", reportBag) lg.Debugf("Dispatching Report %d out of %d", i+1, len(req.Attributes)) // 真正開始分發,將資料逐步加入到快取中 if err := reporter.Report(reportBag); err != nil { span.LogFields(otlog.String("error", err.Error())) span.Finish() errors = multierror.Append(errors, err) continue } span.Finish() // purge the effect of the Preprocess call so that the next time through everything is clean reportBag.Reset() } reportBag.Done() accumBag.Done() protoBag.Done() // 真正的傳送函式,從快取中取出併傳送到adaptor if err := reporter.Flush(); err != nil { errors = multierror.Append(errors, err) } reporter.Done() if errors != nil { reportSpan.LogFields(otlog.String("error", errors.Error())) } reportSpan.Finish() if errors != nil { lg.Errora("Report failed:", errors.Error()) return nil, grpc.Errorf(codes.Unknown, errors.Error()) } // 過程結束 return reportResp, nil }
透過上述程式碼解讀,我們瞭解了Report介面的工作流程,但此時我們還並不知道一個請求的狀態是如何報給adaptor的,下
面我們透過簡要的函式串接,把這部分流程串起來:
上述的預處理階段Preprocess與上報階段Report,最終都會呼叫到dispatch函式,僅透過不同的type來區分要做的事情;
func (d *Impl) Preprocess(ctx context.Context, bag attribute.Bag, responseBag *attribute.MutableBag) error { s := d.getSession(ctx, tpb.TEMPLATE_VARIETY_ATTRIBUTE_GENERATOR, bag) s.responseBag = responseBag err := s.dispatch() if err == nil { err = s.err } … … } func (r *reporter) Report(bag attribute.Bag) error { s := r.impl.getSession(r.ctx, tpb.TEMPLATE_VARIETY_REPORT, bag) s.reportStates = r.states err := s.dispatch() if err == nil { err = s.err } … … }
而dispatch函式中做了真正的分發動作,包括:
1.遍歷所有adaptor,呼叫adaptor中的函式,針對不同的adaptor生成不同的instance,並將instance快取放入
reportstates
var instance interface{} if instance, err = input.Builder(s.bag); err != nil { log.Errorf("error creating instance: destination='%v', error='%v'", destination.FriendlyName, err) s.err = multierror.Append(s.err, err) continue } type NamedBuilder struct { InstanceShortName string Builder template.InstanceBuilderFn } InstanceBuilderFn func(attrs attribute.Bag) (interface{}, error) CreateInstanceBuilder: func(instanceName string, param proto.Message, expb *compiled.ExpressionBuilder) (template.InstanceBuilderFn, error) builder.build(attr) // For report templates, accumulate instances as much as possible before commencing dispatch. if s.variety == tpb.TEMPLATE_VARIETY_REPORT { state.instances = append(state.instances, instance) continue }
2.將instance分發到所有adaptor,最終呼叫並分發到adaptor的HandleMetric函式中
func (r *reporter) Flush() error { s := r.impl.getSession(r.ctx, tpb.TEMPLATE_VARIETY_REPORT, nil) s.reportStates = r.states s.dispatchBufferedReports() err := s.err … … } func (s *session) dispatchBufferedReports() { // Ensure that we can run dispatches to all destinations in parallel. s.ensureParallelism(len(s.reportStates)) // dispatch the buffered dispatchStates we've got for k, v := range s.reportStates { s.dispatchToHandler(v) delete(s.reportStates, k) } s.waitForDispatched() } func (s *session) dispatchToHandler(ds *dispatchState) { s.activeDispatches++ ds.session = s s.impl.gp.ScheduleWork(ds.invokeHandler, nil) } case tpb.TEMPLATE_VARIETY_REPORT: ds.err = ds.destination.Template.DispatchReport( ctx, ds.destination.Handler, ds.instances) type TemplateInfo struct { Name string Variety tpb.TemplateVariety DispatchReport template.DispatchReportFn DispatchCheck template.DispatchCheckFn DispatchQuota template.DispatchQuotaFn DispatchGenAttrs template.DispatchGenerateAttributesFn } DispatchReport: func(ctx context.Context, handler adapter.Handler, inst []interface{}) error { // Convert the instances from the generic []interface{}, to their specialized type. instances := make([]*metric.Instance, len(inst)) for i, instance := range inst { instances[i] = instance.(*metric.Instance) } // Invoke the handler. if err := handler.(metric.Handler).HandleMetric(ctx, instances); err != nil { return fmt.Errorf("failed to report all values: %v", err) } return nil }
2.2 相關結構體定義
Report介面請求體定義
// Used to report telemetry after performing one or more actions. type ReportRequest struct { // 代表一個請求中的屬性 // 每個attribute代表一個請求動作,多個動作可彙總在一條message中以提高效率 //雖然每個“屬性”訊息在語義上被視為與訊息中的其他屬性無關的獨立獨立實體,但此訊息格式利用屬性訊息之間 的增量編碼,以便大幅減少請求大小並改進端到端 效率。 每組單獨的屬性用於修改前一組。 這消除了在單個請求中 多次冗餘地傳送相同屬性的需要。 // 如果客戶端上報時不想使用增量編碼,可全量的傳送所有屬性. Attributes []CompressedAttributes `protobuf:"bytes,1,rep,name=attributes" json:"attributes"` // 所有屬性的預設訊息級字典. // 這使得可以為該請求中的所有屬性共享相同的字典,這可以大大減少整體請求大小 DefaultWords []string `protobuf:"bytes,2,rep,name=default_words,json=defaultWords" json:"default_words, omitempty"` // 全域性字典的詞條數,可檢測客戶端與服務端之間的全域性字典是否同步 GlobalWordCount uint32 `protobuf:"varint,3,opt,name=global_word_count,json=globalWordCount,proto3" json: "global_word_count,omitempty"` }
3、總結
Mixer中涉及很多快取命中等用於最佳化效能的設計,本文僅介紹了Mixer中Report介面傳送到adaptor的過程,一些效能最佳化
設計,如protobag,dispatch快取等內容,將會在後續文章中解析。
相關服務請訪問https://support.huaweicloud.com/cce/index.html?cce_helpcenter_2019
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69908804/viewspace-2636662/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- idou老師教你學istio 31:Istio-proxy的report流程
- idou老師教你學Istio:如何用 Istio 實現速率限制
- idou老師教你學Istio 28:istio-proxy check 的快取快取
- idou老師教你學Istio 23 : 如何用 Istio 實現速率限制
- idou老師教你學Istio 19 : Istio 流量治理功能原理與實戰
- idou老師教你學Istio :5分鐘簡析Istio異常檢測
- idou老師教你學Istio06: 如何用istio實現流量遷移
- idou老師教你學Istio 22 : 如何用istio實現呼叫鏈跟蹤
- idou老師教你學Istio 07: 如何用istio實現請求超時管理
- idou老師教你學Istio: 如何用Istio實現K8S Egress流量管理K8S
- idou老師教你學Istio :如何用istio實現監控和日誌採集
- idou老師教你學Istio 25:如何用istio實現監控和日誌採集
- idou老師教你學Istio05: 如何用Isito實現智慧路由配置路由
- idou老師教你學Istio 09: 如何用Istio實現K8S Ingress流量管理K8S
- idou老師教你學Istio 26:如何使用Grafana進行視覺化監控Grafana視覺化
- idou老師教你學Istio 17 : 透過HTTPS進行雙向TLS傳輸HTTPTLS
- idou老師教你學Istio 17 : 通過HTTPS進行雙向TLS傳輸HTTPTLS
- idou教你學Istio10 : 如何用Istio實現K8S Egress流量管理K8S
- 分析原始碼:istio mixer原始碼
- Istio Mixer Adapter開發 (二)Istio環境搭建APT
- Istio Mixer Adapter開發 (四)Mixer基本概念與元件APT元件
- Istio Mixer Adapter開發系列 - 概述APT
- Istio Mixer Adapter開發 (三)自定義Mixer Grpc Adapter部署APTRPC
- Istio Mixer元件和服務的重要說明元件
- 【轉】istio原始碼分析——mixer遙測報告原始碼
- Istio技術與實踐04:最佳實踐之教你寫一個完整的Mixer AdapterAPT
- 《老碼農教你學英語》讀者的經驗分享
- 經濟學老師教你選大學專業的13個訣竅
- Istio Mixer Adapter開發 (一)K8S環境搭建APTK8S
- 阿里九年架構師教你如何學會閱讀原始碼阿里架構原始碼
- 內控流程解讀
- 閱讀和實踐是最好的老師
- 上海文華學院 Java老師Java
- Istio Polit-agent & Envoy 啟動流程
- StageFright框架流程解讀框架
- 通過自定義Istio Mixer Adapter在JWT場景下實現使用者封禁APTJWT
- 侯捷老師C++學習路線C++
- 張傳波老師Scrum學習心得Scrum