作者:张富春(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的单核性能极为强悍,且能够做到水平扩容,可以认为其数据写入能力是没有上限的。

vm-insert到vm-storage链路上的配置说明的更多相关文章

  1. Linux虚拟内存(swap)调优篇-“swappiness”,“vm.dirty_background_ratio”和“vm.dirty_ratio”

      Linux虚拟内存(swap)调优篇-“swappiness”,“vm.dirty_background_ratio”和“vm.dirty_ratio” 作者:尹正杰 版权声明:原创作品,谢绝转载 ...

  2. centos6 下FastDFS 在storage节点上nginx的fastdfs-nginx-module 模块编译出现的问题

    centos6.6  下FastDFS  在storage节点上   make(编译)nginx的fastdfs-nginx-module 出现如下报错: /root/fastdfs-nginx-mo ...

  3. 攻击链路识别——CAPEC(共享攻击模式的公共标准)、MAEC(恶意软件行为特征)和ATT&CK(APT攻击链路上的子场景非常细)

    结合知识图谱对网络威胁建模分析,并兼容MITRE组织的CAPEC(共享攻击模式的公共标准).MAEC和ATT&CK(APT攻击链路上的子场景非常细)等模型的接入,并从情报中提取关键信息对知识图 ...

  4. SQL VM上磁盘延迟高, 但Host和Storage Array上的延迟却很低的问题

    按照下面的步骤, 问题解决. =========================== Per Microsoft DDK, Microsoft storport.sys maintains a dev ...

  5. 虚拟机最佳实践:单个 VM、临时存储和已上传磁盘

    大家好! 我是 Drew McDaniel,来自 Microsoft Azure虚拟机功能研发团队,我从团队成立之初就已加入. 在本博客文章中,我将分享一些最佳实践指南,帮助您充分利用您的Azure虚 ...

  6. vm.dirty_background_ratio and vm.dirty_ratio

    http://hellojava.info/?p=264&utm_source=tuicool&utm_medium=referral 解决磁盘io紧张的一种临时方法 有些时候可能会碰 ...

  7. Standard 1.1.x VM与Standard VM的区别

    在Eclipse或MyEclipse中要设置Installed JREs时,有三个选择: - Execution Environment Description - Standard 1.1.x VM ...

  8. Azure Storage 分块上传

    概述 Azure 存储提供三种类型的 Blob:块 Blob.页 Blob 和追加 Blob.其中,块 Blob 特别适用于存储短的文本或二进制文件,例如文档和媒体文件. 块 Blob 由块组成,每个 ...

  9. Java HotSpot(TM) Client VM 与 server VM 的配置

    在Linux 6.5 下安装Elasticsearch 出现错误: JVM is using the client VM [Java HotSpot(TM) Client VM] but should ...

  10. 1, vm: PropTypes.instanceOf(VM).isRequired

    子模块的文件引入父工程对象时,出现红色warning,提示传入的对象类型不是所要求的类型. 思路是父工程引用的JS包和子模块使用的包不是同一个包,解决办法是父工程和子工程都使用同一个包. resolv ...

随机推荐

  1. 浏览器史话中chrome霸主地位的奠定与国产浏览器的割据混战

    作为前端老鸟,从IE的6.7.8开始做前端,各种兼容性折磨死人.js还好有了jQuery.chrome出来后,真是救苦救难,解救程序员的于水火.但是可恶的boss还是要求兼容ie6,7.感谢淘宝团队的 ...

  2. hadoop清空回收站命令

    直接删除目录(不放入回收站) hdfs dfs -rm -skipTrash /tmp/aaa 清空回收站 hdfs dfs -expunge 执行完命令后,回收站的数据会在一分钟后清除.

  3. linux 实时刷新显示当前时间

    同步阿里云时间 ntpdate ntp1.aliyun.com 使用watch命令:周期性的执行一个命令,并全屏显示. watch -n 1 date 即可:每1秒刷新date命令. # 格式 wat ...

  4. AtCoder Beginner Contest 335 总结

    ABC335总结 A.202<s>3</s> 翻译 给你一个由小写英文字母和数字组成的字符串 \(S\). \(S\) 保证以 2023 结尾. 将 \(S\) 的最后一个字符 ...

  5. ME21N 采购订单屏幕增强

    1.业务需求 采购订单行项目新增"图号"和"价格类型"字段.其中图号只查询底表展示,不做修改:价格类型做下拉框: 2.增强实现 增强标准表EKPO结构CI_EK ...

  6. 汇编 | DosBox初步接触的一些初始化设置(窗口大小 & 默认命令)

    如何在win10 64位下搭载汇编环境请参考这篇博客:Here 学习汇编时下载了 DosBox,然而窗口小到眼睛酸痛.解决方案如下. Updata:VSC 插件使用方法,Here 1.点开配置文件 配 ...

  7. 无需代码绘制人工神经网络ANN模型结构图的方法

      本文介绍几种基于在线网页或软件的.不用代码的神经网络模型结构可视化绘图方法.   之前向大家介绍了一种基于Python第三方ann_visualizer模块的神经网络结构可视化方法,大家可以直接点 ...

  8. vue Promise的使用

    一.Promise是什么? Promise是异步编程的一种解决方案. 二.那什么时候我们会来处理异步事件呢? 1. 一种很常见的场景应该就是网络请求了. 我们封装一个网络请求的函数,因为不能立即拿到结 ...

  9. python进阶(2)--列表

    文档目录:一.访问列表元素二.更新列表三.删除元素四.列表排序五.len()与range()/list()六.创建一个包含1-10平方的列表七.复制列表两种方式八.元组:不可变的列表 -------- ...

  10. MongoDB 根据多个条件批量修改

    转载请注明出处: MongoDB 根据单个条件修改的sql 如下: db.collection_name.update({"userid":"1111111"} ...