系统实战 - 逻辑多租服务的高并发调优
性能优化是没有上限的,当系统能满足用户业务诉求,且系统成本可接受情况下,就可以点到为止了!由于系统开发语言是 Go,所以本文的相关例子均使用 Go 相关的内容了。
一、工具准备
高并发调优,实际上就是使用熟练的使用工具,压测出来具体的瓶颈点,然后针对性的优化;当然,熟悉业务的情况下,能更好的从系统架构层面优化。go 程序中常用的性能优化工具如下:
go pprof
黑盒形式:通过 pprof 抓取到系统的瓶颈分布点
benchmark
白盒形式:感知到系统瓶颈后,针对改造后的方法,可以快速使用 benchmark 来本地测试,修改后的代码是否符合预期,避免频繁的发布上线来测试
二、方法
优化的最底层实际上是优化计算资源性能、网络资源性能、存储资源性能,对应的是 cpu、内存、网络带宽、http 连接、磁盘 I/O
对应的业务层可以做的事情:
端到端调用链路分析,解决依赖服务和系统自身瓶颈,任何一个组件都有瓶颈问题
扩 cpu、内存资源
扩连接数资源
扩网络带宽资源
扩 ssd 磁盘性能
重复数据,本地缓存化
redis、etcd、数据库等元数据,可以本地缓存化,减少网络 I/O
token 认证、鉴权、签名生成,可以本地缓存化,定时刷新本地 token
参数类校验,涉及正则匹配等吃 cpu 的操作,本地缓存化
善用 sync.Pool,将重复使用的对象缓存下来,减少对象生成次数,GC 次数,例
json.unmarshal 时,反序列化对象使用 sync.Pool 进行改造
ioutil.ReadAll 改造成 bytes.NewBuffer 结合 sync.Pool 进行改造
减少共享资源的竞争
读远大于写的,尽量使用读写锁
写远大于读的,考虑优化架构(不使用全局锁、锁粒度尽量小、转换成异步操作,非必要不在高并发场景走该逻辑),尽量不要出现该场景
能做到租户之间的资源隔离,甚至是更小力度的隔离,非必要不使用全局的控制变量
注意共享 channel 的使用
减少阻塞性操作
全局性的变量做流量控制时,避免单租户给阻塞,做到异常场景下的租户隔离
统计指标、审计日志、健康检查等任务,使用异步任务,非阻塞式的去执行
注意共享 channel 的使用
池化,将资源做到可控
http 连接的池化,防止连接数量不够,爆掉了;另外复用连接,防止频繁新建,提升性能
请求内容的池化,防止请求 body 过大,将内存爆掉了
减少日志打印
只打印最关键的日志,另外日志中字符串拼接选用高性能的 api(例 buffer)
减少重复代码执行逻辑,提前返回
三、实践
1、工具的用法
1)go pprof 观测系统火焰图
在待优化的程序的 main 函数中,引入 pprof 包,开启 http 服务,监听系统资源的变化
使用 Jmeter 直接对核心接口进行压测,其中 Jmeter 里面的参数配置勾选上“Same user on each iteration”,确保复用线程,避免 Jmeter 频繁新建线程,引起不必要的开销
在本地电脑的 cmd 窗口界面,输入一下命令进行获取远程数据面的性能数据,加上-http 后,可以直接弹出浏览器页面,在浏览器页面上可以看到性能引用图,以及火焰图,以及 top 数据,根据图像数据定位具体的 cpu 消耗点,以及内存泄露点
获取 cpu 的数据
go tool pprof -http=:8888 http://123.123.123.123:8888/debug/pprof/profile
获取内存数据
go tool pprof -http=:8888 http://123.123.123.123:8888/debug/pprof/heap
获取 goroutine 数据
其中,debug=1 时可以查看泄露的协程总数,debug=2 可以查看具体协程阻塞多久
http://123.123.123.123:8888/debug/pprof/goroutine?debug=1
也可已在控制台直接查看 pprof 的信息
在控制台输入:
go tool pprof http://123.123.123.123:8888/debug/pprof/goroutine?debug=1
使用 traces,展示函数的调用链
2)关键指标的观测,配合火焰图,定位问题
系统时延
关键的组件端到端调度时延,一定要有业务日志记录
查看进程 cpu 和内存消耗
top
查看容器的监控指标
docker stats {containerId}
查看当前某个端口连接数
netstat –ano|grep 31220 |wc -l
查看当前句柄数目最多的进程
lsof -n|awk '{print $2}'| sort | uniq -c | sort -nr | head
2)代码改造实战
http 连接调优
使用 http2 的连接池特性,自建长连接池,复用连接,避免新连接导致的性能开销
fasthttp 替换 net/http,使用更高性能的开源组件
本地缓存
redis+本地 gcache,存储数据库中的元数据,加速元数据访问
token、签名本地缓存,支持定时刷新
sync.Pool 缓存重复重建的对象,减少 gc 频率
锁力度优化
优化阻塞性的全局锁,改造业务逻辑,避免出现全局锁竞争问题
非关键流程,同步阻塞改造成异步阻塞
读多写少的场景,将互斥锁全部改造成读写锁
推荐参考:
go 的高性能编程 Go 语言高性能编程 | 极客兔兔 (geektutu.com)
sync.Pool 优化实战 Golang 内存分配优化 – 萌叔 (vearne.cc)
go pprof 的可参考 Golang 大杀器之性能剖析 PProf - SegmentFault 思否
fasthttp 和 net/http 的区别 net/http与fasthttp区别 - 简书 (jianshu.com)
评论