Repository navigation
Discussion 2764
- 编号:#2764
- 分类:General
- 状态:打开 / 无已采纳答案
- 创建:2024-12-18T07:23:58Z
- 更新:2025-03-16T12:59:07Z
- 作者:No-SilverBullet
社区原始记录;其中的建议、未决提案和版本描述需结合 版本与升级 核对。快照日期:2026-09-20。
Based on the following issues
https://github.com/apache/dubbo-go/issues/2031 https://github.com/apache/dubbo-go/issues/2339 https://github.com/apache/dubbo-go/issues/2701
we can see that common.URL has a memory leak problem. However, none of these issues mentioned the minimum steps to reproduce. Therefore, I tried to reproduce the cause of the memory leak in a generic-call scenario (the use case where I found the memory leak myself). The simulation scenario here is to make generic-call to different providers(the same provider org.apache.dubbo.samples.UserProvider is used in the client code for convenience.) multiple times (similar to the gateway scenario, generic-call to multiple providers as a proxy). The following are the steps to reproduce and how to use the [goref](https://github.com/cloudwego/goref) tool for memory leak analysis.
Make sure your go version is >= go1.21
go install github.com/cloudwego/goref/cmd/grf@latestIn dubbo-go-samples root directory, all related test is based on dubbo-go-samples;
make -f build/Makefile docker-upfor example
cd ./compatibility/generic/default/go-server/cmd
export DUBBO_GO_CONFIG_PATH="../conf/dubbogo.yml"
go run .based on [go ref test samples](https://github.com/cloudwego/goref/blob/main/testdata/mockleak/main.go)
for example
cd ./compatibility/generic/default/go-client/cmdimport proof
import _ "net/http/pprof"start to listen 6060 port, add these lines in the begining of client.go/main()
go func() {
log.Println(http.ListenAndServe("127.0.0.1:6060", nil))
}()modify the code to simulate multiple generic-calls, i simulated a four-minute generic-calls with the following code
func main() {
go func() {
log.Println(http.ListenAndServe("127.0.0.1:6060", nil))
}()
// register POJOs
hessian.RegisterPOJO(&pkg.User{})
anchorTime := time.Now()
for {
if time.Since(anchorTime) > 4*time.Minute {
break
}
time.Sleep(1 * time.Second)
dubboRefConf := newRefConf("org.apache.dubbo.samples.UserProvider", dubbo.DUBBO)
callGetUser(dubboRefConf)
}
//wait gc, 1 minute is enough
time.Sleep(1 * time.Minute)
select {}
}Add script gif_test.sh
#!/bin/bash
# set variables
GO_FILE="client.go"
OUTPUT_BINARY="client_binary"
LOG_FILE="app.log"
PID_FILE="app.pid"
CONFIG_FILE="../conf/dubbogo.yml"
export PATH=$PATH:$(go env GOPATH)/bin
export DUBBO_GO_CONFIG_PATH=$CONFIG_FILE
# build go file
echo "> Compiling $GO_FILE..."
go build -o $OUTPUT_BINARY $GO_FILE
if [ $? -ne 0 ]; then
echo "Compilation failed!"
exit 1
fi
echo "> Compilation successful: $OUTPUT_BINARY"
# save PID and log file
nohup ./$OUTPUT_BINARY >$LOG_FILE 2>&1 &
echo $! >$PID_FILE
PID=$(cat $PID_FILE)
echo "> Application started with PID: $PID"
#wait client run and gc
sleep 300
#grf attach to target process
if [ -n "$PID" ]; then
echo "> Attaching grf to process $PID..."
grf attach $PID
kill -9 $PID
echo "> Killed process with PID $PID."
else
echo "Failed to retrieve PID!"
exit 1
fi
run this script and wait for terminal output (maybe a few minutes)
successfully output to `grf.out`install graphviz, and then
go tool pprof -http=:5079 ./grf.out
click this to get detailed heap object reference information

We can find that it is mainly baseinvoker that holds these url objects, which eventually leads to memory leaks. Since dubbo-go-samples is using the dubbo.apache.org/dubbo-go/v3 v3.2.0-rc1 version, and the related PR to fix this momery leak issue is https://github.com/apache/dubbo-go/pull/2726 which has not been released yet. To verify whether this PR fixes the memory leak problem, i replaced the file to the local file(which keep consistent with the dubbo-go/develop branch);
replace dubbo.apache.org/dubbo-go/v3 v3.2.0-rc1.0.20240808044912-f61d7c94bfd2 => /path/to/dubbo-go
and retest the above scenario, the final result is
we can see that there is no memory leak of common.URL;
Welcome any discussion
永久链接 · 2024-12-18T15:15:13Z
Another good tool is pyroscope
Pyroscope 是一个开源的性能分析工具,用于帮助开发者收集、查看和分析应用程序的性能数据(如 CPU 使用率、内存消耗等)。它通过采样的方式,收集程序的性能数据并提供可视化界面,以便开发者查找性能瓶颈,优化代码。
Pyroscope 支持多种编程语言,包括 Go、Java、Python 等,通常用于生产环境中的应用程序性能监控。
首先,需要在本地或服务器上安装 Pyroscope。它有多种部署方式,包括 Docker、Kubernetes、以及本地安装。
docker run -d -p 4040:4040 pyroscope/pyroscope:latest这将会启动一个容器,并在本地的 4040 端口上暴露 Pyroscope 的 Web 界面。
brew install pyroscope-io/pyroscope/pyroscope如果你更喜欢从源代码安装,可以按照 Pyroscope GitHub 仓库上的 安装指南 进行操作。
安装 Pyroscope 后,你需要将其集成到你的应用程序中来收集性能数据。
Pyroscope 有一个 Web 界面,可以用来查看和分析性能数据。启动 Pyroscope 后,访问 http://localhost:4040 来查看性能分析数据。
Pyroscope 提供了多个客户端库,可以帮助你将性能数据发送到 Pyroscope 服务。
import (
"github.com/pyroscope-io/pyroscope"
)
func main() {
// 设置 Pyroscope 客户端
pyroscope.Start(pyroscope.Config{
ApplicationName: "your-app",
ServerAddress: "http://localhost:4040", // Pyroscope 服务器地址
})
// Your application logic here
}import pyroscope
# Set up Pyroscope client
pyroscope.configure(
application_name="your-app",
server_address="http://localhost:4040", # Pyroscope server address
)
# Your application logic here通过这种方式,Pyroscope 客户端将会定期发送性能数据到 Pyroscope 服务器。
访问 Pyroscope 的 Web 界面(默认地址是 http://localhost:4040),你可以看到以下几种信息:
- Flame Graph:展示每个函数调用的性能数据(CPU 使用情况),帮助你识别热点函数。
- Profile Over Time:展示不同时间段内应用的性能波动,帮助你分析负载的变化。
- Top Functions:显示最消耗资源的函数和方法,帮助你定位瓶颈。
你可以选择不同的时间窗口、查看不同的应用等,来详细分析你的应用程序性能。
- 实时性能监控:Pyroscope 可以实时收集和展示应用程序的性能数据,帮助开发者在生产环境中追踪性能瓶颈。
- 可视化分析:通过 Flame Graph 等可视化工具,帮助开发者快速定位性能问题。
- 低开销:与一些传统的性能分析工具不同,Pyroscope 具有较低的性能开销,适合在生产环境中使用。
- 开源:Pyroscope 是一个开源工具,免费供开发者使用,也支持自定义功能。
- 长期存储:你可以将 Pyroscope 与长期存储系统(如 Prometheus、Elasticsearch 等)集成,方便长期追踪和历史数据分析。
- 自定义指标:除了默认的性能指标外,你还可以自定义 Pyroscope 收集的指标,结合自己应用的业务场景进行性能监控。
- 集成第三方监控工具:Pyroscope 也可以与其他监控工具(如 Grafana、Prometheus 等)进行集成,从而在同一个监控平台上查看更多的数据。
Pyroscope 是一个非常实用的性能分析工具,特别适合开发者在生产环境中进行应用性能监控。通过简单的安装和配置,你可以在你的应用中集成 Pyroscope,收集实时的性能数据,并通过 Pyroscope 的 Web 界面进行可视化分析,帮助你快速识别并解决性能瓶颈。
永久链接 · 2024-12-18T15:20:00Z
在 Go 程序中,内存泄漏是一个常见且严重的问题,它会导致程序内存消耗不断增加,最终可能引起系统崩溃或严重性能下降。使用 Pyroscope 进行内存泄漏检测可以帮助你实时监控程序的内存使用情况,发现潜在的内存泄漏问题。
下面是如何使用 Pyroscope 来检测 Go 程序中的内存泄漏:
首先,确保 Pyroscope 已经安装并启动。在本地测试时,可以通过 Docker 启动 Pyroscope 服务:
docker run -d -p 4040:4040 pyroscope/pyroscope:latest这将会在 localhost:4040 上启动 Pyroscope Web 界面,供你查看和分析数据。
要在 Go 程序中集成 Pyroscope 进行内存分析,你需要使用 Pyroscope Go 客户端。按照以下步骤操作:
通过 go get 命令安装 Pyroscope 客户端:
go get github.com/pyroscope-io/pyroscope在你的 Go 程序中引入 Pyroscope 库,并进行配置,启用内存采样。
package main
import (
"github.com/pyroscope-io/pyroscope"
"log"
"time"
)
func main() {
// 配置 Pyroscope 客户端,指定应用名称和 Pyroscope 服务器地址
err := pyroscope.Start(pyroscope.Config{
ApplicationName: "your-go-app",
ServerAddress: "http://localhost:4040", // Pyroscope 服务器地址
})
if err != nil {
log.Fatalf("Error starting pyroscope: %v", err)
}
// 模拟内存使用,可能引发内存泄漏
for {
leakMemory()
time.Sleep(5 * time.Second)
}
}
// 模拟内存泄漏的函数
func leakMemory() {
// 创建一个大的数组或对象,模拟内存泄漏
_ = make([]byte, 10*1024*1024) // 分配 10MB 内存
}-
pyroscope.Start()启动 Pyroscope 客户端,并指定应用名称和服务器地址。 -
leakMemory()函数每隔一段时间分配新的内存,这样就能模拟内存泄漏的情况。
在程序运行时,Pyroscope 会定期采样内存使用情况。你可以通过访问 Pyroscope 的 Web 界面(默认地址为 http://localhost:4040)来查看内存使用情况。
在 Pyroscope Web 界面中,你会看到以下内容:
- Flame Graph(火焰图):展示 CPU 和内存的调用栈。通过火焰图,你可以直观地看到哪些函数在消耗最多的内存。
- Heap Profile(堆分析):展示程序中内存分配的情况。如果你看到内存分配量持续增加而没有释放,可能表明存在内存泄漏。
- Time Series(时间序列):展示内存使用的变化趋势。通过时间轴,你可以观察内存使用情况是否在不断增长,从而发现潜在的内存泄漏问题。
- 在 Pyroscope Web 界面中,你可以选择查看
Heap(堆内存)相关的指标,观察内存的增长趋势。如果发现内存使用量持续增加,且没有明显的下降趋势,可能表明存在内存泄漏。 - 内存分配的热点:通过分析 Flame Graph,你可以确定内存泄漏是否与特定的函数或操作相关。
除了使用 Pyroscope 进行基础的内存分析,你还可以利用 Pyroscope 来收集更多的内存相关数据:
Pyroscope 允许你分析堆、栈和内存分配等信息。你可以在配置文件中启用内存采样:
err := pyroscope.Start(pyroscope.Config{
ApplicationName: "your-go-app",
ServerAddress: "http://localhost:4040", // Pyroscope 服务器地址
ProfileTypes: []pyroscope.ProfileType{
pyroscope.TypeCPU,
pyroscope.TypeAllocations,
},
})在这个配置中,pyroscope.TypeAllocations 会启用内存分配的采样,这对于检测内存泄漏非常有用。
-
调优采样频率:如果你需要更精细的内存分析,可以调整 Pyroscope 的采样频率。采样频率过低可能导致漏掉一些重要的内存分配,而过高则可能对程序性能产生影响。
你可以通过以下方式设置采样间隔(单位:秒):
err := pyroscope.Start(pyroscope.Config{ ApplicationName: "your-go-app", ServerAddress: "http://localhost:4040", ProfileInterval: 1 * time.Second, // 设置每秒采样一次 })
-
结合其它监控工具:如果 Pyroscope 检测到内存泄漏,可以结合其他监控工具(如 Prometheus、Grafana)进一步深入分析,做出优化。
- Pyroscope 通过实时内存采样,可以帮助你发现 Go 程序中的内存泄漏问题,尤其适合在生产环境中使用。
- Heap Profile 和 Flame Graph 是分析内存泄漏的关键工具,能够帮助你识别内存使用的热点和增长趋势。
- 结合 Pyroscope 的内存分析和其它监控工具,你可以更高效地解决内存泄漏问题,优化应用程序的内存管理。
永久链接 · 2024-12-19T07:07:18Z
use pyroscope(https://grafana.com/docs/pyroscope/latest/get-started/), client code is almost same with the goref test
func main() {
// register POJOs
serverAddress := os.Getenv("PYROSCOPE_SERVER_ADDRESS")
if serverAddress == "" {
serverAddress = "http://localhost:4040"
}
_, err := pyroscope.Start(pyroscope.Config{
ApplicationName: "generic-call-cache.test.app.20minutes",
ServerAddress: serverAddress,
Logger: pyroscope.StandardLogger,
ProfileTypes: []pyroscope.ProfileType{
pyroscope.ProfileAllocObjects,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileInuseSpace,
},
})
if err != nil {
log.Fatalf("error starting pyroscope profiler: %v", err)
}
hessian.RegisterPOJO(&pkg.User{})
anchorTime := time.Now()
dubboRefConf := newRefConf("org.apache.dubbo.samples.UserProvider", dubbo.DUBBO)
for {
if time.Since(anchorTime) > 4*time.Minute {
break
}
time.Sleep(1 * time.Second)
callGetUser(dubboRefConf)
}
select {}
}final result is

永久链接 · 2024-12-18T15:22:38Z
github.com/cloudwego/goref 是一个用于 Go 项目的函数调用图生成工具,它可以帮助开发者分析和可视化 Go 项目中的函数调用关系。它通过静态分析 Go 源代码,自动生成函数调用图,帮助开发者更好地理解代码结构和调用流程。
你可以通过 go install 命令安装 goref 工具:
go install github.com/cloudwego/goref/cmd/goref@latest这将会安装 goref 命令行工具,并将其放在 GOPATH/bin 目录下。
在安装完成后,你就可以使用 goref 来生成你的 Go 项目的函数调用图了。
你可以在 Go 项目的根目录中运行以下命令来生成函数调用图:
goref -dir ./path/to/your/project这里的 -dir 参数是指定你的 Go 项目的目录。你也可以使用其他参数来调整输出和过滤规则。
-
-dir:指定要分析的 Go 项目目录。 -
-output:指定输出的文件路径,默认是callgraph.svg。 -
-format:指定输出的格式,支持svg和dot格式。 -
-pkg:指定要分析的特定包(如果你只想分析某个包而不是整个项目)。 -
-exclude:排除某些文件或目录不进行分析。
-
生成默认的 SVG 格式的函数调用图:
如果你想生成项目的函数调用图并输出为
callgraph.svg:goref -dir ./path/to/your/project
-
生成
dot格式的调用图:如果你希望输出 Graphviz 的
dot格式,可以使用以下命令:goref -dir ./path/to/your/project -format dot -output callgraph.dot
-
指定输出路径和文件名:
你可以自定义输出路径和文件名:
goref -dir ./path/to/your/project -output /path/to/output/callgraph.svg
goref 默认生成的函数调用图是一个 SVG 格式的图像文件。你可以在浏览器中查看该文件,或使用支持 SVG 的图片查看工具来查看。
如果你选择输出 dot 格式的调用图,可以将其导入到 Graphviz 工具中生成图形:
dot -Tsvg callgraph.dot -o callgraph.svg这会将 dot 格式的调用图转换为 svg 格式,便于查看和分析。
生成的函数调用图会列出项目中函数之间的调用关系。你可以通过图形查看哪些函数之间有调用关系,以及这些关系是如何层次化的。这对于以下几种情况非常有用:
- 理解代码结构:特别是当项目中有复杂的函数调用时,调用图可以帮助你更清楚地理解代码的结构。
- 优化代码:通过查看哪些函数调用最频繁或依赖最多,你可以发现潜在的性能瓶颈或复杂的依赖关系。
- 重构代码:如果你想重构代码,函数调用图可以帮助你清晰地识别出需要重构的模块或函数。
除了最基本的生成函数调用图的操作,goref 还支持一些更高级的用法。例如,你可以指定只分析特定的包或排除不需要的代码。
如果你只关心项目中的某个特定包,可以通过 -pkg 参数指定包路径:
goref -dir ./path/to/your/project -pkg mypackage如果你希望排除某些不需要的文件或目录,可以使用 -exclude 参数:
goref -dir ./path/to/your/project -exclude ./path/to/exclude_dir这会让 goref 排除指定的目录或文件,从而减少分析的范围。
goref 是一个非常有用的工具,它通过静态分析 Go 代码生成函数调用图,帮助开发者理解代码中的函数调用关系。以下是 goref 的一些常用功能:
- 自动生成函数调用图。
- 支持输出多种格式(SVG、DOT)。
- 可以自定义输出路径、过滤特定目录或包。
通过使用 goref,你可以更容易地理解 Go 项目的代码结构,发现潜在的问题,并进行有效的性能优化和代码重构。
永久链接 · 2024-12-19T07:12:51Z
My question is, is this simulation scenario reasonable? are there any other scenarios that can reproduce the problem?
永久链接 · 2024-12-20T11:31:07Z
如果在执行泛化调用场景下复现时出现以下如图所示的错误(原因是不支持无参数的泛化调用)

参考这个pr https://github.com/apache/dubbo-go-samples/pull/790
永久链接 · 2025-03-16T12:59:07Z
@luoyingtao