summary类型怎样使用

1 背景

在微服务项目中,我们通常需要监测客户请求的耗时,进而掌握系统整体的性能情况。

若发现某些请求耗时非常高,那肯定会对客户体验造成影响。

并且高耗时的服务非常容易成为整个服务的瓶颈,在高并发下很可能引发微服务雪崩效应,进而导致整个服务不可用。

2 微服务项目中如何监测请求耗时呢?

例如常见的监测手段是:

  1. 某个请求的最大耗时。(木桶效应里的最短的那块板)
  2. 某个请求的耗时百分位。(请求耗时的整体分布情况)

例如:

请求:http://127.0.0.1/hello

最大耗时:300ms [需要重点关注,什么情况下产生这么大的耗时,必须被优化掉]

耗时百分位:

  • 50分位,50%:100ms(有50%的请求,耗时低于100ms)[性能很好,耗时较低]
  • 90分位,90%:230ms(有90%的请求,耗时低于230ms)[230ms,性能可接受]
  • 95分位,95%:260ms(有95%的请求,耗时低于260ms)[260ms,需要优化性能]
  • 99分位,99%:270ms(有99%的请求,耗时低于270ms)[270ms,影响客户体验]

3 使用Prometheus的Summary类型来统计HTTP请求耗时

3.1 实践:如何使用Summary类型Metric?

示例代码:


      // 统计http请求耗时
      var httpRequestDuration = prometheus.NewSummaryVec(
      	prometheus.SummaryOpts{
      		Name: "http_request_duration",
      		Help: "http request duration",
      		Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.95: 0.005, 0.99: 0.001},
      	},
      	[]string{"endpoint"},
      )
  
 

采样结果:


      http_request_duration{endpoint="/hello/2",quantile="0.5"} 35
      http_request_duration{endpoint="/hello/2",quantile="0.9"} 94
      http_request_duration{endpoint="/hello/2",quantile="0.95"} 97
      http_request_duration{endpoint="/hello/2",quantile="0.99"} 98
      http_request_duration_sum{endpoint="/hello/2"} 1172
      http_request_duration_count{endpoint="/hello/2"} 28
  
 

Summary类型的Metric会生成三种类型的值:

  1. xxx_sum:表示“/hello/2”这个请求,耗时的总和。
  2. xxx_count:表示“/hello/2”这个请求,请求的次数。
  3. xxx{xxxx, quantile="0.5"}:表示“/hello/2”这个请求,50分位的值,例如上述示例中,50分位值是35,意思是这个url 50%的请求耗时都小于35ms

3.2 源码分析:Summary是如何计算分位数的?

首先看Summary的定义


      type Summary interface {
      	Metric
      	Collector
     	// Observe adds a single observation to the summary.
      // 新增一个观测值
      	Observe(float64)
      }
  
 

Summary很核心的一个方法是Observe(),在本地增加一个观测值。

再看Summary的实现


      // Summary接口的实现类
      type summary struct {
      	selfCollector
     	// 省略
      	objectives map[float64]float64 // 分位数,告诉Summary要统计哪些分位的值
      	sortedObjectives []float64 // 对分位数进行排序,升序,防止用户输入的分位数是乱序的
      	labelPairs []*dto.LabelPair
      	sum float64 // 观测到的数据值的总和
      	cnt uint64 // 观测的次数
     	// 省略
      	streams []*quantile.Stream
      	streamDuration time.Duration
      	headStream *quantile.Stream // 存储当前观测数据的地方
      	headStreamIdx int
      // 省略
      }
  
 

系统观测到的数据放在quantile.Stream里:


      type Stream struct {
      	*stream // 历史所有观测数据保存在这里,会在一定条件下将b里的观测值merge到stream中
      	b Samples // Samples类型本质就是[]Sample,保存当前观测到的数据
      	sorted bool // 是否已排序
      }
      // stream结构
      type stream struct {
      	n float64 // 数量
      	l []Sample // 所有观测到的数据
      	ƒ invariant
      }
      // 一次观测获取的数据
      type Sample struct {
      	Value float64 `json:",string"`
      	Width float64 `json:",string"`
      	Delta float64 `json:",string"`
      }
  
 

由此可见,每一次观测到的值被包装成一个Sample,然后所有观测到的值放在一个list里。

如何计算分位数?

我们搞清楚了最终的存储结构是List,那计算分位数就明确了。

先对list进行排序,升序。

根据list的长度length,例如取50分位,index=Ceil( length * 0.5 ),list[index]就是最终分位数对应的值。

4 Summary就这么简单?

如何并发写list?

若通过lock保证写入安全,那怎样保证lock的竞争不会消耗太多时间?

高并发写入时,如何保证写入性能?

写入的数据量太大时如何存储list?

如何保证一个超大的list的数据是有序的?

如果一直往list里写数据,内存会不会被干爆?

推荐阅读:

  1. Prometheus核心概念:一图了解瞬时向量Instant vector和区间向量Range vector的区别
  2. Prometheus源码分析:基于Go Client自定义的Exporter,是如何在Local存储Metrics的?
  3. Prometheus核心概念:一图了解Counter和Gauge两种数据指标类型的区别
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值