• vminsert到vmstorage链路上的配置说明


    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


    本文是为了解决Victoria-Metrics中的过载问题而得出的较好实践。也是为了回答这样一些问题:

    • vm-storage的单核写入性能的极限在哪里?(请见之前的三篇文章)
    • 怎么样才能感知到,vm-insert到vm-storage这条写入链路上已经发生了过载?
    • 如果写入链路发生过载,是否会丢数据?如果丢数据,究竟在哪个环节丢数据?过载了如何做保护,如何避免雪崩?

    下面开始回答以下问题:

    1.vm-storage的单核写入性能的极限在哪里?

    • 全是新metric的情况:6000/核/s
    • 全是旧的metric的情况:43万/核/s
    • 新metric占整体1%的情况:24.6万/核/s

    此外:写入性能是否会随着时间推移索引数增多后,导致写入性能下降?
    会的!原因在于内存占用量升高,tsid cache命中率降低导致。
    只要活跃的metric都在tsid cache中,写入性能就不会受到影响。

    2.写入链路过载感知

    2.1 vm-storage中,几乎不会发生丢数据的情况

    统计drop相关的metric,可以看出vm-storage上丢弃的数据量:

    sum by () (rate(vm_hourly_series_limit_rows_dropped_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"})) +
    sum by () (rate(vm_daily_series_limit_rows_dropped_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"})) +
    sum by () (rate(vm_concurrent_addrows_dropped_rows_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"}))
    

    或者,把接收到的行数为分母,把最终写入存储的行数为分子,可以计算出vm-storage上的写入成功率:

    sum by () (rate(vm_rows_added_to_storage_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"}[1m])) /
    sum by () (rate(vm_vminsert_metrics_read_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"}[1m]))
    

    在没有刻意限制每小时和每天的新metric数量的情况下,实测过程中没有发现任何vm-storage上的数据丢弃。

    2.2 vm-insert中的数据丢弃逻辑:

    通过配置 -dropSamplesOnOverload 来丢弃数据

    see: app/vminsert/netstorage/netstorage.go:53

    func (sn *storageNode) push(buf []byte, rows int) error {
    	if len(buf) > maxBufSizePerStorageNode {
    		logger.Panicf("BUG: len(buf)=%d cannot exceed %d", len(buf), maxBufSizePerStorageNode)
    	}
    	sn.rowsPushed.Add(rows)
    	if sn.trySendBuf(buf, rows) {
    		// Fast path - the buffer is successfully sent to sn.
    		return nil
    	}  // 只要缓冲区写满,且配置了丢弃数据,就会直接丢弃数据。丢弃数据能够避免vm-insert OOM崩溃
    	if *dropSamplesOnOverload && atomic.LoadUint32(&sn.isReadOnly) == 0 {
    		sn.rowsDroppedOnOverload.Add(rows)
    		dropSamplesOnOverloadLogger.Warnf("some rows dropped, because -dropSamplesOnOverload is set and vmstorage %s cannot accept new rows now. "+
    			"See vm_rpc_rows_dropped_on_overload_total metric at /metrics page", sn.dialer.Addr())
    		return nil
    	}
    	// Slow path - sn cannot accept buf now, so re-route it to other vmstorage nodes.
    	if err := sn.rerouteBufToOtherStorageNodes(buf, rows); err != nil {
    		return fmt.Errorf("error when re-routing rows from %s: %w", sn.dialer.Addr(), err)
    	}
    	return nil
    }
    

    由源码可知:当vm-storage达到瓶颈,vm-insert与vm-storage之间的传输必然变慢,变慢后导致netstorage客户端的缓冲区无法及时释放。这个时候如果配置了-dropSamplesOnOverload选项,就会直接丢弃数据。
    丢弃数据后,通过metric vm_rpc_rows_dropped_on_overload_total 会体现出丢弃的量。
    虽然这个配置项能够避免vm-insert发生OOM(Out of memory)崩溃,但是这个层面上的数据丢弃无法补救。
    建议vm-insert不要配置-dropSamplesOnOverload选项

    vm-insert的http remote write协议层面

    作为http服务器,可以看看vm-insert在http协议上如何处理过载:
    see: lib/writeconcurrencylimiter/concurrencylimiter.go:32

    // Do calls f with the limited concurrency.
    func Do(f func() error) error {
    	// Limit the number of conurrent f calls in order to prevent from excess
    	// memory usage and CPU thrashing.
    	select {
    	case ch <- struct{}{}:  // 放入channel成功代表允许的并发数足够
    		err := f()  // 执行具体的协议回调函数
    		<-ch
    		return err
    	default:
    	}
    
    	// All the workers are busy.
    	// Sleep for up to *maxQueueDuration.
    	concurrencyLimitReached.Inc()
    	t := timerpool.Get(*maxQueueDuration)  // maxQueueDuration 默认60秒,也就是无法处理的请求最长要等待60秒
    	select {
    	case ch <- struct{}{}:
    		timerpool.Put(t)
    		err := f()
    		<-ch
    		return err
    	case <-t.C:
    		timerpool.Put(t)
    		concurrencyLimitTimeout.Inc()
    		return &httpserver.ErrorWithStatusCode{
    			Err: fmt.Errorf("cannot handle more than %d concurrent inserts during %s; possible solutions: "+
    				"increase `-insert.maxQueueDuration`, increase `-maxConcurrentInserts`, increase server capacity", *maxConcurrentInserts, *maxQueueDuration),
    			StatusCode: http.StatusServiceUnavailable,  // 等待60秒后仍然没有资源,则向调用端返回http 503错误
    		}
    	}
    }
    

    由源码可知:后端过载后,发送数据变慢,变慢导致用于处理并发的协程长时间阻塞。新来的请求没有协程去处理,就进入了等待。等待足够长的时间仍然没有资源,就向调用端返回http 503错误码。
    把无法处理的请求通过错误码返回,这样调用方就能够感知到后端的过载了。
    通过metric可以查询到vm-insert上拒绝的请求数:vm_http_request_errors_total{protocol="promremotewrite"}
    到这里,怎么样才能感知到,vm-insert到vm-storage这条写入链路上已经发生了过载?这个问题已经有了答案:
    1.关闭-dropSamplesOnOverload选项,不要在vm-insert这个层面上丢包;
    2.通过http remote write的状态码 http 503 来感知后端是否已经过载;

    vm-insert仍然有其他细节可能导致丢包,比如配置了 -maxInsertRequestSize / -maxLabelValueLen / -maxLabelsPerTimeseries 等选项的情况下。
    可以通过下面的表达式来查询vm-insert这个环节的转发成功率:

    sum by () (rate(vm_rpc_rows_sent_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.*"})) /
    sum by () (rate(vm_rows_inserted_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.*",type="promremotewrite"}))
    

    如何合理配置vm-insert的参数

    首先,分析一下vm-insert写数据到vm-storage的延迟是多少:

    max by () (rate(vm_rpc_send_duration_seconds_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.*"}[1m]))
    

    我实验群集查询出来的数值是 0.996~1.01秒。
    -maxConcurrentInserts 参数不配置的情况下,每核的默认并发为4.
    因此,每协程的延迟大约是: 1000ms / 4核 / 4 协程 = 62.5ms
    vm-insert与vm-storage之间的延迟如此之短,所以 -insert.maxQueueDuration=60s 这个时间实在太长了。我在压测中发现,-insert.maxQueueDuration=2s能够带来很好的吞吐量,并使vm-insert的内存压力很小。
    -insert.maxQueueDuration=2s后,remote write客户端的请求延迟要略大于2s。这样的话,vm写入链路上如果发生过载,绝大多数的过载请求都会通过 http 503来体现。

    总结一下:

    • 通过metric max by () (rate(vm_rpc_send_duration_seconds_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.*"}[1m])) 来计算实例的最大延迟。然后除以总的协程数,得到每个协程的延迟。
    • 队列中的等待时间insert.maxQueueDuration要与上面得到的延迟值相对等同。否则,过载情况下会积累非常多远超于延迟时间的请求,容易导致vm-insert发生OOM
    • vm-insert的参数-maxConcurrentInserts建议不配置,使用默认的每核4协程的并发量。
      • 协程数增加对于vm-insert的吞吐量没有明显提升,反而导致增加协程调度的消耗。
      • 实际压测中发现每核4协程的吞吐量很好,瓶颈还是在vm-storage一侧。
    • 关闭-dropSamplesOnOverload,把过载的信息传给上游。然后由上游来选择丢弃还是重试。

    3.过载的一系列问题

    如果写入链路发生过载,是否会丢数据?如果丢数据,究竟在哪个环节丢数据?过载了如何做保护,如何避免雪崩?
    综合上面的信息:

    • 如果不在vm-insert上配置-dropSamplesOnOverload选项,几乎不会发生丢数据
      • 如果需要知道在某些数据合法性检查的环节是否有丢数据,可以通过vm中的某些metric计算出来具体丢了多少;(压测过程中一次也没发生过)
    • 丢数据的最大环节仍然在vm-insert上,导致丢弃的原因是netstorage客户端的buffer写满。
    • 过载保护最好的位置是在remote write的发送端,通过http 503错误码来感知后端是否过载。
      • vm-storage过载后,表现为CPU耗满,插入时没有对应的可调度协程。最终导致vm-insert端连接超时。也就是说,vm-storage自身不会因为写入太多而发生崩溃。
      • vm-insert的过载一般由于vm-storage变慢导致。过载后,等待处理的缓冲区的数据变多,没有可用的insert协程,最终触发超时,返回http 503错误。只要insert.maxQueueDuration参数设置合理,一般也不会发生vm-insert上的崩溃。

    总结

    • 把vm-insert和vm-storage看成一个整体,在这条路径上可以配置为不丢弃数据;
    • 把过载的检查和过载后的等待/重试等任务交给remote write的客户端;
    • vm-storage的单核性能极为强悍,且能够做到水平扩容,可以认为其数据写入能力是没有上限的。
  • 相关阅读:
    常用的系统存储过程
    考勤信息(员工打卡)
    项目经理评分(评价)
    体验套餐管理系统
    C#中简单的继承和多态
    面向对象的七个设计原则
    office 2010 安装时出错 MSXML版本6.10.1129.0
    phpstorm + Xdebug断点调试PHP
    wamp server 3.0.0 修改默认浏览器,软件语言和配置文件编辑器
    vue.js指令v-model实现方法
  • 原文地址:https://www.cnblogs.com/ahfuzhang/p/16474054.html
Copyright © 2020-2023  润新知