哎,失踪人口又回归了,三个月没发了,不该不该
最近工作主要分析向量DB的查询性能瓶颈。由于我们是嵌入式DB,并没搞服务器DB 那套业界流利的监控框架。但是由于业务场景是针对千万、亿级的向量查询,其实也不再那么"嵌入式"了,所以完全是可以把优秀的监控方法借鉴过来。
我指的是就是那套exporter + prometheus + grafana
组合,利用强大的PromSQL能力 + grafana面板监控来更好地捕捉系统正在发生的事情,放大 observability 能力,避免简单地只从日志、火焰图上看信息,对复杂性能问题,远远不够!
但目前DB又没有暴露满足符合prometheus格式的数据。按照我以前的经验,对redis-cerberus 程序做过如下的事情:
-
在各个地方记录请求数和请求时延
-
创建单独http 监听线程,将其他线程的counter, gauge获取到,借助
prometheus-cpp
库暴露到http端口,以让prometheus来抓。即程序本身就自带了exporter功能。
这一套弄下来,感觉要费点时间。我想有没有更简单的暴露方式?问claude: prometheus在哪些c/c++程序中得到应用了?看到了envoy,说它里面用到了prometheus-cpp(实际发现并没有),但意外发现它自己内部生成了简单格式的数据,然后发给statsd-exporter,而后者是一个通用性的exporter,可自定义mapping rule来将任何数据转换成prometheus 接受的格式。顿时感觉这个好,研究一下。
本文研究一下怎么快速部署envoy和发现其和statsd-export交互的过程。
直接二进制启动
envoy 直接可从github release 下载。
本是可以用docker compose快速搭建的,比如我找到了 [1],可以跑通,但发现无常规命令去跟踪envoy 发了什么内容给 statsd-exporter,这是我最想知道的。
于是我参考了另外一篇文章[2],对其配置文件改造如下:
node:
id: front-envoy
cluster: mycluster
# Envoy 管理配置
admin:
# 访问日志路径
access_log_path: "/data/tmp/envoy.log"
# 管理界面监听地址和端口
address:
socket_address:
address: 0.0.0.0
port_value: 10001
stats_sinks:
- name: envoy.stat_sinks.statsd
typed_config:
"@type": type.googleapis.com/envoy.config.metrics.v3.StatsdSink
tcp_cluster_name: statsd_exporter
prefix: front-envoy
# 静态资源配置
static_resources:
listeners:
# 定义一个监听器,监听在 0.0.0.0:10000
- address:
socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
# 使用 HTTP 连接管理器过滤器
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: auto
stat_prefix: http
access_log:
# 记录 HTTP 请求到文件
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: "/data/tmp/envoy_http.log"
log_format:
text_format: |
>>>>
start_time: "%START_TIME%"
method: "%REQ(:METHOD)%", original_path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", protocol: "%PROTOCOL%", response_code: "%RESPONSE_CODE%", response_flags: "%RESPONSE_FLAGS%"
user_agent: "%REQ(USER-AGENT)%"
authority: "%REQ(:AUTHORITY)%"
upstream_host: "%UPSTREAM_HOST%"
request_id: "%REQ(X-REQUEST-ID)%"
custom_header: "%REQ(custom_header)%"
static_header: "%REQ(static_header)%"
route_config:
name: search_route
virtual_hosts:
- name: backend
domains:
- "*"
routes:
- match:
prefix: "/"
route:
# 将请求发送到 baidu 集群
#cluster: baidu
cluster: localserver
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: baidu
connect_timeout: 1s
# 使用 STRICT_DNS 模式进行 DNS 解析
type: STRICT_DNS
dns_lookup_family: V4_ONLY
lb_policy: round_robin
load_assignment:
cluster_name: baidu
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# 目标服务器地址
address: www.baidu.com
port_value: 80
- name: localserver
connect_timeout: 1s
# 使用 STATIC 模式,直接指定目标地址
type: STATIC
dns_lookup_family: V4_ONLY
lb_policy: round_robin
load_assignment:
cluster_name: localserver
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# 本地服务器地址
address: 127.0.0.1
port_value: 8081
- name: statsd_exporter
connect_timeout: 0.25s
type: strict_dns
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: statsd_exporter
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: localhost
port_value: 9125
其中,
-
配置一个简单的http 代理,将请求发往8081端口,我会在那用 python -m http.server 8081
创建一个简单的http server. -
要想数据发往statsd-exporter,必须配置stats_sink和stats_exporter 的cluster,打算用docker启动一个statsd-exporter容器
启动
envoy: envoy -c front-proxy.yaml -l info
statsd-exporter: docker run -p 9102:9102 -p 9125:9125 -p 9125:9125/udp prom/statsd-exporter
看官方的例子是:
docker run -d -p 9102:9102 -p 9125:9125 -p 9125:9125/udp \
-v $PWD/statsd_mapping.yml:/tmp/statsd_mapping.yml \
prom/statsd-exporter --statsd.mapping-config=/tmp/statsd_mapping.yml
暂未研究mapping是怎么弄的,后期可能需要考虑将我们DB的格式通过它映射成prometheus格式
抓取envoy主动向exporter发送的数据
用tcpdump抓取方便(与9102端口区分开来,它是向prometheus暴露的http 端口): tcpdump -i any tcp dst port 9125 -A -nn
数据如下(定期接收到):
13:50:46.463664 lo In IP6 ::1.41804 > ::1.9125: Flags [P.], seq 910642444:910649206, ack
882327520 ecr 3882322520], length 6762
`.@....@.................................L#.6GM./..............
.g...g.Xfront-envoy.cluster_manager.cluster_added:0|c
front-envoy.cluster.localserver.default.total_match_count:0|c
front-envoy.cluster.localserver.upstream_cx_destroy:0|c
front-envoy.cluster.statsd_exporter.upstream_cx_tx_bytes_total:6762|c
front-envoy.cluster.localserver.external.upstream_rq_200:0|c
front-envoy.listener_manager.listener_create_success:0|c
front-envoy.dns.cares.resolve_total:2|c
front-envoy.cluster.localserver.upstream_rq_200:0|c
front-envoy.runtime.override_dir_not_exists:0|c
front-envoy.filesystem.flushed_by_timer:2|c
front-envoy.cluster.localserver.upstream_rq_total:0|c
front-envoy.http.http.downstream_cx_total:0|c
13:50:46.463871 docker0 Out IP 172.17.0.1.32918 > 172.17.0.2.9125: Flags [P.], seq 241
[nop,nop,TS val 889465382 ecr 807951972], length 6762
E...JD@.@.}...........#.....).......r......
5.*&0(^dfront-envoy.cluster_manager.cluster_added:0|c
front-envoy.cluster.localserver.default.total_match_count:0|c
front-envoy.cluster.localserver.upstream_cx_destroy:0|c
front-envoy.cluster.statsd_exporter.upstream_cx_tx_bytes_total:6762|c
front-envoy.cluster.localserver.external.upstream_rq_200:0|c
front-envoy.listener_manager.listener_create_success:0|c
front-envoy.dns.cares.resolve_total:2|c
front-envoy.cluster.localserver.upstream_rq_200:0|c
front-envoy.runtime.override_dir_not_exists:0|c
front-envoy.filesystem.flushed_by_timer:2|c
原来是这样的格式,后面的c 代表counter, g 代表 gauge
导入到prometheus并简单测试
我是在另外一条机器上单独起了prometheus 容器,
docker run --rm --name prom -p 9090:9090 -v ./coding/prom.yml:/etc/prometheus/prometheus.yml prom/prometheus
增加配置:
- job_name: 'statsd'
scrape_interval: 5s
static_configs:
- targets: ['192.168.0.111:9102']
labels:
group: 'group1
bash端循环发请求:
while true; do curl -s localhost:10000 | grep wtperf.svg; sleep 0.01; done
一段时间后prometheus 9090端口简单查询:

有点没明白平均qps 35 是怎么来的。。
OK. 基本上弄清楚了。下一步就可看看envoy 源码怎么生成类似front-envoy.cluster.localserver.external.upstream_rq_200:0|c
这样的格式,以便用在内部DB上, statsd-exporter可拿来即用
更多硬核技术,关注公众号"极客影忍", Happy Coding ~
References
https://www.cnblogs.com/wangguishe/p/16700590.html: docker-compose启动envoy
[2]https://juejin.cn/post/7321410883875143730: 单机启动envoy的一个前端代理示例