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函数看起:

  1. func main() {
  2.    rootCmd := cmd.GetRootCmd(os.Args[1:], supportedTemplates(), supportedAdapters(), shared.Printf, shared.Fatalf)
  3.  
  4.    if err := rootCmd.Execute(); err != nil {
  5.       os.Exit(-1)
  6.    }
  7. }

在rootCmd中,mixs通过server命令启动了mixer server,从而触发了runserver函数,在runserver中初始化(New)了一个server,我们一起看下newServer的函数,在这个函数中,与我们本节相关的内容就是Mixer初始化了一个grpc服务器NewGRPCServer。

  1. rootCmd.AddCommand(serverCmd(info, adapters, printf, fatalf))
  2. func serverCmd(info map[string]template.Info, adapters []adapter.InfoFn, printf, fatalf shared.FormatFn) *cobra.Command {
  3.    sa := server.DefaultArgs()
  4.    sa.Templates = info
  5.    sa.Adapters = adapters
  6.  
  7.    serverCmd := &cobra.Command{
  8.       Use:   "server",
  9.       Short: "Starts Mixer as a server",
  10.       Run: func(cmd *cobra.Command, args []string) {
  11.          runServer(sa, printf, fatalf)
  12.       },
  13.    }… 
  14. }
  15. func newServer(*Args, p *patchTable) (*Server, error) {
  16.    grpc.EnableTracing = a.EnableGRPCTracing
  17.    s.server = grpc.NewServer(grpcOptions...)
  18.    mixerpb.RegisterMixerServer(s.server, api.NewGRPCServer(s.dispatcher, s.gp, s.checkCache))
  19. }

在这个grpc的服务端中,定义了一个Report接口,这就是我们这节课主要关注的内容(可以看到Check接口也在此定义,我们下节再讲)

  1. func (*grpcServer) Report(ctx context.Context, req *mixerpb.ReportRequest) (*mixerpb.ReportResponse, error) {
  2.    lg.Debugf("Report (Count: %d)", len(req.Attributes))
  3.    // 校验attribute是否为空,空则直接return
  4.    if len(req.Attributes) == 0 {
  5.       return reportResp, nil
  6.    }
  7.  
  8.    // 若属性word为空,赋为默认值
  9.    for i := 0; i < len(req.Attributes); i++ {
  10.       iflen(req.Attributes[i].Words) == 0 {
  11.          req.Attributes[i].Words = req.DefaultWords
  12.       }
  13.    }
  14.  
  15.    // 根据第一条attribute,生成proto包,proto包能跟踪一组attributes
  16.    protoBag := attribute.NewProtoBag(&req.Attributes[0], s.globalDict, s.globalWordList)
  17.  
  18.    // 初始化,开始跟踪attributes各个条目中属性
  19.    accumBag := attribute.GetMutableBag(protoBag)
  20.  
  21.    // 保存accumBag的增量状态
  22.    reportBag := attribute.GetMutableBag(accumBag)
  23.  
  24.    reportSpan, reportCtx := opentracing.StartSpanFromContext(ctx, "Report")
  25.    reporter := s.dispatcher.GetReporter(reportCtx)
  26.  
  27.    var errors *multierror.Error
  28.    for i := 0; i < len(req.Attributes); i++ {
  29.       span, newctx := opentracing.StartSpanFromContext(reportCtx, fmt.Sprintf("attribute bag %d", i))
  30.  
  31.       // 第一个属性已经在创建proto包时创建,在此追踪所有attributes
  32.       if i > 0 {
  33.          if err := accumBag.UpdateBagFromProto(&req.Attributes[i], s.globalWordList); err != nil {
  34.             err = fmt.Errorf("request could not be processed due to invalid attributes: %v", err)
  35.             span.LogFields(otlog.String("error", err.Error()))
  36.             span.Finish()
  37.             errors = multierror.Append(errors, err)
  38.             break
  39.          }
  40.       }
  41.  
  42.       lg.Debug("Dispatching Preprocess")
  43.       // 真正开始分发,预处理阶段
  44.       if err := s.dispatcher.Preprocess(newctx, accumBag, reportBag); err != nil {
  45.          err = fmt.Errorf("preprocessing attributes failed: %v", err)
  46.          span.LogFields(otlog.String("error", err.Error()))
  47.          span.Finish()
  48.          errors = multierror.Append(errors, err)
  49.          continue
  50.       }
  51.  
  52.       lg.Debug("Dispatching to main adapters after running preprocessors")
  53.       lg.Debuga("Attribute Bag: \n", reportBag)
  54.       lg.Debugf("Dispatching Report %d out of %d", i+1, len(req.Attributes))
  55.       // 真正开始分发,将数据逐步加入到缓存中
  56.       if err := reporter.Report(reportBag); err != nil {
  57.          span.LogFields(otlog.String("error", err.Error()))
  58.          span.Finish()
  59.          errors = multierror.Append(errors, err)
  60.          continue
  61.       }
  62.  
  63.       span.Finish()
  64.  
  65.       // purge the effect of the Preprocess call so that the next time through everything is clean
  66.       reportBag.Reset()
  67.    }
  68.  
  69.    reportBag.Done()
  70.    accumBag.Done()
  71.    protoBag.Done()
  72.    // 真正的发送函数,从缓存中取出并发送到adaptor
  73.    if err := reporter.Flush(); err != nil {
  74.       errors = multierror.Append(errors, err)
  75.    }
  76.    reporter.Done()
  77.  
  78.    if errors != nil {
  79.       reportSpan.LogFields(otlog.String("error", errors.Error()))
  80.    }
  81.    reportSpan.Finish()
  82.  
  83.    if errors != nil {
  84.       lg.Errora("Report failed:", errors.Error())
  85.       return nil, grpc.Errorf(codes.Unknown, errors.Error())
  86.    }
  87.    // 过程结束
  88.    return reportResp, nil
  89. }

