惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
D
DataBreaches.Net
M
MIT News - Artificial intelligence
量子位
N
Netflix TechBlog - Medium
The Cloudflare Blog
The GitHub Blog
The GitHub Blog
P
Proofpoint News Feed
人人都是产品经理
人人都是产品经理
B
Blog RSS Feed
B
Blog
博客园_首页
博客园 - Franky
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
MongoDB | Blog
MongoDB | Blog
云风的 BLOG
云风的 BLOG
爱范儿
爱范儿
H
Help Net Security
Y
Y Combinator Blog
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell

博客园 - toong

envoy离线编译方法 Kubernetes AI Gateway Kind 归属地图 kubuntu通过flatpak安装软件 ai-gateway 安装记录 家用台式工作站软件配置 envoy DDOS HTTP/2 Bomb CVE-2026-47774 安全漏洞分析 envoy源码分析(十四)-- XDS的启动流程 P2P与FRP与websocket ros2入门(零)-- 难点与挑战 ros2入门(二)-- 基本概念 envoy源码分析(十三)-- WASM python运行虚拟化环境 大模型从0到1 envoy源码分析(十二)-- XDS/GRPC 绑定网卡中断后是否需要设置RFS linux socket reuse port 小测试 envoy源码分析(十)--事件机制专项分析 envoy源码分析(八)--main线程专项分析 envoy源码分析(九) -- circuit breakers 现代C++ envoy timeout 说明 观察中断的脚本 /proc/interrupts openssl查看编译时选项设置的方法 内核ipsec转发优化方法 提高ipsec多核并行能力的优化方法之一 tc qdisc 的burst如何设置 bash模拟netstat取值的脚本 linux内核对MSI网卡队列的smp_affinity亲和CPU选择 bash多并发--进程池数量控制 bash单例模式
envoy源码分析(十一)--性能分析
toong · 2026-01-06 · via 博客园 - toong

主线程性能

如  envoy源码分析(八)--main线程专项分析  的分析,主线程有如下几部分功能:

1  xds消息处理

2  主动健康检查

3   admin接口请求处理

4   state信息周期合并

5   state sink 插件调用。

随着配置量增长,主动健康检查 与  admin 接口请求, 将成为主线程的性能瓶颈。导致CPU满载。

xds 消息可以配置成ads的增量模式,不对导致CPU瓶颈。

state的周期是全局可调的,另外 state sink 有 dog-stated 的 UDP 插件,可以将统计信息外送至stated统一处理。

worker线程转发性能

总结有4个瓶颈

1  accpt时 alloc fd的锁

2  中断与worker分开后,tcp_push_pending_frames的锁

     这个不是真的锁住了,中断在超线程上会有cache争抢问题,导致锁原子操作被放大。绑独立核心的时候没有这个问题。

3  libevent 启用多线程时的 mutex

4  libevnet 全局变量 event_debug_mode_on_  /  event_debug_mode_too_late  cache miss

如 envoy源码分析(十)--事件机制专项分析  的分析, libevent 的 多线程锁会成为瓶颈。

实测在32个CPU后,性能增长趋于平缓。

解法也比较简单直接。分成多进程,利用内核的port reuse,实现多进程无差别服务,

不需要做额外的角色管理,只需要同时订阅一份xds配置即可。

还有另一个解法,如  envoy源码分析(十)--事件机制专项分析  的分析

可以使用 io_uring

perf top 查看热点是 libevent 的锁:

image

使用bpftrace,找到热点锁的调用位置

/*
 * 追踪 pthread_mutex_lock 的等待耗时,并按锁地址和调用栈分组
 */

BEGIN {
    printf("正在追踪 Envoy 锁竞争... 按 Ctrl-C 结束并查看结果\n");
}

uprobe:libc:pthread_mutex_lock {
    @start[tid] = nsecs;
    @lock_addr[tid] = arg0; // 记录锁对象的内存地址
}

