idou老師教你學Istio 27:解讀Mixer Report流程

CCE_huawei發表於2019-02-22

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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章