1. 项目概述:这不是“调参数”,而是重新理解系统如何呼吸
“第28章:性能优化案例”——光看这个标题,很多人第一反应是:“哦,又是一章讲CPU占用率、内存泄漏、SQL慢查询的教科书章节”。但我在某高校实验室带过三届系统工程实训,也给五家不同规模的软件公司做过性能诊断支持,发现一个反复出现的现象:90%的所谓“性能问题”,根本不是技术缺陷,而是对业务节奏与系统负载模式的误判。这一章之所以排在第28章,恰恰因为它不是入门知识的堆砌,而是把前面27章所有模块——网络协议栈、数据库事务、缓存策略、线程模型、前端渲染机制——全部拉到同一张压力测试图上,看它们怎么互相咬合、哪里卡顿、谁在拖后腿。它解决的不是“某个接口响应慢”,而是“为什么在凌晨三点批量导出报表时,用户登录反而变卡了”;不是“怎么让页面首屏快100ms”,而是“当300个销售同时点击‘提交订单’按钮,库存扣减失败率为何从0.2%跳到12%”。适合谁?适合已经能写出功能完整代码、却总在压测报告前皱眉的中级开发者;适合能配置Nginx但说不清worker_connections和epoll事件循环关系的运维同学;更适合那些被老板问“服务器都加到8核了,为啥还是卡?”却只能回答“再加内存试试”的技术负责人。核心关键词——性能瓶颈定位、真实负载建模、跨层协同优化、可观测性落地、反直觉优化——这些词背后不是理论,而是我亲手在模拟项目X中复现、测量、推翻、再重建的17次迭代记录。
2. 内容整体设计与思路拆解:放弃“单点修复”,拥抱“系统脉搏监测”
2.1 为什么必须抛弃“先看监控再改代码”的惯性思维?
很多团队一遇到性能问题,第一动作是打开Prometheus看CPU曲线,发现峰值85%,立刻开PR加机器、调JVM堆大小、甚至重写算法。我在某电商后台项目里见过最典型的反例:他们把订单服务的GC频率从每分钟3次降到0.5次,CPU使用率下降12%,但用户投诉“下单成功但收不到短信”的比例反而上升了40%。根因排查花了整整三天——最终发现是短信网关SDK的连接池超时时间(connectTimeout=3000ms)被设死,而数据库主从同步延迟在高峰时波动到2800ms,导致部分订单状态查不到,SDK直接抛异常,上层服务误判为“发送失败”并重试,形成雪崩。这个案例说明:性能不是孤立指标,而是多层依赖链上的共振现象。本章的设计起点,就是彻底放弃“单点归因”路径,转而构建一个三层穿透式分析框架:
- 表层(用户可感层):HTTP状态码分布、首字节时间(TTFB)、资源加载瀑布图、JS执行耗时堆栈;
- 中层(服务交互层):RPC调用链路耗时(含序列化/反序列化开销)、数据库连接等待队列长度、缓存命中率突降点、消息队列积压速率;
- 底层(系统资源层):CPU运行队列长度(
r字段)、内存页回收速率(pgpgin/pgpgout)、磁盘IOPS饱和度、网络重传率(retrans/segsout)。
这三层不是并列关系,而是因果嵌套:底层资源争抢(如CPU调度延迟)会放大中层调用耗时(如RPC超时),进而扭曲表层用户体验(如页面白屏)。本章所有案例,都强制要求从表层异常出发,用perf record -e sched:sched_switch抓取调度事件,再用bpftrace跟踪特定进程的系统调用耗时,最后回溯到代码中的锁竞争点——这种“自上而下验证、自下而上归因”的闭环,才是真实世界里最可靠的优化路径。
2.2 为什么选择“模拟项目X”作为贯穿案例?它比真实业务更难搞
市面上很多性能教程用“秒杀系统”或“微博热搜”当案例,看似高大上,实则掩盖了真正难点:真实业务系统的负载是混沌的、非周期的、带强业务语义的。比如某金融风控系统,白天交易时段QPS稳定在2000,但每到整点,会有15秒的“规则引擎全量校验”任务,瞬间拉起300个线程扫描千万级用户画像数据,此时订单接口P99延迟从80ms飙到1200ms——这种“脉冲式干扰”在标准压测工具(如JMeter)里极难模拟。模拟项目X刻意设计了三类典型混沌负载:
- 语义耦合型:用户“修改收货地址”操作,会触发地址库校验、物流区域匹配、优惠券资格重算三个强依赖服务,且每个服务的SLA不同(校验服务要求<200ms,匹配服务允许<800ms);
- 资源错配型:日志采集Agent与业务进程共享同一块SSD,当业务写入大量临时文件时,Agent的
fsync()调用被阻塞,导致日志丢失,运维误判为“服务崩溃”; - 时间敏感型:报表生成任务依赖上游数据仓库的ETL完成时间,但ETL本身受网络抖动影响,完成时间在02:15~02:47之间随机浮动,导致下游任务要么空等、要么超时失败。
选择它,是因为它逼着你放弃“理想化假设”,学会在不完美的监控覆盖、不完整的链路追踪、不一致的日志格式中,用strace -p <pid> -T -e trace=write,read,sendto,recvfrom这种原始手段,从字节流层面确认问题。这才是工业级性能工程师的核心能力——不是会调参,而是能在混沌中建立确定性认知。
2.3 为什么强调“可观测性落地”而非“监控告警”?因为90%的告警是噪音
很多团队花大价钱上ELK+Prometheus+Grafana,结果告警群每天刷屏:“CPU>80%”、“磁盘使用率>90%”、“HTTP 5xx>0.1%”。我在某SaaS公司做驻场支持时,发现他们的告警规则是运维同学按“教科书阈值”硬编码的:node_cpu_seconds_total{mode="idle"} < 10。但实际业务中,凌晨批处理任务本就会让CPU idle降到5%,这是健康状态,不是故障。本章彻底重构可观测性逻辑:不定义“异常阈值”,而是定义“业务黄金信号”。以模拟项目X的订单服务为例,我们只监控四个指标:
order_submit_success_rate:提交成功数 / 提交请求数(业务正确性)order_submit_p95_latency_ms:提交耗时P95(用户体验)db_connection_wait_time_ms_sum / db_connection_wait_time_ms_count:数据库连接平均等待时长(资源瓶颈)cache_hit_ratio{service="order"}:订单缓存命中率(架构有效性)
这四个指标必须满足因果约束:当cache_hit_ratio下降5%时,order_submit_p95_latency_ms必须上升;若未上升,则说明缓存失效未影响主路径,无需干预。这种基于业务逻辑的指标关联,让告警从“CPU高了”变成“缓存失效导致订单延迟上升”,问题定位时间从小时级压缩到分钟级。这也是本章所有案例的统一前提——没有这套指标体系,任何优化都是蒙眼打靶。
3. 核心细节解析与实操要点:从“看到现象”到“抓住命门”
3.1 真实负载建模:别信产品经理说的“并发5000”,用tc造出真实的网络抖动
性能优化最大的陷阱,是用“理想网络”测试“现实业务”。很多团队压测环境跑得飞快,上线就崩,根源在于忽略了网络不可靠性。模拟项目X的支付回调服务,在内网压测时TPS 12000,上线后回调失败率高达3%,查日志全是Connection reset by peer。最终发现是云厂商的SLB在连接空闲60秒后强制断连,而支付平台回调超时设为90秒,导致长连接在第61秒被SLB静默关闭,客户端发包时收到RST。要复现这种问题,不能靠等线上事故,必须主动制造网络混沌。tc(traffic control)是Linux内核自带的网络控制工具,比任何商业混沌工程平台都精准:
# 模拟SLB断连:对目标IP的TCP连接,60秒后注入RST包 sudo tc qdisc add dev eth0 root handle 1: htb default 12 sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit sudo tc filter add dev eth0 protocol ip parent 1:0 u32 match ip dst 192.168.10.5/32 flowid 1:1 sudo tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 0ms loss 0% corrupt 0% reorder 0% # 关键:用iptables标记60秒以上空闲连接,再用tc丢弃 sudo iptables -t mangle -A OUTPUT -p tcp --dport 8080 -m conntrack --ctstate ESTABLISHED -m connmark --mark 0x1 -j CONNMARK --save-mark sudo iptables -t mangle -A OUTPUT -p tcp --dport 8080 -m conntrack --ctstate ESTABLISHED -m connmark ! --mark 0x1 -m recent --name long_idle --rcheck --seconds 60 -j CONNMARK --restore-mark sudo iptables -t mangle -A OUTPUT -p tcp --dport 8080 -m conntrack --ctstate ESTABLISHED -m connmark --mark 0x1 -j CONNMARK --save-mark sudo iptables -t mangle -A OUTPUT -p tcp --dport 8080 -m conntrack --ctstate ESTABLISHED -m recent --name long_idle --set -j CONNMARK --save-mark这段脚本的核心思想是:用iptables的recent模块标记空闲超60秒的连接,再通过tc对这些连接注入错误。实测下来,它能100%复现线上RST问题,且不影响其他流量。注意事项:tc规则需在压测容器启动前注入,且iptables规则要持久化(iptables-save > /etc/iptables/rules.v4),否则容器重启即失效。我踩过的坑是:早期用netem直接加delay 60s,结果所有连接都卡住,完全无法模拟“部分连接断开”的真实场景——这提醒我们:混沌不是制造混乱,而是精确复现故障模式。
3.2 跨层协同优化:当数据库慢查询遇上JVM GC停顿,谁该背锅?
这是本章最具争议性的案例。某内容平台的“热门文章列表”接口,P95延迟从200ms突然升到1800ms,DBA说“SQL没问题,执行计划最优”,Java组说“GC正常,Young GC 50ms/次”。真相藏在perf的火焰图里。我们用以下命令采集:
# 同时采集内核态和用户态事件 sudo perf record -e 'syscalls:sys_enter_read,syscalls:sys_exit_read,syscalls:sys_enter_write,syscalls:sys_exit_write,sched:sched_switch,cpu-cycles,instructions' -g -p $(pgrep -f "java.*OrderService") -- sleep 60 sudo perf script > perf.out # 生成火焰图 ./FlameGraph/stackcollapse-perf.pl perf.out | ./FlameGraph/flamegraph.pl > java-flame.svg火焰图显示:java.lang.Thread.sleep占比35%,但点开看,它总在java.sql.PreparedStatement.execute之后出现。进一步用bpftrace跟踪:
# 跟踪JDBC execute方法的耗时 bpftrace -e ' kprobe:__sys_sendto { $pid = pid; @start[tid] = nsecs; } kretprobe:__sys_sendto / @start[tid] / { $delta = (nsecs - @start[tid]) / 1000000; if ($delta > 100) { // 超过100ms的网络发送 printf("Slow send: %d ms, PID %d\n", $delta, $pid); print(ustack); } delete(@start[tid]); } '结果发现:PreparedStatement.execute调用后,JVM线程在socket write上卡住,而此时vmstat 1显示si(swap in)值飙升。根因是:数据库连接池配置了maxActive=100,但应用层有200个线程争抢连接,当连接池满时,线程进入WAITING状态,而JVM的-XX:+UseG1GC在并发标记阶段会触发Concurrent Mode Failure,导致Full GC,而Full GC期间所有线程Stop-The-World,包括网络IO线程——于是数据库连接请求堆积,连接池耗尽,新请求无限等待,形成死锁。解决方案不是加连接池大小,而是:
- 将
G1MaxNewSizePercent从默认45%调到30%,减少Young GC频率; - 在连接池获取连接时添加
timeout=3000ms,超时直接抛异常,避免线程无限等待; - 用
@Scheduled(fixedDelay = 5000)定时任务,每5秒检查连接池活跃连接数,超过80%时自动扩容10个连接。
这个案例的教训是:性能瓶颈永远在层与层的缝隙里。DBA看SQL执行时间,Java组看GC日志,但没人看“SQL执行完到结果返回给应用”之间的那100ms发生了什么。本章所有优化方案,都强制要求画出“跨层时序图”,标出每一毫秒在哪个环节消耗,这是破除部门墙的技术语言。
3.3 反直觉优化:为什么把Redis缓存从本地升级到集群,反而让接口变慢了?
这是新手最容易栽跟头的地方。模拟项目X的用户信息接口,最初用Caffeine做本地缓存,QPS 5000时P95=15ms。为提升一致性,团队决定迁移到Redis Cluster,结果QPS 3000时P95飙升到210ms。监控显示Redis CPU<40%,网络延迟<0.5ms,一切“正常”。问题出在序列化成本上。本地Caffeine缓存的是UserDO对象,而Redis需要序列化成JSON字符串。我们用jmh基准测试:
@Benchmark public String jsonSerialize() { return objectMapper.writeValueAsString(user); // Jackson } @Benchmark public byte[] kryoSerialize() { return kryo.writeClassAndObject(output, user).toByteArray(); // Kryo }结果:Jackson序列化耗时120μs,Kryo仅8μs。但更致命的是网络往返次数。Redis Cluster的GET user:123命令,客户端SDK(Lettuce)会先查slot映射,再路由到对应节点,平均增加2次RTT(约1.2ms)。而本地缓存是内存读取,<100ns。解决方案不是换序列化器,而是分层缓存:
- L1:Caffeine本地缓存,TTL=10s,存储高频访问的1000个用户;
- L2:Redis Cluster,TTL=30min,存储全量用户;
- 应用层逻辑:先查L1,未命中则查L2,L2命中后异步写回L1。
这样,95%的请求走L1,耗时仍<20ms;5%的请求走L2+回填,但避免了所有请求都承受网络开销。实测QPS回到4500,P95=18ms。这个案例揭示了一个铁律:任何引入网络IO的优化,必须量化其延迟成本,并证明收益远大于成本。否则,就是用分布式复杂度,换取微不足道的一致性提升。
4. 实操过程与核心环节实现:手把手复现“订单提交延迟突增”案例
4.1 环境准备:用Docker Compose搭建可重现的混沌环境
所有优化必须在可控环境中验证。我们用Docker Compose构建模拟项目X的最小闭环:
# docker-compose.yml version: '3.8' services: order-service: image: openjdk:17-jdk-slim ports: ["8080:8080"] environment: - SPRING_PROFILES_ACTIVE=docker - REDIS_HOST=redis - DB_URL=jdbc:mysql://mysql:3306/order_db depends_on: [mysql, redis, kafka] # 关键:限制资源,暴露瓶颈 deploy: resources: limits: cpus: '1.0' memory: 1G # 注入网络混沌 command: > sh -c " tc qdisc add dev eth0 root handle 1: htb default 12 && tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit && exec java -jar /app.jar " mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: order_db # 模拟磁盘IO瓶颈 command: --innodb-buffer-pool-size=256M --innodb-log-file-size=64M redis: image: redis:7-alpine # 限制内存,触发LRU淘汰 command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru kafka: image: bitnami/kafka:3.4 environment: KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092这个配置的关键点在于:
order-service限制1核CPU,确保CPU争抢可复现;mysql的innodb-buffer-pool-size设为256M,远小于订单表数据量(5GB),强制频繁磁盘读;redis内存限制128MB,当缓存用户数据超限时触发LRU,模拟缓存失效风暴。
启动后,用docker stats实时观察各容器资源占用,这是所有优化的起点——没有基线数据,优化就是空中楼阁。
4.2 压测脚本编写:用Gatling模拟真实用户行为,而非简单并发
JMeter的“线程组”模型过于简单,无法模拟真实用户。Gatling的DSL能精准描述用户旅程:
class OrderSimulation extends Simulation { val httpProtocol = http .baseUrl("http://localhost:8080") .acceptHeader("application/json") val scn = scenario("Order Submit Flow") .exec(http("Get User Info") .get("/api/user/${userId}") .check(status.is(200)) .check(jsonPath("$.balance").saveAs("userBalance"))) .pause(1) // 用户思考时间 .exec(http("Submit Order") .post("/api/order") .body(StringBody("""{"userId": ${userId}, "amount": ${userBalance}}""")) .check(status.is(200)) .check(jsonPath("$.orderId").saveAs("orderId"))) .pause(2 seconds, 5 seconds) // 随机等待,模拟网络波动 .exec(http("Query Order Status") .get("/api/order/${orderId}") .check(status.is(200))) setUp(scn.inject(atOnceUsers(100)).protocols(httpProtocol)) }这个脚本的价值在于:
pause(1)模拟用户阅读页面的时间,避免请求洪峰;pause(2 seconds, 5 seconds)用随机等待模拟4G/5G网络切换;check(jsonPath("$.balance").saveAs("userBalance"))将上一步结果注入下一步,保证数据链路真实。
运行mvn gatling:test后,Gatling自动生成HTML报告,重点关注Request Rate(请求率)和Response Time Distribution(响应时间分布),而不是简单的平均值。P95/P99才是用户真实感知的延迟。
4.3 瓶颈定位实战:从火焰图到源码的三步归因法
当压测显示Submit Order接口P99=1500ms时,按以下步骤定位:
第一步:全局热点扫描
# 在order-service容器内执行 sudo perf record -F 99 -g -p $(pgrep -f "java.*OrderService") -- sleep 30 sudo perf script > perf-report.txt # 生成火焰图 ./FlameGraph/stackcollapse-perf.pl perf-report.txt | ./FlameGraph/flamegraph.pl > flame.svg打开flame.svg,发现org.springframework.jdbc.core.JdbcTemplate.queryForObject占据45%宽度,说明数据库是瓶颈。
第二步:SQL执行深度追踪
# 用bpftrace跟踪JDBC execute bpftrace -e ' kprobe:__sys_sendto / pid == $1 / { @start[tid] = nsecs; } kretprobe:__sys_sendto / @start[tid] && (nsecs - @start[tid]) > 100000000 / { $delta = (nsecs - @start[tid]) / 1000000; printf("Slow DB call: %d ms\n", $delta); print(ustack); } ' $(pgrep -f "java.*OrderService")输出显示:com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal调用后,__sys_sendto耗时>100ms,确认是网络或DB端问题。
第三步:MySQL内核级分析
进入mysql容器:
-- 查看当前慢查询 SELECT * FROM performance_schema.events_statements_summary_by_digest WHERE DIGEST_TEXT LIKE '%INSERT INTO order%' ORDER BY AVG_TIMER_WAIT DESC LIMIT 1; -- 查看锁等待 SELECT * FROM performance_schema.data_lock_waits;发现INSERT INTO order语句的AVG_TIMER_WAIT为850ms,且data_lock_waits显示大量TABLE锁等待。根因是:订单表缺少user_id索引,导致INSERT ... SELECT语句全表扫描并锁表。解决方案:ALTER TABLE order ADD INDEX idx_user_id (user_id);。执行后,P99降至220ms。整个过程耗时18分钟,比盲目调优高效十倍。
4.4 优化效果验证:用Prometheus+Grafana构建黄金信号看板
优化不是改完代码就结束,必须用数据证明价值。我们在Prometheus中配置关键指标:
# prometheus.yml scrape_configs: - job_name: 'order-service' static_configs: - targets: ['host.docker.internal:8080'] metrics_path: '/actuator/prometheus' # 自定义业务指标 metric_relabel_configs: - source_labels: [__name__] regex: 'order_submit_success_rate|order_submit_p95_latency_ms' action: keepGrafana看板配置四个核心面板:
| 面板名称 | 数据源 | 关键公式 | 业务意义 |
|---|---|---|---|
| 订单提交成功率 | Prometheus | rate(order_submit_success_count[5m]) / rate(order_submit_total[5m]) | 低于99.5%触发告警 |
| P95提交延迟 | Prometheus | histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{uri="/api/order"}[5m])) by (le)) | 超过300ms标红 |
| 数据库连接等待 | MySQL Exporter | mysql_global_status_threads_connected / mysql_global_status_threads_created | >0.8说明连接池不足 |
| 缓存命中率 | Redis Exporter | redis_keyspace_hits / (redis_keyspace_hits + redis_keyspace_misses) | <95%需检查缓存策略 |
这个看板的价值在于:它不展示“技术指标”,而是直接映射业务健康度。当P95延迟上升时,运营同学能立刻看到“每分钟损失多少订单”,技术决策从此有了业务语言。
5. 常见问题与排查技巧实录:那些文档里不会写的血泪经验
5.1 “优化后性能反而下降”——时间戳精度陷阱
某次优化中,我们将订单ID生成从System.currentTimeMillis()改为Snowflake算法,期望降低时钟同步开销。结果压测P99延迟上升了200ms。用perf record -e cycles,instructions对比发现,Snowflake.nextId()的指令数是currentTimeMillis()的3倍。根因是:Snowflake内部用synchronized块保护workerId生成,而currentTimeMillis()是JVM内联的本地方法。更隐蔽的问题是:System.nanoTime()在某些虚拟化环境下(如VMware)存在精度漂移,导致Snowflake的时间戳计算错误,触发重试逻辑。解决方案:
- 优先用
ThreadLocalRandom.current().nextLong()生成ID(无锁,足够随机); - 若必须用Snowflake,将
synchronized块替换为AtomicLong; - 在Docker中运行时,添加
--cap-add=SYS_TIME权限,确保纳秒级时钟可用。
这个案例的教训:任何“理论上更快”的算法,必须在目标环境实测。JVM的currentTimeMillis()经过数十年优化,其内联效率远超手写算法。
5.2 “监控显示一切正常,但用户就是卡”——前端资源加载瀑布图盲区
很多团队只关注后端API延迟,却忽略前端资源加载。模拟项目X的订单页,后端API P95=80ms,但用户实测首屏时间>5s。用Chrome DevTools的Network面板查看瀑布图,发现main.js加载耗时4.2s。进一步检查:
main.js体积2.8MB(未压缩);- Nginx未开启
gzip on; - CDN未配置
Cache-Control: public, max-age=31536000。
解决方案分三步:
- Webpack配置
optimization.splitChunks,将lodash等第三方库抽离为vendor.js; - Nginx添加:
gzip on; gzip_types application/javascript text/css; gzip_vary on; - CDN设置:
Cache-Control: public, max-age=31536000, immutable。
优化后,main.js加载降至320ms,首屏时间<1.2s。关键洞察:前端性能是后端性能的放大器。后端快10倍,若前端资源加载慢100倍,用户感知仍是“卡”。
5.3 “为什么同样的优化,在测试环境有效,生产环境无效?”——内核参数差异
某次将net.core.somaxconn从128调至65535,测试环境QPS提升40%,生产环境却无变化。用sysctl -a | grep somaxconn对比发现:
- 测试环境:
net.core.somaxconn = 65535 - 生产环境:
net.core.somaxconn = 65535,但net.ipv4.tcp_max_syn_backlog = 1024
根因是:somaxconn是应用层listen()的队列长度,而tcp_max_syn_backlog是内核SYN队列长度,当SYN队列满时,新连接被丢弃,应用层永远收不到accept()请求。生产环境需同步调整:
# 临时生效 sudo sysctl -w net.ipv4.tcp_max_syn_backlog=65535 # 永久生效 echo "net.ipv4.tcp_max_syn_backlog = 65535" | sudo tee -a /etc/sysctl.conf这个案例说明:生产环境的内核参数,必须和应用配置同频演进。我们建立了“内核参数清单”,每次应用升级,都同步检查/proc/sys/net/下的关键参数。
5.4 “如何判断优化是否真的必要?”——成本效益分析表
不是所有慢接口都值得优化。我们用一张表决策:
| 指标 | 计算方式 | 阈值 | 说明 |
|---|---|---|---|
| 业务影响权重 | 日均调用量 × 单次失败损失(元) | >5000元/天 | 如支付回调失败,每单损失佣金5元,日调用2000次,则权重=10000元 |
| 技术可优化空间 | 当前P99延迟 ÷ 行业基准延迟 | >3倍 | 行业基准:订单提交<300ms,当前1200ms,则空间=4倍 |
| 实施成本 | 预估人天 × 工程师时薪 | <2万元 | 包含开发、测试、灰度、回滚预案 |
| ROI | (业务影响权重 × 技术空间) ÷ 实施成本 | >2 | ROI>2才启动优化 |
| 这张表让我们砍掉了7个“看起来很美”的优化需求,聚焦在真正创造价值的3个案例上。这才是专业性能工程师的思维方式——用业务语言谈技术,用财务语言谈架构。 |
提示:所有
perf、bpftrace命令需在容器内以root权限运行,Docker启动时添加--privileged或--cap-add=SYS_ADMIN。
注意:tc网络限速会影响所有容器流量,压测时建议单独启一个网络命名空间:ip netns add testns && ip netns exec testns bash。
实操心得:火焰图不是终点,而是起点。看到JDBC.execute热点后,必须用bpftrace确认是网络层还是数据库层耗时,否则可能误判为“DB慢”,实则为“网络丢包”。
常见误区:认为“加缓存=性能提升”。缓存失效时的穿透压力,可能比不加缓存更致命。务必在缓存层添加熔断(如Resilience4j)和降级(返回旧数据)。