写点什么

Golang 反模式避坑实践

作者:俞凡
  • 2025-06-23
    上海
  • 本文字数:2122 字

    阅读完需:约 7 分钟

本文介绍了 Golang 编程过程中非常容易出现的 5 种反模式,这些反模式很容易造成系统性能下降,如果能仔细分析代码并避免这些反模式,就能更好的获得 Golang 的性能优势。原文:How I Made My Golang Services 3x Faster by Avoiding Common Anti-Patterns



Golang 以其性能和效率而闻名,但是在编写 Go 代码时如果忽视了最佳实践,就有可能会写出缓慢、低效的服务。按照我的经验,避免一些常见的反模式可以显著提高服务性能,获得近 3 倍的性能提升空间。本文将介绍这些能够产生重大影响的优化实践。

1. 避免在 Goroutine 中阻塞操作

Golang 中最常见的错误之一是无意中阻塞了程序,缓慢的数据库调用、未缓冲的通道或资源锁定都可能使整个系统停滞。


优化前:数据库调用阻塞


func getUserData(userID int) User {    var user User    db.QueryRow("SELECT * FROM users WHERE id = ?", userID).Scan(&user.ID, &user.Name)    return user}
复制代码


每个 API 请求都将等待数据库调用,从而降低负载。


优化后:使用带有上下文超时的异步查询


func getUserData(ctx context.Context, userID int) (User, error) {    ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)    defer cancel()        var user User    err := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", userID).Scan(&user.ID, &user.Name)    if err != nil {        return User{}, err    }    return user, nil}
复制代码


结果:负载延迟减少了 50%,慢查询会超时而不会阻塞执行。

2. 过度使用没有工作池的 goroutine

一个常见误解是,更多的 goroutine 总是意味着更好的性能,但实际上不受控制的生成 goroutine 可能导致内存使用过高以及资源竞争。


优化前:不受控制的 goroutine


func processRequests(requests []Request) {    for _, req := range requests {        go handleRequest(req)    }}
复制代码


如果 requests 包含数千个元素,则该代码可能生成数千个 goroutine,从而使系统过载。


优化后:使用工作池


func processRequests(requests []Request) {    workerPool := make(chan struct{}, 10) // Limit concurrency to 10 workers        for _, req := range requests {        workerPool <- struct{}{} // Block if pool is full        go func(r Request) {            defer func() { <-workerPool }()            handleRequest(r)        }(req)    }}
复制代码


结果:减少内存占用和峰值 CPU,提高请求处理效率。

3. 避免过度使用反射

反射(reflect 包)功能强大,但需要付出高昂的性能代价,基于反射的序列化会显著降低系统速度。


优化前:在 JSON 序列化中使用反射


json.NewEncoder(w).Encode(response)
复制代码


优化后:使用 jsoniter 更快的处理 JSON


import jsoniter "github.com/json-iterator/go"jsoniter.ConfigFastest.Marshal(response)
复制代码


结果:序列化时间减少了 40%。

4. 低效的字符串连接

在循环内使用 += 进行字符串连接会产生不必要的内存分配并降低性能。


优化前:在循环中使用 +=


var result stringfor _, item := range items {    result += item + ", "}
复制代码


优化后:使用 strings.Builder


var builder strings.Builderfor _, item := range items {    builder.WriteString(item + ", ")}result := builder.String()
复制代码


结果:内存分配减少,循环效率提高 30%。

5. 不为外部 API 使用连接池

默认情况下,每个 HTTP 请求都会创建一个新连接,从而导致资源的过度使用。


优化前:每个请求都有新的 HTTP 客户端


func fetchData(url string) ([]byte, error) {    client := &http.Client{}    resp, err := client.Get(url)    defer resp.Body.Close()    return ioutil.ReadAll(resp.Body)}
复制代码


优化后:使用共享 HTTP 客户端


var httpClient = &http.Client{Timeout: 5 * time.Second}
func fetchData(url string) ([]byte, error) { resp, err := httpClient.Get(url) defer resp.Body.Close() return ioutil.ReadAll(resp.Body)}
复制代码


结果:减少内存使用并改进了 API 响应时间。

最终结果和关键要点

通过避免这些常见的反模式,能够使 Golang 服务提升 3 倍性能:


  • 带超时的异步数据库调用将负载延迟减少了 50%

  • 工作池代替不受控制的 goroutine 提高了效率

  • 替换基于反射的 JSON 序列化将处理时间减少了 40%

  • **使用 strings.Builder 替换 += 减少了内存分配

  • HTTP 请求连接池减少了外部 API 响应时间


经验教训


  1. 并发必须得到控制 —— 太多的 goroutine 会影响性能。

  2. 优化 I/O 操作 —— 数据库查询和 API 调用通常是瓶颈。

  3. 在优化之前进行测量和分析 —— 在修改代码之前始终明确问题在哪里。


如果你正在优化 Golang 服务,请尝试实现这些技术!🚀




你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

发布于: 刚刚阅读数: 4
用户头像

俞凡

关注

公众号:DeepNoMind 2017-10-18 加入

俞凡,Mavenir Systems研发总监,关注高可用架构、高性能服务、5G、人工智能、区块链、DevOps、Agile等。公众号:DeepNoMind

评论

发布
暂无评论
Golang 反模式避坑实践_golang_俞凡_InfoQ写作社区