uretprobe:libc:pthread_mutex_lock /@start[tid]/ {
    $duration = nsecs - @start[tid];
    $lock = @lock_addr[tid];

    // 只记录耗时超过 1000 纳秒(1微秒)的竞争,过滤掉极快的无竞争加锁
    if ($duration > 1000) {
        @total_wait_ns[$lock] = sum($duration);      // 该地址锁的总等待时间
        @max_wait_ns[$lock] = max($duration);        // 该地址锁的最大单次等待
        @contention_stacks[ustack, $lock] = count(); // 导致竞争的调用栈
    }

    delete(@start[tid]);
    delete(@lock_addr[tid]);
}

END {
    printf("\n--- 各个锁地址的总等待时长 (ns) ---\n");
    // 结果会按总耗时排序输出
}

输出结果:

排名第一的函数,event_process_active_single_queue(),比第二名多了一个数量级

image

代码:

https://github.com/libevent/libevent/blob/release-2.1.12-stable/event.c#L1727

分析主要原因:

libevent的事件循环累计锁的原子操作,导致的 耗时操作的高频访问,引起的累计效果。

image

为什么线程数增多,会变的越来越严重?

猜测可能是缓存一致性问题引起。

函数 pthread_mutex_unlock_usrcnt

image

函数 event_del_nolock

image

event_debug_note_del_

全局变量:event_debug_mode_on_  /  event_debug_mode_too_late = 1;

函数 event_add_nolock 

image

event_debug_note_setup_

全局变量:event_debug_mode_on_  /  event_debug_mode_too_late = 1;

修改设置全局变量cache line 对齐:

envoy/build_root/bazel_root/base/external/com_github_libevent_libevent::event.c

image

image

用 bpftrace 查看锁是否被竞争

[root@A06-R08-I208-229-925A008 caotong1]# cat envoy-4.bt 
#!/usr/bin/env bpftrace

#include <linux/sched.h>

BEGIN {
    printf("正在监测锁(Lock/Unlock)访问分布... 采样周期 5s\n");
    printf("%-18s | %-16s | %s\n", "MUTEX_ADDR", "OP_TYPE", "THREAD_NAME:COUNT");
    printf("-------------------|------------------|--------------------------\n");
}

/* 监控加锁 */
uprobe:/lib64/libc.so.6:pthread_mutex_lock,
uprobe:/lib64/libc.so.6:pthread_mutex_trylock,
{
    @ops[arg0, "lock", comm] = count();
}

uprobe:/lib64/libc.so.6:pthread_mutex_unlock 
{
    @ops[arg0, "unlock", comm] = count();
}

END {
    print(@ops);
    clear(@ops);
}
[root@A06-R08-I208-229-925A008 caotong1]# #./envoy-4.bt  > 1
[root@A06-R08-I208-229-925A008 caotong1]# cat 1 |awk '{print $1}' |sort |uniq -c |tail
      4 @ops[8635020759488,
      4 @ops[8635020759808,
      4 @ops[8635020760128,
      4 @ops[8635020760448,
      4 @ops[8635020760768,
      4 @ops[8635020761088,
      4 @ops[8635020761344,
      2 @ops[93987636247152,
      4 @ops[93987636268544,
      1 正在监测锁(Lock/Unlock)访问分布...
[root@A06-R08-I208-229-925A008 caotong1]# cat 1 |grep 8635020761344
@ops[8635020761344, lock, envoy]: 144
@ops[8635020761344, unlock, envoy]: 146
@ops[8635020761344, lock, wrk:worker_47]: 746930
@ops[8635020761344, unlock, wrk:worker_47]: 747892
我现在想知道,锁8635020761344,在这些命中时的调用栈

针对锁 8635020761344 查看其调用栈

[root@A06-R08-I208-229-925A008 caotong1]# cat envoy-5.bt 
#!/usr/bin/env bpftrace

/* * 专门针对特定热点锁地址抓取调用栈
 * 热点地址: 8635020761344
 */

BEGIN {
    printf("正在追踪锁 8635020761344 的调用栈... (Ctrl+C 结束)\n");
}

uprobe:/lib64/libc.so.6:pthread_mutex_lock /arg0 == 8635020761344/ {
    @[comm, ustack] = count();
}

uprobe:/lib64/libc.so.6:pthread_mutex_unlock /arg0 == 8635020761344/ {
    @[comm, ustack] = count();
}

END {
    print(@);
    clear(@);
}

image

是 state 的定时刷新在枪锁。