通过上述代码解读,我们了解了Report接口的工作流程,但此时我们还并不知道一个请求的状态是如何报给adaptor的,下面我们通过简要的函数串接,把这部分流程串起来:

上述的预处理阶段Preprocess与上报阶段Report,最终都会调用到dispatch函数,仅通过不同的type来区分要做的事情;

  1. func (*Impl) Preprocess(ctx context.Context, bag attribute.Bag, responseBag *attribute.MutableBag) error {
  2.    s := d.getSession(ctx, tpb.TEMPLATE_VARIETY_ATTRIBUTE_GENERATOR, bag)
  3.    s.responseBag = responseBag
  4.    err := s.dispatch()
  5.    if err == nil {
  6.       err = s.err
  7.    }
  8.     
  9. }
  10. func (*reporter) Report(bag attribute.Bag) error {
  11.    s := r.impl.getSession(r.ctx, tpb.TEMPLATE_VARIETY_REPORT, bag)
  12.    s.reportStates = r.states
  13.    err := s.dispatch()
  14.    if err == nil {
  15.       err = s.err
  16.    }
  17.     
  18. }

而dispatch函数中做了真正的分发动作,包括:

1.遍历所有adaptor,调用adaptor中的函数,针对不同的adaptor生成不同的instance,并将instance缓存放入reportstates

  1. var instance interface{}
  2. if instance, err = input.Builder(s.bag); err != nil {
  3.    log.Errorf("error creating instance: destination='%v', error='%v'", destination.FriendlyName, err)
  4.    s.err = multierror.Append(s.err, err)
  5.    continue
  6. }
  7. type NamedBuilder struct {
  8.    InstanceShortName string
  9.    Builder           template.InstanceBuilderFn
  10. }
  11. InstanceBuilderFn func(attrs attribute.Bag) (interface{}, error)
  12. CreateInstanceBuilder: func(instanceName string, param proto.Message, expb *compiled.ExpressionBuilder) (template.InstanceBuilderFn, error)
  13. builder.build(attr)
  14. // For report templates, accumulate instances as much as possible before commencing dispatch.
  15. if s.variety == tpb.TEMPLATE_VARIETY_REPORT {
  16.    state.instances = append(state.instances, instance)
  17.    continue
  18. }

