Tips
Go
(18条消息) Go语言自学系列 | golang包_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之channel的遍历_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之select switch_COCOgsta的博客-CSDN博客_golang select switch
(18条消息) Go语言自学系列 | golang并发编程之runtime包_COCOgsta的博客-CSDN博客_golang runtime包
(18条消息) Go语言自学系列 | golang接口值类型接收者和指针类型接收者_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之Timer_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang方法_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之WaitGroup实现同步_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang构造函数_COCOgsta的博客-CSDN博客_golang 构造函数
(18条消息) Go语言自学系列 | golang方法接收者类型_COCOgsta的博客-CSDN博客_golang 方法接收者
(18条消息) Go语言自学系列 | golang接口_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang接口和类型的关系_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang结构体_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang结构体_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang标准库os模块 - File文件读操作_COCOgsta的博客-CSDN博客_golang os.file
(18条消息) Go语言自学系列 | golang继承_COCOgsta的博客-CSDN博客_golang 继承
(18条消息) Go语言自学系列 | golang嵌套结构体_COCOgsta的博客-CSDN博客_golang 结构体嵌套
(18条消息) Go语言自学系列 | golang并发编程之Mutex互斥锁实现同步_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发变成之通道channel_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之原子操作详解_COCOgsta的博客-CSDN博客_golang 原子操作
(18条消息) Go语言自学系列 | golang并发编程之原子变量的引入_COCOgsta的博客-CSDN博客_go 原子变量
(18条消息) Go语言自学系列 | golang并发编程之协程_COCOgsta的博客-CSDN博客_golang 协程 并发
(18条消息) Go语言自学系列 | golang接口嵌套_COCOgsta的博客-CSDN博客_golang 接口嵌套
(18条消息) Go语言自学系列 | golang包管理工具go module_COCOgsta的博客-CSDN博客_golang 包管理器
(18条消息) Go语言自学系列 | golang标准库os模块 - File文件写操作_COCOgsta的博客-CSDN博客_go os模块
(18条消息) Go语言自学系列 | golang结构体的初始化_COCOgsta的博客-CSDN博客_golang 结构体初始化
(18条消息) Go语言自学系列 | golang通过接口实现OCP设计原则_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang标准库os包进程相关操作_COCOgsta的博客-CSDN博客_golang os包
(18条消息) Go语言自学系列 | golang标准库ioutil包_COCOgsta的博客-CSDN博客_golang ioutil
(18条消息) Go语言自学系列 | golang标准库os模块 - 文件目录相关_COCOgsta的博客-CSDN博客_go语言os库
Golang技术栈,Golang文章、教程、视频分享!
(18条消息) Go语言自学系列 | golang结构体指针_COCOgsta的博客-CSDN博客_golang 结构体指针
Ansible
太厉害了,终于有人能把Ansible讲的明明白白了,建议收藏_互联网老辛
ansible.cfg配置详解
Docker
Docker部署
linux安装docker和Docker Compose
linux 安装 docker
Docker中安装Docker遇到的问题处理
Docker常用命令
docker常用命令小结
docker 彻底卸载
Docker pull 时报错:Get https://registry-1.docker.io/v2/library/mysql: net/http: TLS handshake timeout
Docker 拉镜像无法访问 registry-x.docker.io 问题(Centos7)
docker 容器内没有权限
Linux中关闭selinux的方法是什么?
docker run 生成 docker-compose
Docker覆盖网络部署
docker pull后台拉取镜像
docker hub
Redis
Redis 集群别乱搭,这才是正确的姿势
linux_离线_redis安装
怎么实现Redis的高可用?(主从、哨兵、集群) - 雨点的名字 - 博客园
redis集群离线安装
always-show-logo yes
Redis集群搭建及原理
[ERR] Node 172.168.63.202:7001 is not empty. Either the nodealready knows other nodes (check with CLUSTER NODES) or contains some - 亲爱的不二999 - 博客园
Redis daemonize介绍
redis 下载地址
Redis的redis.conf配置注释详解(三) - 云+社区 - 腾讯云
Redis的redis.conf配置注释详解(一) - 云+社区 - 腾讯云
Redis的redis.conf配置注释详解(二) - 云+社区 - 腾讯云
Redis的redis.conf配置注释详解(四) - 云+社区 - 腾讯云
Linux
在终端连接ssh的断开关闭退出的方法
漏洞扫描 - 灰信网(软件开发博客聚合)
find 命令的参数详解
vim 编辑器搜索功能
非root安装rpm时,mockbuild does not exist
Using a SSH password instead of a key is not possible because Host Key checking
(9条消息) 安全扫描5353端口mDNS服务漏洞问题_NamiJava的博客-CSDN博客_5353端口
Linux中使用rpm命令安装rpm包
ssh-copy-id非22端口的使用方法
How To Resolve SSH Weak Key Exchange Algorithms on CentOS7 or RHEL7 - infotechys.com
Linux cp 命令
yum 下载全量依赖 rpm 包及离线安装(终极解决方案) - 叨叨软件测试 - 博客园
How To Resolve SSH Weak Key Exchange Algorithms on CentOS7 or RHEL7 - infotechys.com
RPM zlib 下载地址
运维架构网站
欢迎来到 Jinja2
/usr/local/bin/ss-server -uv -c /etc/shadowsocks-libev/config.json -f /var/run/s
ruby 安装Openssl 默认安装位置
Linux 常用命令学习 | 菜鸟教程
linux 重命名文件和文件夹
linux命令快速指南
ipvsadm
Linux 下查找日志中的关键字
Linux 切割大 log 日志
CentOS7 关于网络的设置
rsync 命令_Linux rsync 命令用法详解:远程数据同步工具
linux 可视化界面安装
[问题已处理]-执行yum卡住无响应
GCC/G++升级高版本
ELK
Docker部署ELK
ELK+kafka+filebeat+Prometheus+Grafana - SegmentFault 思否
(9条消息) Elasticsearch设置账号密码_huas_xq的博客-CSDN博客_elasticsearch设置密码
Elasticsearch 7.X 性能优化
Elasticsearch-滚动更新
Elasticsearch 的内存优化_大数据系统
Elasticsearch之yml配置文件
ES 索引为Yellow状态
Logstash:Grok filter 入门
logstash grok 多项匹配
Mysql
Mysql相关Tip
基于ShardingJDBC实现数据库读写分离 - 墨天轮
MySQL-MHA高可用方案
京东三面:我要查询千万级数据量的表,怎么操作?
OpenStack
(16条消息) openstack项目中遇到的各种问题总结 其二(云主机迁移、ceph及扩展分区)_weixin_34104341的博客-CSDN博客
OpenStack组件介绍
百度大佬OpenStack流程
openstack各组件介绍
OpenStack生产实际问题总结(一)
OpenStack Train版离线部署
使用Packstack搭建OpenStack
K8S
K8S部署
K8S 集群部署
kubeadm 重新 init 和 join-pudn.com
Kubernetes 实战总结 - 阿里云 ECS 自建 K8S 集群 Kubernetes 实战总结 - 自定义 Prometheus
【K8S实战系列-清理篇1】k8s docker 删除没用的资源
Flannel Pod Bug汇总
Java
Jdk 部署
JDK部署
java线程池ThreadPoolExecutor类使用详解 - bigfan - 博客园
ShardingJDBC实现多数据库节点分库分表 - 墨天轮
Maven Repository: Search/Browse/Explore
其他
Git在阿里,我们如何管理代码分支?
chrome F12调试网页出现Paused in debugger
体验IntelliJ IDEA的远程开发(Remote Development) - 掘金
Idea远程调试
PDF转MD
强哥分享干货
优秀开源项目集合
vercel 配合Github 搭建项目Doc门户
如何用 Github Issues 写技术博客?
Idea 2021.3 Maven 3.8.1 报错 Blocked mirror for repositories 解决
列出maven依赖
[2022-09 持续更新] 谷歌 google 镜像 / Sci-Hub 可用网址 / Github 镜像可用网址总结
阿里云ECS迁移
linux访问github
一文教你使用 Docker 启动并安装 Nacos-腾讯云开发者社区-腾讯云
Nginx
Nginx 部署
Nginx 部署安装
Nginx反向代理cookie丢失的问题_longzhoufeng的博客-CSDN博客_nginx 代理后cookie丢失
Linux 系统 Https 证书生成与Nginx配置 https
数据仓库
实时数仓
松果出行 x StarRocks:实时数仓新范式的实践之路
实时数据仓库的一些分层和分层需要处理的事情,以及数据流向
湖仓一体电商项目
湖仓一体电商项目(一):项目背景和架构介绍
湖仓一体电商项目(二):项目使用技术及版本和基础环境准备
湖仓一体电商项目(三):3万字带你从头开始搭建12个大数据项目基础组件
数仓笔记
数仓学习总结
数仓常用平台和框架
数仓学习笔记
数仓技术选型
尚硅谷教程
尚硅谷学习笔记
尚硅谷所有已知的课件资料
尚硅谷大数据项目之尚品汇(11数据质量管理V4.0)
尚硅谷大数据项目之尚品汇(10元数据管理AtlasV4.0)
尚硅谷大数据项目之尚品汇(9权限管理RangerV4.0)
尚硅谷大数据项目之尚品汇(8安全环境实战V4.0)
尚硅谷大数据项目之尚品汇(7用户认证KerberosV4.1)
尚硅谷大数据项目之尚品汇(6集群监控ZabbixV4.1)
尚硅谷大数据项目之尚品汇(5即席查询PrestoKylinV4.0)
尚硅谷大数据项目之尚品汇(4可视化报表SupersetV4.0)
尚硅谷大数据项目之尚品汇(3数据仓库系统)V4.2.0
尚硅谷大数据项目之尚品汇(2业务数据采集平台)V4.1.0
尚硅谷大数据项目之尚品汇(1用户行为采集平台)V4.1.0
数仓治理
数据中台 元数据规范
数据中台的那些 “经验与陷阱”
2万字详解数据仓库数据指标数据治理体系建设方法论
数据仓库,为什么需要分层建设和管理? | 人人都是产品经理
网易数帆数据治理演进
数仓技术
一文看懂大数据生态圈完整知识体系
阿里云—升舱 - 数据仓库升级白皮书
最全企业级数仓建设迭代版(4W字建议收藏)
基于Hue,Dolphinscheduler,HIVE分析数据仓库层级实现及项目需求案例实践分析
详解数据仓库分层架构
数据仓库技术细节
大数据平台组件介绍
总览 2016-2021 年全球机器学习、人工智能和大数据行业技术地图
Apache DolphinScheduler 3.0.0 正式版发布!
数据仓库面试题——介绍下数据仓库
数据仓库为什么要分层,各层的作用是什么
Databend v0.8 发布,基于 Rust 开发的现代化云数据仓库 - OSCHINA - 中文开源技术交流社区
数据中台
数据中台设计
大数据同步工具之 FlinkCDC/Canal/Debezium 对比
有数数据开发平台文档
Shell
Linux Shell 命令参数
shell 脚本编程
一篇教会你写 90% 的 Shell 脚本
Kibana
Kibana 查询语言(KQL)
Kibana:在 Kibana 中的四种表格制作方式
Kafka
Kafka部署
canal 动态监控 Mysql,将 binlog 日志解析后,把采集到的数据发送到 Kafka
OpenApi
OpenAPI 标准规范,了解一下?
OpenApi学术论文
贵阳市政府数据开放平台设计与实现
OpenAPI简介
开放平台:运营模式与技术架构研究综述
管理
技术部门Leader是不是一定要技术大牛担任?
华为管理体系流程介绍
DevOps
*Ops
XOps 已经成为一个流行的术语 - 它是什么?
Practical Linux DevOps
Jenkins 2.x实践指南 (翟志军)
Jenkins 2权威指南 ((美)布伦特·莱斯特(Brent Laster)
DevOps组件高可用的思路
KeepAlived
VIP + KEEPALIVED + LVS 遇到Connection Peer的问题的解决
MinIO
MinIO部署
Minio 分布式集群搭建部署
Minio 入门系列【16】Minio 分片上传文件 putObject 接口流程源码分析
MinioAPI 浅入及问题
部署 minio 兼容 aws S3 模式
超详细分布式对象存储 MinIO 实战教程
Hadoop
Hadoop 部署
Hadoop集群部署
windows 搭建 hadoop 环境(解决 HADOOP_HOME and hadoop.home.dir are unset
Hadoop 集群搭建和简单应用(参考下文)
Hadoop 启动 NameNode 报错 ERROR: Cannot set priority of namenode process 2639
jps 命令查看 DataNode 进程不见了 (hadoop3.0 亲测可用)
hadoop 报错: Operation category READ is not supported in state standby
Spark
Spark 部署
Spark 集群部署
spark 心跳超时分析 Cannot receive any reply in 120 seconds
Spark学习笔记
apache spark - Failed to find data source: parquet, when building with sbt assembly
Spark Thrift Server 架构和原理介绍
InLong
InLong 部署
Apache InLong部署文档
安装部署 - Docker 部署 - 《Apache InLong v1.2 中文文档》 - 书栈网 · BookStack
基于 Apache Flink SQL 的 InLong Sort ETL 方案解析
关于 Apache Pulsar 在 Apache InLong 接入数据
zookeeper
zookeeper 部署
使用 Docker 搭建 Zookeeper 集群
美团技术团队
StarRocks
StarRocks技术白皮书(在线版)
JuiceFS
AI 场景存储优化:云知声超算平台基于 JuiceFS 的存储实践
JuiceFS 在 Elasticsearch/ClickHouse 温冷数据存储中的实践
JuiceFS format
元数据备份和恢复 | JuiceFS Document Center
JuiceFS 元数据引擎选型指南
Apache Hudi 使用文件聚类功能 (Clustering) 解决小文件过多的问题
普罗米修斯
k8s 之 Prometheus(普罗米修斯)监控,简单梳理下 K8S 监控流程
k8s 部署 - 使用helm3部署监控prometheus(普罗米修斯),从零到有,一文搞定
k8s 部署 - 使用 helm3 部署监控 prometheus(普罗米修斯),从零到有,一文搞定
k8s 部署 - 如何完善 k8s 中 Prometheus(普罗米修斯)监控项目呢?
k8s 部署 - k8s 中 Prometheus(普罗米修斯)的大屏展示 Grafana + 监控报警
zabbix
一文带你掌握 Zabbix 监控系统
Stream Collectors
Nvidia
Nvidia API
CUDA Nvidia驱动安装
NVIDIA驱动失效简单解决方案:NVIDIA-SMI has failed because it couldn‘t communicate with the NVIDIA driver.
ubuntu 20 CUDA12.1安装流程
nvidia开启持久化模式
nvidia-smi 开启持久化
Harbor
Harbor部署文档
Docker 爆出 it doesn't contain any IP SANs
pandoc
其他知识
大模型
COS 597G (Fall 2022): Understanding Large Language Models
如何优雅的使用各类LLM
ChatGLM3在线搜索功能升级
当ChatGLM3能用搜索引擎时
OCR神器,PDF、数学公式都能转
Stable Diffusion 动画animatediff-cli-prompt-travel
基于ERNIE Bot自定义虚拟数字人生成
pika负面提示词
开通GPT4的方式
GPT4网站
低价开通GPT Plus
大模型应用场景分享
AppAgent AutoGPT变体
机器学习
最大似然估计
权衡偏差(Bias)和方差(Variance)以最小化均方误差(Mean Squared Error, MSE)
伯努利分布
方差计算公式
均值的高斯分布估计
没有免费午餐定理
贝叶斯误差
非参数模型
最近邻回归
表示容量
最优容量
权重衰减
正则化项
Sora
Sora官方提示词
看完32篇论文,你大概就知道Sora如何炼成? |【经纬低调出品】
Sora论文
Sora 物理悖谬的几何解释
Sora 技术栈讨论
RAG垂直落地
DB-GPT与TeleChat-7B搭建相关RAG知识库
ChatWithRTX
ChatRTX安装教程
ChatWithRTX 踩坑记录
ChatWithRTX 使用其他量化模型
ChatWithRTX介绍
RAG 相关资料
英伟达—大模型结合 RAG 构建客服场景自动问答
又一大模型技术开源!有道自研RAG引擎QAnything正式开放下载
收藏!RAG入门参考资料开源大总结:RAG综述、介绍、比较、预处理、RAG Embedding等
RAG调研
解决现代RAG实际生产问题
解决现代 RAG 系统中的生产问题-II
Modular RAG and RAG Flow: Part Ⅰ
Modular RAG and RAG Flow: Part II
先进的Retriever技术来增强你的RAGs
高级RAG — 使用假设文档嵌入 (HyDE) 改进检索
提升 RAG:选择最佳嵌入和 Reranker 模型
LangGraph
增强型RAG:re-rank
LightRAG:使用 PyTorch 为 LLM 应用程序提供支持
RAG 101:分块策略
模型训练
GPU相关资料
[教程] conda安装简明教程(基于miniconda和Windows)
PyTorch CUDA对应版本 | PyTorch
资料
李一舟课程全集
零碎资料
苹果各服共享ID
数据中心网络技术概览
华为大模型训练学习笔记
百度AIGC工程师认证考试答案(可换取工信部证书)
百度智能云生成式AI认证工程师 考试和证书查询指南
深入理解 Megatron-LM(1)基础知识
QAnything
接入QAnything的AI问答知识库,可私有化部署的企业级WIKI知识库
wsl --update失效Error code: Wsl/UpdatePackage/0x80240438的解决办法
Docker Desktop 启动docker engine一直转圈解决方法
win10开启了hyper-v,docker 启动还是报错 docker desktop windows hypervisor is not present
WSL虚拟磁盘过大,ext4迁移 Windows 中创建软链接和硬链接
WSL2切换默认的Linux子系统
Windows的WSL子系统,自动开启sshd服务
新版docker desktop设置wsl(使用windown的子系统)
WSL 开启ssh
Windows安装网易开源QAnything打造智能客服系统
芯片
国内互联网大厂自研芯片梳理
超算平台—算力供应商
Linux 磁盘扩容
Linux使用growpart工具进行磁盘热扩容(非LVM扩容方式)
关于centos7 扩容提示no tools available to resize disk with 'gpt' - o夜雨随风o - 博客园
(小插曲)neo4j配置apoc插件后检查版本发现:Unknown function ‘apoc.version‘ “EXPLAIN RETURN apoc.version()“
vfio-pci与igb_uio映射硬件资源到DPDK的流程分析
KubeVirt
vnc server配置、启动、重启与连接 - 王约翰 - 博客园
虚拟机Bug解决方案
kubevirt 如何通过CDI上传镜像文件
在 K8S 上也能跑 VM!KubeVirt 簡介與建立(部署篇) | Cloud Solutions
KubeVirt 04:容器化数据导入 – 小菜园
Python
安装 flash_attn
手把手教你在linux上安装pytorch与cuda
AI
在启智社区基于PyTorch运行国产算力卡的模型训练实验
Scaling law
免费的GPT3.5 API
AI Engineer Roadmap & Resources 🤖
模型排行
edk2
K8S删除Evicted状态的pod
docker 中启动 docker
远程本地多用户桌面1.17(一种不让电脑跟你抢键鼠的思路) - 哔哩哔哩
华为鲲鹏服务器(ARM架构)部署Prometheus
在Linux上安装配置Grafana_AI开发平台ModelArts_华为云
abrt-ccpp干崩服务器查询记录
kubevirt 中文社区
VNCServer 连接方法
Pod创建流程代码版本[kubelet篇]
[译]深入剖析 Kubernetes MutatingAdmissionWebhook-腾讯云开发者社区-腾讯云
[译]深入剖析 Kubernetes MutatingAdmissionWebhook-腾讯云开发者社区-腾讯云
深入理解 Kubernetes Admission Webhook-阳明的博客
CentOS7 安装 mbedtls和mbedtls-devel
docker in docker 启动命令
go 协程泄漏 pprof
-
+
首页
go 协程泄漏 pprof
**声明:**本文参考这篇文章,自己跟着做了一遍,记录一下,然后补充了火焰图的使用。 [https://blog.wolfogre.com/posts/go-ppof-practice/](https://blog.wolfogre.com/posts/go-ppof-practice/) [https://www.liwenzhou.com/posts/Go/performance\_optimisation/#autoid-1-5-2](https://www.liwenzhou.com/posts/Go/performance_optimisation/#autoid-1-5-2) [https://blog.wolfogre.com/posts/go-ppof-practice/](https://blog.wolfogre.com/posts/go-ppof-practice/) [https://github.com/wolfogre/blog-utterances/issues/39](https://github.com/wolfogre/blog-utterances/issues/39) [https://golang2.eddycjy.com/posts/ch6/01-pprof-1/](https://golang2.eddycjy.com/posts/ch6/01-pprof-1/) [https://github.com/eddycjy/blog](https://github.com/eddycjy/blog) [https://github.com/brendangregg/FlameGraph/blob/master/README.md](https://github.com/brendangregg/FlameGraph/blob/master/README.md) [\[鸟窝\]-\[译\] Go 可视化性能分析工具](https://colobu.com/2017/03/02/a-short-survey-of-golang-pprof/) [https://www.cnblogs.com/jiujuan/p/14604609.html](https://www.cnblogs.com/jiujuan/p/14604609.html) ## [](#1-1-性能分析)1.1 性能分析 > 在软件工程中,性能分析(Performance Analysis,也称为Profiling),是以收集程序运行时信息为手段研究程序行为的分析方法,是一种动态程序分析方法 > > –维基百科 在日常开发中,性能优化往往是一个长期的事情,在业务的不同阶段,会遇到各种各样的性能问题,迅速分析并定位程序的性能瓶颈,能让我们付出最小的努力,得到最大的回报。 ## [](#1-2-代码开发上线步骤)1.2 代码开发上线步骤 1. 上线前 a. 压测:我们通过压测可以获知系统的性能,例如每秒能处理的请求数,平均响应时间,错误率等指标。这样,我们对自己服务的性能算是有个底。 b. 问题:但是压测是线下的模拟流量,如果到了线上呢?会遇到高并发、大流量,不靠谱的上下游,突发的尖峰流量等等场景,这些都是不可预知的。 2. 上线后 1. 线上突然大量报警,接口超时,错误数增加 2. 终极解决方案:`看日志`、`看监控`;`使用性能分析工具`分析程序的性能,找到瓶颈 3. 临时解决方案:首先要`降级、限流、回滚`,先止损 3. 总结 1. 性能分析很重要,尤其是要统计好性能数据,出问题了,可以随时拿出来看。 根据上面内容引出了性能分析,性能分析主要关注 `CPU、内存、磁盘 IO、网络`这些指标。 在软件工程中,性能分析(performance analysis,也称为 profiling),是以收集程序运行时信息为手段研究程序行为的分析方法,是一种动态程序分析的方法。 `profiling` 是指在程序执行过程中,收集能够反映程序执行状态的数据。 `一般常规分析内容:` 1. **cpu**:程序对cpu的使用情况 - 使用时长,占比等 2. **内存**:程序对cpu的使用情况 - 使用时长,占比,内存泄露等。如果在往里分,程序堆、栈使用情况 3. **I/O**:IO的使用情况 - 哪个程序IO占用时间比较长 `golang 程序中分析内容:` 1. **goroutine**:go的协程使用情况,调用链的情况 2. **goroutine leak**:goroutine泄露检查 3. **go dead lock**:死锁的检测分析 4. **data race detector**:数据竞争分析,其实也与死锁分析有关 ## [](#1-3-golang-性能调试优化方法)1.3 golang 性能调试优化方法 比如 linux 中 cpu 性能调试,工具有 top,dstat,perf 等。 那么在 golang 中,有哪些分析方法? **golang 性能调试优化方法:** - **Benchmark**:**基准测试**,对特定代码的运行时间和内存信息等进行测试 - **Profiling**:**程序分析**,程序的运行画像,在程序执行期间,通过采样收集的数据对程序进行分析 - **Trace**:**跟踪**,在程序执行期间,通过采集发生的事件数据对程序进行分析 > profiling 和 trace 有啥区别? > profiling 分析没有时间线,trace 分析有时间线。 在 golang 中,`应用方法的工具`呢? 这里介绍 pprof 这个 golang 工具,它可以帮助我们调试优化程序。 > 它的最原始程序是 [gperftools](https://github.com/gperftools/gperftools) - [https://github.com/gperftools/gperftools,golang](https://github.com/gperftools/gperftools,golang) 的 pprof 是从它而来的。 ## [](#2-pprof-是什么)2 pprof 是什么 在 Go 语言中,pprof 是用于可视化和分析性能分析数据的工具,pprof 以 profile.proto 读取分析样本的集合,并生成报告以可视化并帮助分析数据(支持文本和图形报告)。 而刚刚提到的 profile.proto 是一个 Protobuf v3 的描述文件,它描述了一组 callstack 和 symbolization 信息, 作用是统计分析的一组采样的调用栈,是很常见的 stacktrace 配置文件格式。 > **一句话描述**:Golang**自带**的一款开箱即用的**性能监控**和**分析**工具。 ## [](#3-pprof-作用)3 pprof 作用 go的pprof提供了2个工具供我们使用,runtime/pprof中是它的源码,net/http/pprof对源码做了简单封装,能让你在http服务中直接使用。 它可以采样程序运行时的CPU、堆内存、Goroutine、锁竞争、阻塞调用、系统线程的使用情况。然后通过可视化终端或网页的形式展示给用户,用户可以通过列表、调用图、源码、火焰图、反汇编等视图去展示采集到的性能指标 ![pprof 作用](https://cdn.jsdelivr.net/gh/debug-LiXiwen/blog_pics/img/image-20211128132832839.png) ## [](#3-1-pprof指标采样的维度)3.1 pprof指标采样的维度 1. CPU(profile) 1. CPU Profiling:CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置。 2. 报告CPU的使用情况,定位到热点(消耗CPU周期最多的)代码。默认情况下Go以100HZ的频率进行CPU采样 2. Goroutine 1. Goroutine Profiling: Goroutine 分析,可以对当前应用程序正在运行的 Goroutine 进行堆栈跟踪和分析。这项功能在实际排查中会经常用到,因为很多问题出现时的表象就是 Goroutine 暴增,而这时候我们要做的事情之一就是查看应用程序中的 Goroutine 正在做什么事情,因为什么阻塞了,然后再进行下一步。 3. 系统线程 1. 获取导致创建 OS 线程的 goroutine 堆栈 4. 堆内存/内存剖析(heap) 1. Memory Profiling:内存分析,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏。 2. 包含每个 goroutine 分配大小,分配堆栈等。 3. 每分配 runtime.MemProfileRate(默认为512K) 个字节进行一次数据采样。 5. 内存剖析(allocs):报告所有内存分配历史 6. 阻塞操作 1. Block Profiling:阻塞分析,记录 Goroutine 阻塞等待同步(包括定时器通道)的位置,默认不开启,需要调用 `runtime.SetBlockProfileRate` 进行设置。 2. 获取导致阻塞的 goroutine 堆栈(如 channel, mutex 等),使用前需要先调用 `runtime.SetBlockProfileRate` 7. 锁竞争 1. Mutex Profiling:互斥锁分析,报告互斥锁的竞争情况,默认不开启,需要调用 `runtime.SetMutexProfileFraction` 进行设置。 8. 执行追踪(trace):追踪当前应用程序的执行栈 ## [](#3-2-原理)3.2 原理 ### [](#3-2-1-CPU采样)3.2.1 CPU采样 CPU采样会记录所有的调用栈和它们的占用时间。 在采样时,进程会每秒暂停一百次,每次会记录当前的调用栈信息。 汇总之后,根据调用栈在采样中出现的次数来推断函数的运行时间。 你需要手动地启动和停止采样。每秒100次的暂停频率也不能更改。 这个定时暂停机制在unix或类unix系统上是依赖信号机制实现的。 每次「暂停」都会接收到一个信号,通过系统计时器来保证这个信号是固定频率发送的。 接下来看看具体的流程。 一共有三个相关角色:进程本身、操作系统和写缓冲。 启动采样时,进程向OS注册一个定时器,OS会每隔10ms向进程发送一个SIGPROF信号,进程接收到信号后就会对当前的调用栈进行记录。 与此同时,进程会启动一个写缓冲的goroutine,它会每隔100ms从进程中读取已经记录的堆栈信息,并写入到输出流。 当采样停止时,进程向OS取消定时器,不再接收信号,写缓冲读取不到新的堆栈时,结束输出。 采样对象:函数调用和它们的占用时间 采样率:100次/秒 固定值采样时间:从手动启动到手动结束 - 操作系统:每10ms向进程发送一次SIGPROF信号 - 进程:每次接收到SIGPROF会记录调用栈 - 写缓冲:每100ms读取一次已经记录的调用栈并写入输出流 ![image-20210801222450112](/2021/07/18/shi-zhan/image-20210801222450112.png) ![image-20210801222823463](/2021/07/18/shi-zhan/image-20210801222823463.png) ### [](#3-2-2-Goroutine-amp-ThreadCreate采样)3.2.2 Goroutine & ThreadCreate采样 接下来我们来看看goroutine和系统线程的采样。这两个采样指标在概念上和实现上都比较相似,所以在这里进行对比。 Goroutine采样会记录所有用户发起,也就是入口不是runtime开头的goroutine,以及main函数所在goroutine的信息和创建这些goroutine的调用栈; ThreadCreate采样会记录由程序创建的所有系统线程的信息和调用栈。 他们在实现上非常的相似,都是会在STW之后,遍历所有goroutine/所有线程的列表(图中的m就是GMP模型中的m,在golang中和线程一一对应),并输出堆栈,最后Start The World继续运行。 这个采样是立刻触发的全量记录,你可以通过比较两个时间点的差值来得到某一时间段的指标。 Goroutine:记录所有用户发起且在运行中的goroutine(即入口非runtime开头的)和runtime.main的调用栈信息 ThreadCreate:记录程序创建的所有系统线程的信息 ![](/2021/07/18/shi-zhan/goroutine&tc.png) ### [](#3-2-3-Heap(堆内存)采样)3.2.3 Heap(堆内存)采样 接下来看看堆内存采样。 我在提到内存指标的时候说的都是「堆内存」而不是「内存」,这是因为pprof的内存采样是有局限性的。 内存采样在实现上依赖了内存分配器的记录,所以它只能记录在堆上分配,且会参与GC的内存,一些其他的内存分配,例如调用结束就会回收的栈内存、一些更底层使用cgo调用分配的内存,是不会被内存采样记录的。 它的采样率是一个大小,默认每分配512KB内存会采样一次,采样率是可以在运行开头调整的,设为1则为每次分配都会记录。 与CPU和goroutine都不同的是,内存的采样是一个持续的过程,它会记录从程序运行起的所有分配或释放的内存大小和对象数量,并在采样时遍历这些结果进行汇总。 还记得刚才的例子中,堆内存采样的四种指标吗?alloc的两项指标是从程序运行开始的累计指标,而inuse的两项指标是通过累计分配减去累计释放得到的程序当前持有的指标。 你也可以通过比较两次alloc的差值来得到某一段时间程序分配的内存【大小和数量】 采样程序通过内存分配器在`堆上`分配和释放的内存,记录分配/释放的`大小和数量` 采样率:每分配512KB记录一次,可在运行开头修改,1为每次分配均记录 采样时间:从程序运行开始到采样时 采样指标:alloc\_space, alloc\_objects, inuse\_space, inuse\_objects 计算方式:inuse=alloc-free ### [](#3-2-4-Block-阻塞-amp-Mutex-锁竞争-采样)3.2.4 Block(阻塞) & Mutex(锁竞争)采样 讲完了堆内存,还剩下阻塞和锁竞争这两种采样。 这两个指标在流程和原理上也非常相似,我在这里也做了一个对比。 这两个采样记录的都是对应操作发生的调用栈、次数和耗时,不过这两个指标的采样率含义并不相同。 阻塞操作的采样率是一个「阈值」,消耗超过阈值时间的阻塞操作才会被记录,1为每次操作都会记录。 锁竞争的采样率是一个「比例」,运行时会通过随机数来只记录固定比例的锁操作,1为每次操作都会记录。 它们在实现上也是基本相同的。都是一个「主动上报」的过程。 在阻塞操作或锁操作发生时,会计算出消耗的时间,连同调用栈一起主动上报给采样器,采样器会根据采样率可能会丢弃一些记录。 在采样时,采样器会遍历已经记录的信息,统计出具体操作的次数、调用栈和总耗时。和堆内存一样,你可以对比两个时间点的差值计算出段时间内的操作指标。 阻塞操作 采样阻塞操作的次数和耗时 采样率:阻塞耗时超过阈值的才会被记录,1为每次阻塞均记录 锁竞争 采样争抢锁的次数和耗时 采样率:只记录固定比例的锁操作,1为每次加锁均记录 ![](/2021/07/18/shi-zhan/image-20210801224628156.png) ## [](#4-pprof-使用模式)4 pprof 使用模式 - Report generation:报告生成 - Interactive terminal use:交互式终端 - Web interface:Web 界面 ## [](#5-采样方式)5 采样方式 | 方式名称 | 如何使用 | 优点 | 缺点 | 使用场景 | | --- | --- | --- | --- | --- | | runtime/pprof | 手动调用【runtime.StartCPUProfile、runtime.StopCPUProfile】等**API**来进行数据的采集。采集程序(非 Server)的指定区块的运行数据进行分析。 | **灵活性高**、按需采集。 | | **工具型应用**(比如说定制化的分析小工具、集成到公司监控系统)。这种应用运行一段时间就结束。 | | net/http/pprof | 通过**http**服务来获取Profile采样文件。 `import _ "net/http/pprof"`。基于 HTTP Server 运行,并且可以采集运行时数据进行分析。`net/http/pprof中只是使用runtime/pprof包来进行封装了一下,并在http端口上暴露出来` | **简单易用** | | 在线服务(一直运行着的程序) | | go test | 通过命令`go test -bench . -cpuprofile cpu.prof`来进行采集数据。 | **针对性强**、细化到函数 | | 进行某函数的性能测试 | 上面的 pprof 开启后,每隔一段时间就会采集当前程序的堆栈信息,获取函数的 cpu、内存等使用情况。通过对采样的数据进行分析,形成一个数据分析报告。 pprof 以 [profile.proto](https://github.com/google/pprof/blob/master/proto/profile.proto) 的格式保存数据,然后根据这个数据可以生成可视化的分析报告,支持文本形式和图形形式报告。 profile.proto 里具体的数据格式是 [protocol buffers](https://developers.google.com/protocol-buffers)。 那用什么方法来对数据进行分析,从而形成文本或图形报告? 用一个命令行工具 `go tool pprof` 。 ## [](#5-1-go-test)5.1 go test - cpu 使用分析:-cpuprofile=cpu.pprof - 内存使用分析:-benchmem -memprofile=mem.pprof - block分析:-blockprofile=block.pprof 运行命令采样数据: ```sh go test -bench=. -run=none -benchmem -memprofile=mem.pprof go test -bench=. -run=none -blockprofile=block.pprof go test -bench=. -run=none -benchmem -memprofile=mem.pprof -cpuprofile=cpu.pprof go test -bench=. -run=none -benchmem -memprofile=mem.pprof -cpuprofile=cpu.pprof -blockprofile=block.pprof ``` ## [](#5-2-runtime-pprof)5.2 runtime/pprof 除了注入 http handler 和 go test 以外,我们还可以在程序中通过 pprof 所提供的 Lookup 方法来进行相关内容的采集和调用,其一共支持六种类型,分别是:goroutine、threadcreate、heap、block、mutex,代码如下: 除了注入 http handler 和 go test 以外,我们还可以在程序中通过 pprof 所提供的 Lookup 方法来进行相关内容的采集和调用,其一共支持六种类型,分别是:goroutine、threadcreate、heap、block、mutex,代码如下: ```go type LookupType int8 const ( LookupGoroutine LookupType = iota LookupThreadcreate LookupHeap LookupAllocs LookupBlock LookupMutex ) func pprofLookup(lookupType LookupType, w io.Writer) error { var err error switch lookupType { case LookupGoroutine: p := pprof.Lookup("goroutine") err = p.WriteTo(w, 2) case LookupThreadcreate: p := pprof.Lookup("threadcreate") err = p.WriteTo(w, 2) case LookupHeap: p := pprof.Lookup("heap") err = p.WriteTo(w, 2) case LookupAllocs: p := pprof.Lookup("allocs") err = p.WriteTo(w, 2) case LookupBlock: p := pprof.Lookup("block") err = p.WriteTo(w, 2) case LookupMutex: p := pprof.Lookup("mutex") err = p.WriteTo(w, 2) } return err } ``` 接下来我们只需要对该方法进行调用就好了,其提供了 `io.Writer` 接口,也就是只要实现了对应的 Write 方法,我们可以将其写到任何支持地方去,如下: ```go ... func init() { runtime.SetMutexProfileFraction(1) runtime.SetBlockProfileRate(1) } func main() { http.HandleFunc("/lookup/heap", func(w http.ResponseWriter, r *http.Request) { _ = pprofLookup(LookupHeap, os.Stdout) }) http.HandleFunc("/lookup/threadcreate", func(w http.ResponseWriter, r *http.Request) { _ = pprofLookup(LookupThreadcreate, os.Stdout) }) http.HandleFunc("/lookup/block", func(w http.ResponseWriter, r *http.Request) { _ = pprofLookup(LookupBlock, os.Stdout) }) http.HandleFunc("/lookup/goroutine", func(w http.ResponseWriter, r *http.Request) { _ = pprofLookup(LookupGoroutine, os.Stdout) }) _ = http.ListenAndServe("0.0.0.0:6060", nil) } ``` 在上述代码中,我们将采集结果写入到了控制台上,我们可以进行一下验证,调用 `http://127.0.0.1:6060/lookup/heap`,控制台输出结果如下: ```shell $ go run main.go heap profile: 0: 0 [0: 0] @ heap/1048576 # runtime.MemStats # Alloc = 180632 # TotalAlloc = 180632 # Sys = 69928960 # Lookups = 0 ... ``` 本章节所有测试都是使用的runtime\_method.go文件,子章节只会展示我的测试代码,统一用一个入口main函数。 mian函数如下 ```go package main import ( "fmt" "log" "os" "runtime" "runtime/pprof" "sync" "time" ) func main() { runtime.GOMAXPROCS(1) CollectCpu() } ``` 运行函数方法:执行命令,runtime\_method.go是我的文件名 ```shell go run runtime_method.go ``` 结果分析方式也是一样的,如下。 ``` go tool pprof cpu.prof 执行命令行两件套 top list 函数名 生成web页面查看 web ``` 或者通过暴露http端口的方式,可以查看更多类型的图。 ```shell go tool pprof -http=:6060 cpu.prof ``` ![profile已经存在的文件](/2021/07/18/shi-zhan/image-20210801183047601.png) ### [](#5-2-1-采集CPU-CPU-占用过高)5.2.1 采集CPU(CPU 占用过高) ```go func CollectCpu() { file, err := os.Create("./cpu.prof") if err != nil { fmt.Printf("创建采集文件失败, err:%v\n", err) return } pprof.StartCPUProfile(file) defer pprof.StopCPUProfile() for i := 0; i < 4; i++ { go do1() } time.Sleep(10 * time.Second) } ``` ### [](#5-2-2-采集内存-heap-内存占用过高)5.2.2 采集内存(heap-内存占用过高) `作用:看哪些方法分配了最多的内存` ```go const ( Ki = 1024 Mi = Ki * Ki Gi = Ki * Mi Ti = Ki * Gi Pi = Ki * Ti ) type Mouse struct { buffer [][Mi]byte } func (m *Mouse)StealMem() { max := Gi for len(m.buffer) * Mi < max { m.buffer = append(m.buffer, [Mi]byte{}) } } func CollectHeap() { runtime.MemProfileRate = 512 * 1024 f, err := os.Create("./heap.prof") if err != nil { log.Fatal("could not create heap profile: ", err) } defer f.Close() m := &Mouse{} m.StealMem() if err := pprof.WriteHeapProfile(f); err != nil { log.Fatal("could not write heap profile: ", err) } } ``` ### [](#5-2-3-采集内存-allocs-频繁内存回收)5.2.3 采集内存(allocs-频繁内存回收) 获取程序运行依赖,所有内存分配的历史 ```go func ApplyMemDoNothing() { _ = make([]byte, 16 * Mi) } func CollectAllocs() { runtime.MemProfileRate = 512 * 1024 f, err := os.Create("./allocs.prof") if err != nil { log.Fatal("could not create memory profile: ", err) } defer f.Close() for i := 0; i < 100; i++ { time.Sleep(3 * time.Second) ApplyMemDoNothing() } if err := pprof.Lookup("allocs").WriteTo(f, 0); err != nil { log.Fatal("could not write allocs profile: ", err) } } ``` 为了获取程序运行过程中 GC 日志,我们需要先退出炸弹程序,再在重新启动前赋予一个环境变量,同时为了避免其他日志的干扰,使用 grep 筛选出 GC 日志查看: 由于内存的申请与释放频度是需要一段时间来统计的,所有我们保证炸弹程序已经运行了几分钟之后,再结束: ```bash go build runtime_method.go GODEBUG=gctrace=1 ./runtime_method | grep gc ``` ![allocs执行结果](/2021/07/18/shi-zhan/image-20210801181024454.png) 可以看到,GC 差不多每 3 秒就发生一次,且每次 GC 都会从 16MB 清理到几乎 0MB,说明程序在不断的申请内存再释放,这是高性能 golang 程序所不允许的。 如果你希望进一步了解 golang 的 GC 日志可以查看[《如何监控 golang 程序的垃圾回收》](https://blog.wolfogre.com/redirect/v3/A9DNc05mRFLA-ZPsjfPhLuZDu-oKbuLF_wQyMDE2xf8CMDfF_wIwMcUtHy8qzDsGiVTMOxzFMRIDAzwK_jrFxVoWBjtuQQYW3Dsh_cU8Bk0KxTsGzDw8Bcw8ghxKiMU),为保证实验节奏,这里不做展开。 所以接下来使用 pprof 排查时,我们在乎的不是什么地方在占用大量内存,而是什么地方在不停地申请内存,这两者是有区别的。 ![image-20210801182209809](/2021/07/18/shi-zhan/image-20210801182209809.png) ### [](#5-2-4-采集goroutine-协程泄露)5.2.4 采集goroutine(协程泄露) ```go func CollectGoroutine() { f, err := os.Create("./goroutine.prof") if err != nil { log.Fatal("could not create goroutine profile: ", err) } defer f.Close() for i := 0; i < 1000; i++ { go func() { time.Sleep(10 * time.Second) }() } if err = pprof.Lookup("goroutine").WriteTo(f, 0); err != nil { fmt.Fprintf(os.Stderr, "Can not write %s: %s", "./goroutine.prof", err) } } func main() { runtime.GOMAXPROCS(1) go func() { if err := http.ListenAndServe(":6060", nil); err != nil { log.Fatal(err) } os.Exit(0) }() for { CollectGoroutine() time.Sleep(3 * time.Second) } } ``` ![goroutine执行结果](/2021/07/18/shi-zhan/image-20210801190007741.png) 在调用栈上来讲,其展示顺序是自下而上的,也就是 `runtime.main` 方法调用了 `main.main` 方法,`main.main` 方法又调用了 `net/http.ListenAndServe` 方法,这里对应的也就是我们所使用的示例代码了,排查起来会非常方便。 每个调用堆栈信息用 `-----------` 分割,函数方法前的就是指标数据,像 Goroutine Profiling 展示是就是该方法占用的 goroutine 的数量。而 Heap Profiling 展示的就是占用的内存大小,如下: ![traces使用](/2021/07/18/shi-zhan/image-20210801190856360.png) ### [](#5-2-5-采集mutex-锁的争用)5.2.5 采集mutex(锁的争用) ```go func CollectMutex() { runtime.SetMutexProfileFraction(1) f, err := os.Create("./mutex.prof") if err != nil { log.Fatal("could not create memory profile: ", err) } defer f.Close() m := &sync.Mutex{} m.Lock() go func() { time.Sleep(3 * time.Second) m.Unlock() }() m.Lock() time.Sleep(10 * time.Second) if err = pprof.Lookup("mutex").WriteTo(f, 0); err != nil { fmt.Fprintf(os.Stderr, "Can not write %s: %s", "./mutex.prof", err) } } ``` ### [](#5-2-6-采集block-阻塞操作)5.2.6 采集block(阻塞操作) ```go func CollectBlock() { runtime.SetBlockProfileRate(1) f, err := os.Create("./block.prof") if err != nil { log.Fatal("could not create block profile: ", err) } defer f.Close() <- time.After(time.Second) if err = pprof.Lookup("block").WriteTo(f, 0); err != nil { fmt.Fprintf(os.Stderr, "Can not write %s: %s", "./block.prof", err) } } ``` ## [](#5-3-net-http-pprof)5.3 net/http/pprof 上代码 ```go package main import ( "fmt" "net/http" _ "net/http/pprof" ) func do() { var c chan int for { select { case v := <-c: fmt.Printf("我是有问题的那一行,因为收不到值:%v", v) default: } } } func main() { for i := 0; i < 4; i++ { go do() } http.ListenAndServe("127.0.0.1:9090", nil) } ``` 通过代码中的关键两步,执行起来就可以通过 [http://127.0.0.1:9090/debug/pprof/看到对应的数据啦~](http://127.0.0.1:9090/debug/pprof/看到对应的数据啦~) ![http方式的结果](/2021/07/18/shi-zhan/image-20210726102116085.png) ## [](#6-采样的数据内容)6 采样的数据内容 不管是前文哪种方式获取,都可以进行分析。这里http的方式把可以看到的信息全部都列出来了。 | 类型 | 描述 | url | | --- | --- | --- | | allocs | **内**存分配情况的采样信息,过去所有内存抽样情况 | $host/debug/pprof/allocs?debug=1 | | blocks | **阻塞**操作情况的采样信息,同步阻塞时程序栈跟踪的一些情况 | $host/debug/pprof/block?debug=1 | | cmdline | 显示程序启动**命令参数**及其参数 | | | goroutine | 显示当前所有**协程**的堆栈信息 | | | heap | **堆**上的内存分配情况的采样信息,活动对象的内存分配情况 | $host/debug/pprof/heap?debug=1 | | mutex | **锁**竞争情况的采样信息,互斥锁持有者的栈帧情况 | $host/debug/pprof/mutex?debug=1 | | profile | **cpu**占用情况的采样信息,点击会下载文件,cpu profile,点击时会得到一个文件,然后可以用 go tool pprof 命令进行分析 | $host/debug/pprof/profile | | threadcreate | 系统**线程**创建情况的采样信息,创建新 OS 线程的堆栈跟踪情况 | $host/debug/pprof/threadcreate?debug=1 | | trace | 程序**运行跟踪**信息,当前程序执行的追踪情况,点击时会得到一个文件,可以用 go tool trace 命令来分析这个文件 | $host/debug/pprof/trace | 通过加粗的关键字,很直观可以看到能分析到的数据。 (后文将重点根据cpu的采样信息展开命令行和图形化页面的讨论,其余项将在实战中应用) ## [](#7-数据分析方式)7 数据分析方式 ## [](#7-1-命令行)7.1 命令行 核心命令:`go tool pprof <binary> <source>` binary:是应用的二进制文件,用来解析各种符号; source:代表生成的分析数据来源,可以是**本地文件**(前文生成的cpu.prof),也可以是**http地址**(比如:`go tool pprof http://127.0.0.1:9090/debug/pprof/profile`) > 需要注意的是:较大负载的情况下(要不就故意写故障代码,或者就模拟访问压力)获取的有效数据更有意义,如果处于空闲状态,得到的结果可能意义不大 例子如下 ```bash go build -o havlak1 havlak1.go go tool pprof havlak1 havlak1.prof ``` 通过工具go tool pprof打开URL或文件后,会显示一个 (pprof)提示符,你可以使用以下命令: | 命令 | 参数 | 说明 | | --- | --- | --- | | gv | \[focus\] | 将当前概要文件以图形化和层次化的形式显示出来。当没有任何参数时,在概要文件中的所有采样都会被显示如果指定了focus参数,则只显示调用栈中有名称与此参数相匹配的函数或方法的采样。 focus参数应该是一个正则表达式需要dot、gv命令,执行下面的命令安装:Shell12sudo apt-**get** install graphvizsudo apt-**get** install gv | | **web** | \[focus\] | 生成 svg 热点图片,与gv命令类似,web命令也会用图形化的方式来显示概要文件。但不同的是,web命令是在一个Web浏览器中显示它 | | **list** | \[routine\_regexp\] | 列出名称与参数 routine\_regexp代表的正则表达式相匹配的函数或方法的相关源代码 | | weblist | \[routine\_regexp\] | 在Web浏览器中显示与list命令的输出相同的内容。它与list命令相比的优势是,在我们点击某行源码时还可以显示相应的汇编代码 | | **top\[N\]** | \[–cum\] | top命令可以以本地采样计数为顺序列出函数或方法及相关信息如果存在标记 –cum则以累积采样计数为顺序默认情况下top命令会列出前10项内容。但是如果在top命令后面紧跟一个数字,那么其列出的项数就会与这个数字相同。默认显示 flat 前10的函数调用,可使用 -cum 以 cum 排序 | | traces | | 打印所有采集的样本 | | disasm | \[routine\_regexp\] | 显示名称与参数 routine\_regexp相匹配的函数或方法的反汇编代码。并且,在显示的内容中还会标注有相应的采样计数 | | callgrind | \[filename\] | 利用callgrind工具生成统计文件。在这个文件中,说明了程序中函数的调用情况。如果未指定 filename参数,则直接调用kcachegrind工具。kcachegrind可以以可视化的方式查看callgrind工具生成的统计文件 | | help | | 显示帮助 | | quit | | 退出 | 开始分析前文生成的cpu.prof: ``` go tool pprof cpu.prof ``` ![go tool pprof cpu.prof](/2021/07/18/shi-zhan/image-20210726103631287.png) | 类型 | 描述 | 举例 | | --- | --- | --- | | flat | 该函数占用CPU的耗时,不包含当前函数调用其它函数并等待返回的时间 | selectnbrecv占用CPU的耗时是12.29s | | flat% | 该函数占用CPU的耗时的百分比 | selectnbrecv耗时:12.29s,cpu总耗时:29.14,12.29/29.14=42.18 | | sum% | 前面所有flat总和,top命令中排在它上面的函数以及本函数flat%之和 | chanrecv:42.18%+30.47% = 72.65% | | cum | 当前函数加上该函数调用其它函数的累计CPU耗时,该指标包含子函数耗时 | chanrecv:8.88+0.54=9.42 | | cum% | 当前函数加上该函数调用其它函数的累计CPU耗时的百分比 | 9.42/29.14=32.33% | | 最后一列 | 当前函数名称 | \- | 什么时候Flat==cum?Flat是当前函数耗时,Cum是当前函数和调用函数累计耗时,他俩相等,代表当前函数没有调用别的函数。 什么时候Flat==0?函数中只有其它函数的调用。 `上面的命令解析` 发现do函数有点问题。此时通过命令:**list funcName**,来进行查看具体的位置 ![web](/2021/07/18/shi-zhan/image-20210726104115967.png) > 1.在前文的对话框中继续输入:**web**。即可生成pprof001.svg的页面。 > 2.执行命令:`go tool pprof -pdf cpu.prof`,会生成profile001.pdf的pdf文件。(参数可选text、pdf、svg),不管哪种形式,都会得到以下图片: ![web生成的图片](/2021/07/18/shi-zhan/image-20210726104221828.png) ## [](#7-2-可视化界面方式)7.2 可视化界面方式 执行命令:`go tool pprof -http=:6060 cpu.prof` ![http方式](/2021/07/18/shi-zhan/image-20210726105437969.png) ![汇总](/2021/07/18/shi-zhan/image-20210726105651670.png) ![top](/2021/07/18/shi-zhan/image-20210726110036404.png) ![](/2021/07/18/shi-zhan/image-20210726105527673.png) - Flame Graph(火焰图) 这里的颜色是随机分布的,只是看起来像火焰。 调用顺序**由上到下**,每一块代表一个函数,越大代表占用 CPU 的时间更长。同时它也可以支持点击块深入进行分析。 ![Flame Graph](/2021/07/18/shi-zhan/image-20210726105743023.png) ![peek](/2021/07/18/shi-zhan/image-20210726105837331.png) - Source(同前文gdb交互页面的list FuncName命令) ![source](/2021/07/18/shi-zhan/image-20210726105919086.png) ![image-20210726105956501](/2021/07/18/shi-zhan/image-20210726105956501.png) ## [](#8-数据分析实战)8 数据分析实战 ## [](#8-1-排查套路)8.1 排查套路 ### [](#8-1-1-套路1)8.1.1 套路1 这里先说一下排查套路。 > 第一步:进入排除对应的gdb交互。 ``` go tool pprof http://localhost:6060/debug/pprof/{填上你想查看的内容}?seconds=10 ``` 内容关键字: | 类型 | 描述 | | --- | --- | | allocs | **内**存分配情况的采样信息 | | blocks | **阻塞**操作情况的采样信息 | | cmdline | 显示程序启动**命令参数**及其参数 | | goroutine | 显示当前所有**协程**的堆栈信息 | | heap | **堆**上的内存分配情况的采样信息 | | mutex | **锁**竞争情况的采样信息 | | profile | **cpu**占用情况的采样信息,点击会下载文件 | | threadcreate | 系统**线程**创建情况的采样信息 | | trace | 程序**运行跟踪**信息 | > 第二步:三联招,top->list FuncName->web 通过占用比分析,查看具体代码行数,看大图确认。 > 第三步:解决问题。 (细心的同学可能会发现没有对trace进行分析,这个请期待《一看就懂系列之Golang的trace》) ### [](#8-1-2-套路2-推荐)8.1.2 套路2-推荐 将其重新运行起来,然后在其它窗口执行下述命令: ```shell wget http://127.0.0.1:6060/debug/pprof/profile ``` 默认需要等待 30 秒,执行完毕后可在当前目录下发现采集的文件 profile,针对可视化界面我们有两种方式可进行下一步分析: 该命令将在所指定的端口号运行一个 PProf 的分析用的站点。 ```shell go tool pprof -http=:6001 profile ``` ## [](#8-2-背景说明)8.2 背景说明 本章节使用WOLFOGRE的文章进行实践。 ```bash git clone github.com/wolfogre/go-pprof-practice cd $GOPATH/src/github.com/wolfogre/go-pprof-practice go mod init go mod tidy go mod download go build ./go-pprof-practice ``` 运行后注意查看一下资源是否吃紧,机器是否还能扛得住,坚持一分钟,如果确认没问题,咱们再进行下一步。 [ http://localhost:6060/debug/pprof/](http://localhost:6060/debug/pprof/) ![image-20210801203418974](file:///Users/bytedance/MyBlog/source/_posts/%E5%AE%9E%E6%88%98/image-20210801203418974.png?lastModify=1627827194) ## [](#8-3-排查-CPU-占用过高)8.3 排查 CPU 占用过高 我们首先通过活动监视器(或任务管理器、top 命令,取决于你的操作系统和你的喜好),查看一下炸弹程序的 CPU 占用: ![image-20210801204540416](/2021/07/18/shi-zhan/image-20210801204540416.png) ```shell wget http://localhost:6060/debug/pprof/profile go tool pprof -http=:8080 profile ``` ![](/2021/07/18/shi-zhan/image-20210801213119054.png) ![image-20210801211815133](/2021/07/18/shi-zhan/image-20210801211815133.png) 很明显,CPU 占用过高是 `github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat` 造成的。 可以看到,是第 24 行那个一百亿次空循环占用了大量 CPU 时间,至此,问题定位成功! ![image-20210801213511656](/2021/07/18/shi-zhan/image-20210801213511656.png) 接下来有一个扩展操作:图形化显示调用栈信息,这很酷,但是需要你事先在机器上安装 `graphviz`,大多数系统上可以轻松安装它: ```shell brew install graphviz # for macos apt install graphviz # for ubuntu yum install graphviz # for centos ``` 至此,这一小节使用 pprof 定位 CPU 占用的实验就结束了,你需要输入 `exit` 退出 pprof 的交互式终端。 为了方便进行后面的实验,我们修复一下这个问题,不用太麻烦,注释掉相关代码即可: ```go func (t *Tiger) Eat() { log.Println(t.Name(), "eat") } ``` 之后修复问题的的方法都是注释掉相关的代码,不再赘述。你可能觉得这很粗暴,但要知道,这个实验的重点是如何使用 pprof 定位问题,我们不需要花太多时间在改代码上。 ## [](#8-4-排查内存占用过高)8.4 排查内存占用过高 重新编译炸弹程序,再次运行,可以看到 CPU 占用率已经下来了,但是内存的占用率仍然很高: ![image-20210801213859734](/2021/07/18/shi-zhan/image-20210801213859734.png) ![image-20210801214029301](/2021/07/18/shi-zhan/image-20210801214029301.png) 可以看到这次出问题的地方在 `github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Steal` 注释代码就可以了 | 选项名 | 作用 | | --- | --- | | alloc\_objects | 分析应用程序的内存临时分配情况 | | alloc\_space | 查看每个函数分配的内存空间大小 | | inuse\_space | 分析应用程序的常驻内存占用情况 | | inuse\_objects | 查看每个函数所分配的对象数量 | 执行该命令后,能够很快的拉取到其结果,因为它不需要像 CPU Profiling 做采样等待,这里需要注意的一点是 `Type` 这一个选项,你可以看到它默认显示的是 `inuse_space`,实际上可以针对多种内存概况进行分析,常用的类别如下: - inuse\_space:分析应用程序的常驻内存占用情况。 ```shell ⋊> ~/testpprof go tool pprof -inuse_space http://localhost:6060/debug/pprof/heap Fetching profile over HTTP from http://localhost:6060/debug/pprof/heap Saved profile in /Users/xxx/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.003.pb.gz Type: inuse_space Time: Aug 1, 2021 at 9:41pm (CST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) (pprof) (pprof) top Showing nodes accounting for 1GB, 100% of 1GB total flat flat% sum% cum cum% 1GB 100% 100% 1GB 100% github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Steal 0 0% 100% 1GB 100% github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Live 0 0% 100% 1GB 100% main.main 0 0% 100% 1GB 100% runtime.main (pprof) ``` - alloc\_objects:分析应用程序的内存临时分配情况。 ```shell ⋊> ~/testpprof go tool pprof -alloc_objects http://localhost:6060/debug/pprof/heap Fetching profile over HTTP from http://localhost:6060/debug/pprof/heap Saved profile in /Users/xxx/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.004.pb.gz Type: alloc_objects Time: Aug 1, 2021 at 9:43pm (CST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) (pprof) top Showing nodes accounting for 51, 100% of 51 total flat flat% sum% cum cum% 41 80.39% 80.39% 41 80.39% github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Run (inline) 10 19.61% 100% 10 19.61% github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Steal 0 0% 100% 41 80.39% github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Live 0 0% 100% 10 19.61% github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Live 0 0% 100% 51 100% main.main 0 0% 100% 51 100% runtime.main (pprof) ``` 另外还有 inuse\_objects 和 alloc\_space 类别,分别对应查看每个函数所分别的对象数量和查看分配的内存空间大小,具体可根据情况选用。 ## [](#8-5-排查频繁内存回收)8.5 排查频繁内存回收 你应该知道,频繁的 GC 对 golang 程序性能的影响也是非常严重的。虽然现在这个炸弹程序内存使用量并不高,但这会不会是频繁 GC 之后的假象呢? 为了获取程序运行过程中 GC 日志,我们需要先退出炸弹程序,再在重新启动前赋予一个环境变量,同时为了避免其他日志的干扰,使用 grep 筛选出 GC 日志查看: ```bash GODEBUG=gctrace=1 ./go-pprof-practice | grep gc ``` 日志输出如下: ![](/2021/07/18/shi-zhan/4a500684e36566cc74cef1e69b4898c99dfb008f3293f3892ad64990e2c62283.jpg) 可以看到,GC 差不多每 3 秒就发生一次,且每次 GC 都会从 16MB 清理到几乎 0MB,说明程序在不断的申请内存再释放,这是高性能 golang 程序所不允许的。 如果你希望进一步了解 golang 的 GC 日志可以查看[《如何监控 golang 程序的垃圾回收》](https://blog.wolfogre.com/redirect/v3/A9DNc05mRFLA-ZPsjfPhLuZDu-oKbuLF_wQyMDE2xf8CMDfF_wIwMcUtHy8qzDsGiVTMOxzFMRIDAzwK_jrFxVoWBjtuQQYW3Dsh_cU8Bk0KxTsGzDw8Bcw8ghxKiMU),为保证实验节奏,这里不做展开。 所以接下来使用 pprof 排查时,我们在乎的不是什么地方在占用大量内存,而是什么地方在不停地申请内存,这两者是有区别的。 由于内存的申请与释放频度是需要一段时间来统计的,所有我们保证炸弹程序已经运行了几分钟之后,再运行命令: ```bash go tool pprof http://localhost:6060/debug/pprof/allocs ``` 同样使用 top、list、web 大法: ![image](/2021/07/18/shi-zhan/7c0bc1cc6839e904865d5c20e363a2876aa1d99076bfdce81e439fbbde4cc42f.png) ![image](/2021/07/18/shi-zhan/087bc92dffda268e29817ccdfd2f61c94a0a1b7e74298a00c8b568b07db1a3b3.png) 可以看到 `github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Run` 会进行无意义的内存申请,而这个函数又会被频繁调用,这才导致程序不停地进行 GC: ```go func (d *Dog) Run() { log.Println(d.Name(), "run") _ = make([]byte, 16 * constant.Mi) } ``` 这里有个小插曲,你可尝试一下将 `16 * constant.Mi` 修改成一个较小的值,重新编译运行,会发现并不会引起频繁 GC,原因是在 golang 里,对象是使用堆内存还是栈内存,由编译器进行逃逸分析并决定,如果对象不会逃逸,便可在使用栈内存,但总有意外,就是对象的尺寸过大时,便不得不使用堆内存。所以这里设置申请 16 MiB 的内存就是为了避免编译器直接在栈上分配,如果那样得话就不会涉及到 GC 了。 我们同样注释掉问题代码,重新编译执行,可以看到这一次,程序的 GC 频度要低很多,以至于短时间内都看不到 GC 日志了: ![image](/2021/07/18/shi-zhan/32e5e14a2d2000367e988242fb463d8ae809d44fac7093c5a9b06e39ecb81370.jpg) ## [](#8-6-排查协程泄露)8.6 排查协程泄露 由于 golang 自带内存回收,所以一般不会发生内存泄露。但凡事都有例外,在 golang 中,协程本身是可能泄露的,或者叫协程失控,进而导致内存泄露。 我们在浏览器里可以看到,此时程序的协程数已经多达 106 条: ![image](/2021/07/18/shi-zhan/a8e2b63a63274980eecbac63f848356039f719559e4664e8937f271403aa5d75.jpg) 虽然 106 条并不算多,但对于这样一个小程序来说,似乎还是不正常的。为求安心,我们再次是用 pprof 来排查一下: ```bash go tool pprof http://localhost:6060/debug/pprof/goroutine ``` 同样是 top、list、web 大法: ![image](/2021/07/18/shi-zhan/75512a296f062e71f29251273f8496ea5deedcb9f104b99342f8dccb9bb1277e.png) ![image](/2021/07/18/shi-zhan/6bbba6ec436951c6857ba231dcb14cefce7133d6833ece711230264e2bd9ce9c.png) 可能这次问题藏得比较隐晦,但仔细观察还是不难发现,问题在于 `github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Drink` 在不停地创建没有实际作用的协程: ```go func (w *Wolf) Drink() { log.Println(w.Name(), "drink") for i := 0; i < 10; i++ { go func() { time.Sleep(30 * time.Second) }() } } ``` 可以看到,Drink 函数每次回释放 10 个协程出去,每个协程会睡眠 30 秒再退出,而 Drink 函数又会被反复调用,这才导致大量协程泄露,试想一下,如果释放出的协程会永久阻塞,那么泄露的协程数便会持续增加,内存的占用也会持续增加,那迟早是会被操作系统杀死的。 我们注释掉问题代码,重新编译运行可以看到,协程数已经降到 4 条了: ![image](/2021/07/18/shi-zhan/cd7e95c42e61bcd16d65ea7892adb09b668b62b9f2c47966982a6e779a3a8917.jpg) ## [](#8-7-排查锁的争用)8.7 排查锁的争用 到目前为止,我们已经解决这个炸弹程序的所有资源占用问题,但是事情还没有完,我们需要进一步排查那些会导致程序运行慢的性能问题,这些问题可能并不会导致资源占用,但会让程序效率低下,这同样是高性能程序所忌讳的。 我们首先想到的就是程序中是否有不合理的锁的争用,我们倒一倒,回头看看上一张图,虽然协程数已经降到 4 条,但还显示有一个 mutex 存在争用问题。 相信到这里,你已经触类旁通了,无需多言,开整。 ```bash go tool pprof http://localhost:6060/debug/pprof/mutex ``` 同样是 top、list、web 大法: ![image](/2021/07/18/shi-zhan/7484245a189adcd85181806fe1e380e6c043f0188d0d7949448819b3f55ce20f.jpg) ![image](/2021/07/18/shi-zhan/4413eeb48e86038a581ee76b53476099c026a1e1be5a8dc7f4b8f88a46711d3d.png) 可以看出来这问题出在 `github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Howl`。但要知道,在代码中使用锁是无可非议的,并不是所有的锁都会被标记有问题,我们看看这个有问题的锁那儿触雷了。 ```go func (w *Wolf) Howl() { log.Println(w.Name(), "howl") m := &sync.Mutex{} m.Lock() go func() { time.Sleep(time.Second) m.Unlock() }() m.Lock() } ``` 可以看到,这个锁由主协程 Lock,并启动子协程去 Unlock,主协程会阻塞在第二次 Lock 这儿等待子协程完成任务,但由于子协程足足睡眠了一秒,导致主协程等待这个锁释放足足等了一秒钟。虽然这可能是实际的业务需要,逻辑上说得通,并不一定真的是性能瓶颈,但既然它出现在我写的“炸弹”里,就肯定不是什么“业务需要”啦。 所以,我们注释掉这段问题代码,重新编译执行,继续。 ## [](#8-8-排查阻塞操作)8.8 排查阻塞操作 好了,我们开始排查最后一个问题。 在程序中,除了锁的争用会导致阻塞之外,很多逻辑都会导致阻塞。 ![image](/2021/07/18/shi-zhan/d830dc8a7323e7106d05857e97ceed34541212f47cb30071fe4a67ff8ab15aeb.jpg) 可以看到,这里仍有 2 个阻塞操作,虽然不一定是有问题的,但我们保证程序性能,我们还是要老老实实排查确认一下才对。 ```bash go tool pprof http://localhost:6060/debug/pprof/block ``` top、list、web,你懂得。 ![image](/2021/07/18/shi-zhan/1f2e7e8ecc6f6ee7e0f6f1890a8b3d0293ba2b52be3eb1ac1df15d2d169029f7.jpg) ![image](/2021/07/18/shi-zhan/12ac6115ebdee8b2e1217e3d47442298d714f2e3bd61b2132911ba694dabc615.png) 可以看到,阻塞操作位于 `github.com/wolfogre/go-pprof-practice/animal/felidae/cat.(*Cat).Pee`: ```go func (c *Cat) Pee() { log.Println(c.Name(), "pee") <-time.After(time.Second) } ``` 你应该可以看懂,不同于睡眠一秒,这里是从一个 channel 里读数据时,发生了阻塞,直到这个 channel 在一秒后才有数据读出,这就导致程序阻塞了一秒而非睡眠了一秒。 这里有个疑点,就是上文中是可以看到有两个阻塞操作的,但这里只排查出了一个,我没有找到其准确原因,但怀疑另一个阻塞操作是程序监听端口提供 porof 查询时,涉及到 IO 操作发生了阻塞,即阻塞在对 HTTP 端口的监听上,但我没有进一步考证。 不管怎样,恭喜你完整地完成了这个实验。 ## [](#9-代码优化建议)9 代码优化建议 以下是一些从其它项目借鉴或者自己总结的实践经验,它们只是建议,而不是准则,实际项目中应该以性能分析数据来作为优化的参考,避免过早优化。 1. 对频繁分配的小对象,使用 [sync.Pool](https://golang.org/pkg/sync/#Pool) 对象池避免分配 2. 自动化的 DeepCopy 是非常耗时的,其中涉及到反射,内存分配,容器(如 map)扩展等,大概比手动拷贝慢一个数量级 3. 用 atomic.Load/StoreXXX,atomic.Value, sync.Map 等代替 Mutex。(优先级递减) 4. 使用高效的第三方库,如用[fasthttp](https://github.com/valyala/fasthttp)替代 net/http 5. 在开发环境加上`-race`编译选项进行竞态检查 6. 在开发环境开启 net/http/pprof,方便实时 pprof 7. 将所有外部IO(网络IO,磁盘IO)做成异步 ## [](#参考)参考 [一看就懂系列之Golang的pprof](https://juejin.cn/post/6844904079525675016#heading-14) [煎鱼-Go 大杀器之性能剖析 PProf(上)](https://golang2.eddycjy.com/posts/ch6/01-pprof-1/#611-pprof-%E6%98%AF%E4%BB%80%E4%B9%88) [Go应用性能剖析](https://blog.gmem.cc/go-program-profiling) [\[WOLFOGRE’S BLOG\]golang pprof 实战](https://blog.wolfogre.com/posts/go-ppof-practice/#%E5%AE%9E%E9%AA%8C%E5%87%86%E5%A4%87) [码农桃花源-深度解密Go语言之pprof](https://qcrao.com/2019/11/10/dive-into-go-pprof/#runtime-pprof)
yg9538
2025年1月17日 14:24
30
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档
PDF文档(打印)
分享
链接
类型
密码
更新密码