2.将instance分发到所有adaptor,最终调用并分发到adaptor的HandleMetric函数中

  1. func (*reporter) Flush() error {
  2.    s := r.impl.getSession(r.ctx, tpb.TEMPLATE_VARIETY_REPORT, nil)
  3.    s.reportStates = r.states
  4.  
  5.    s.dispatchBufferedReports()
  6.    err := s.err
  7.     
  8. }
  9. func (*session) dispatchBufferedReports() {
  10.    // Ensure that we can run dispatches to all destinations in parallel.
  11.    s.ensureParallelism(len(s.reportStates))
  12.  
  13.    // dispatch the buffered dispatchStates we've got
  14.    for k, v := range s.reportStates {
  15.       s.dispatchToHandler(v)
  16.       delete(s.reportStates, k)
  17.    }
  18.  
  19.    s.waitForDispatched()
  20. }
  21. func (*session) dispatchToHandler(ds *dispatchState) {
  22.    s.activeDispatches++
  23.    ds.session = s
  24.    s.impl.gp.ScheduleWork(ds.invokeHandler, nil)
  25. }
  26. case tpb.TEMPLATE_VARIETY_REPORT:
  27.    ds.err = ds.destination.Template.DispatchReport(
  28.       ctx, ds.destination.Handler, ds.instances)
  29. type TemplateInfo struct {
  30.    Name             string
  31.    Variety          tpb.TemplateVariety
  32.    DispatchReport   template.DispatchReportFn
  33.    DispatchCheck    template.DispatchCheckFn
  34.    DispatchQuota    template.DispatchQuotaFn
  35.    DispatchGenAttrs template.DispatchGenerateAttributesFn
  36. }
  37. DispatchReport: func(ctx context.Context, handler adapter.Handler, inst []interface{}) error {
  38.  
  39.    // Convert the instances from the generic []interface{}, to their specialized type.
  40.    instances := make([]*metric.Instance, len(inst))
  41.    for i, instance := range inst {
  42.       instances[i] = instance.(*metric.Instance)
  43.    }
  44.  
  45.    // Invoke the handler.
  46.    if err := handler.(metric.Handler).HandleMetric(ctx, instances); err != nil {
  47.       return fmt.Errorf("failed to report all values: %v", err)
  48.    }
  49.    return nil
  50. }

2.2 相关结构体定义

Report接口请求体定义

  1. // Used to report telemetry after performing one or more actions.
  2. type ReportRequest struct {
  3.    // 代表一个请求中的属性
  4.    // 每个attribute代表一个请求动作,多个动作可汇总在一条message中以提高效率
  5.    //虽然每个“属性”消息在语义上被视为与消息中的其他属性无关的独立独立实体,但此消息格式利用属性消息之间的增量编码,以便大幅减少请求大小并改进端到端 效率。 每组单独的属性用于修改前一组。 这消除了在单个请求中多次冗余地发送相同属性的需要。
  6.    // 如果客户端上报时不想使用增量编码,可全量的发送所有属性.
  7.    Attributes []CompressedAttributes `protobuf:"bytes,1,rep,name=attributes" json:"attributes"`
  8.    // 所有属性的默认消息级字典.
  9.    // 这使得可以为该请求中的所有属性共享相同的字典,这可以大大减少整体请求大小
  10.    DefaultWords []string `protobuf:"bytes,2,rep,name=default_words,json=defaultWords" json:"default_words,omitempty"`
  11.    // 全局字典的词条数,可检测客户端与服务端之间的全局字典是否同步
  12.    GlobalWordCount uint32 `protobuf:"varint,3,opt,name=global_word_count,json=globalWordCount,proto3" json:"global_word_count,omitempty"`
  13. }

3、总结

Mixer中涉及很多缓存命中等用于优化性能的设计,本文仅介绍了Mixer中Report接口发送到adaptor的过程,一些性能优化设计,如protobag,dispatch缓存等内容,将会在后续文章中解析。

相关服务请访问https://support.huaweicloud.com/cce/index.html?cce_helpcenter_2019

idou老师教你学Istio 27:解读Mixer Report流程的更多相关文章

  1. idou老师教你学Istio 29:Envoy启动流程

    1. 功能概述 Envoy启动时,会启动一个进程,并在这个进程中启动很多线程,这样,可以启动很多worker线程,一般worker线程数与核心数相同,每个worker线程处理所有已配置的listene ...

  2. idou老师教你学Istio 07: 如何用istio实现请求超时管理

    在前面的文章中,大家都已经熟悉了Istio的故障注入和流量迁移.这两个方面的功能都是Istio流量治理的一部分.今天将继续带大家了解Istio的另一项功能,关于请求超时的管理. 首先我们可以通过一个简 ...

  3. idou老师教你学Istio 16:如何用 Istio 实现微服务间的访问控制

    摘要 使用 Istio 可以很方便地实现微服务间的访问控制.本文演示了使用 Denier 适配器实现拒绝访问,和 Listchecker 适配器实现黑白名单两种方法. 使用场景 有时需要对微服务间的相 ...

  4. idou老师教你学Istio 04:Istio性能及扩展性介绍

    Istio的性能问题一直是国内外相关厂商关注的重点,Istio对于数据面应用请求时延的影响更是备受关注,而以现在Istio官方与相关厂商的性能测试结果来看,四位数的qps显然远远不能满足应用于生产的要 ...

  5. idou老师教你学Istio 28:istio-proxy check 的缓存

    功能概述 istio-proxy主要的功能是连接istio的控制面组件和envoy之间的交互,其中check的功能是将envoy收集的attributes信息上报给mixer,在istio中有几十种a ...

  6. idou老师教你学Istio :如何用istio实现监控和日志采集

    大家都知道istio可以帮助我们实现灰度发布.流量监控.流量治理等功能.每一个功能都帮助我们在不同场景中实现不同的业务.那Istio是如何帮助我们实现监控和日志采集的呢? 这里我们依然以Bookinf ...

  7. idou老师教你学istio :基于角色的访问控制

    istio的授权功能,也称为基于角色的访问控制(RBAC),它为istio服务网格中的服务提供命名空间级别.服务级别和方法级别的访问控制.基于角色的访问控制具有简单易用.灵活和高性能等特性.本文介绍如 ...

  8. idou老师教你学Istio 17 : 通过HTTPS进行双向TLS传输

    众所周知,HTTPS是用来解决 HTTP 明文协议的缺陷,在 HTTP 的基础上加入 SSL/TLS 协议,依靠 SSL 证书来验证服务器的身份,为客户端和服务器端之间建立“SSL”通道,确保数据运输 ...

  9. idou老师教你学Istio: 如何用Istio实现K8S Egress流量管理

    本文主要介绍在使用Istio时如何访问集群外服务,即对出口流量的管理. 默认安装的Istio是不能直接对集群外部服务进行访问的,如果需要将外部服务暴露给 Istio 集群中的客户端,目前有两种方案: ...

随机推荐

  1. spring mvc框架+ ajax实现 文件上传

    1.前端页面,通过form表单提交,必须设置 enctype="multipart/form-data" 代表form表单在发送到服务器时候编码方式是二进制类型,一般用于图片.mp ...

  2. Linux 学习笔记 1 使用最小的系统,从分区安装系统开始

    我们常用的linux系统在安装过程中大多都省略了对系统进行分区的操作,以至于后期,不了解什么是分区以及分区当中最基本的一些概念, 我们不说最细的知识,只求了解这个过程,那直接步入正题,开始第一节的学习 ...

  3. codevs1227:方格取数2

    题目描述 Description 给出一个n*n的矩阵,每一格有一个非负整数Aij,(Aij <= )现在从(,)出发,可以往右或者往下走,最后到达(n,n),每达到一格,把该格子的数取出来,该 ...

  4. JVM —— 类文件结构(下)

    简介 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成 虚拟机中许多指令并不包含操作数.只有 ...

  5. protobuf 序列化 使用

    protocol buff是一种协议,是谷歌推出的一种序列化协议 .Java序列化协议也是一种协议 两者的目的是,将对象序列化成字节数组,或者说是二进制数据 导包 <dependency> ...

  6. 版本管理——Git和SVN的介绍及其优缺点

    版本管理 概念:版本管理是软件配置管理的基础,它管理并保护开发者的软件资源.   好处:可以保留我们的历史版本,在代码开发到一半的时候,不至于无故丢失,还可以查看BUG的来龙去脉.   版本管理种类: ...

  7. Python面试题集合带答案

    目录 Python基础篇 1:为什么学习Python 2:通过什么途径学习Python 3:谈谈对Python和其他语言的区别 Python的优势: 4:简述解释型和编译型编程语言 5:Python的 ...

  8. Windows计划任务无法写Log的问题

    参照:https://www.cnblogs.com/jonezzz/p/10364153.html 使用WIndows计划任务去执行Exe文件时无法写Log,而Exe双击执行就能写Log,这是由于计 ...

  9. ggplot2|详解八大基本绘图要素

    本文首发于微信公众号 *“ 生信补给站 ” ,期待您的关注!!!* 原文链接:https://mp.weixin.qq.com/s?__biz=MzIyNDI1MzgzOQ==&mid=265 ...

  10. 1 Refused to display ‘url’ in a frame because it set 'X-Frame-Options' to 'sameorigin' 怎么解决?

    进在开发公司的文件中心组件,提供各个子系统的附件上传下载.预览.版本更新等功能,前端在今天突然给我发一张图,说预览缩略图遇到问题了,然后发了个截图给我: 这很明显是一个跨域问题, X-Frame-Op ...