为什么选择 Go ?

为什么选择 Go ?

起因

事情的起因其实很简单,我写了一个访客统计服务。真的非常简单的那种,只有两个接口:

  • 一个 POST,用户进入页面时打点
  • 一个 GET,返回统计数据(PV、UV、地区、设备等)

没什么高并发、没什么复杂业务,就是一个“小透明服务”。这个服务最初是用 Node 写的,Express + SQLite,一切都很熟,开发速度也很快。但我把服务用 pm2 跑起来之后,看了一眼服务器监控:内存接近 200MB。

访客统计node版占用内存

说实话,那一刻我有点懵。这坑定不行呀,我的服务器才2G的内存,这一个小小的服务就占用十分之一。不是说 Node 不该用这么多,而是这个体量的服务,真的不该用这么多。按经验,一个普通的 Node API 服务,50MB 左右是比较正常的数字。于是我开始怀疑是不是我代码写得太离谱了。

我把整个服务从头到尾过了一遍:

  • 没有大对象常驻内存
  • SQLite 查询都很简单
  • IP 缓存是一个 Map,也没无限增长
  • 没有 websocket、没有定时大任务
  • axios 请求有超时
  • UA 解析、Geo 解析也都是常规用法

说白了:这就是一个很“干净”的 Node 服务。

但现实就是:

Node + V8 + 一堆依赖,本身就是一个重量级运行时

哪怕你业务再简单,它的“起步成本”就在那里。

那我还能选什么?

我当时会、且能比较熟练使用的后端语言:

  • Node.js
  • Python
  • Java

但这三个放在“小服务 + 小服务器 + 内存敏感”这个前提下,说实话都不太合适:

  • Python:开发快,但性能和内存基本不在考虑范围
  • Java:性能不错,但 JVM 本身就是内存大户
  • Node:开发体验好,但 V8 常驻内存对我不友好

于是我把目光投向了两个“更底层一点”的选择:Go 和 Rust

Rust?我真的试过,但……不行

先说 Rust。我对 Rust 的评价其实一直很高:

  • 性能顶级
  • 内存占用极低
  • 接近 C++,但更安全

但问题在于:我入门好几次,到头来还是在门外徘徊。不是那种“看不懂”,而是那种:

每写一段代码,都要跟编译器进行一次心理博弈。

对一个需要尽快把服务跑起来的场景来说,Rust 的心智成本太高了。这不是语言不好,是现在的我不适合它。

Go:我需要的,刚好它都有

再看 Go。我越看越觉得:它几乎是为我这个场景量身定做的。

1.开发效率:真的很高

Go 的关键词少得离谱,语法也非常直白:

  • 没有复杂泛型(至少日常用不到)
  • 没有继承地狱
  • 错误处理虽然啰嗦,但非常清晰(虽然说我还是不喜欢,习惯了 try...catch之后,写 if err != nil 太别扭了)

几乎可以用“写脚本”的心态,在写一个性能级别的服务。

2.内存占用:这是关键

这一点是我下定决心的核心原因。

Go 编译后是一个静态二进制文件

  • 没有 JVM
  • 没有 V8
  • 没有庞大的运行时依赖

实际跑起来,一个类似的 HTTP 服务,十几 MB 内存就能活得很好。对我这种小服务器来说,简直是救命稻草。

3.性能:完全够用,甚至富余

Go 的性能可能比不过 Rust、C++,但对一个访客统计服务来说:Go 的性能,已经是严重过剩的级别了。

IO、多协程、并发请求,Go 天生就擅长这些。

4.一些额外但很香的点

  • goroutine:写并发几乎不用思考
  • 跨平台编译:Linux / Windows / Mac 一条命令
  • 部署简单:一个文件,scp 上去就完事

理论成立,那就开始实干

1.安装 Go 环境

  • 下载 Go SDK:https://go.dev/dl/

    选择 Windows 版本(msi 安装包,如 go1.22.x.windows-amd64.msi)。

  • 安装 Go SDK

​ 双击下载的 msi 安装包,看不懂就一路点击 Next,如果能看懂就可以修改想要修改的配置,建议默认安装路径(C:\Program Files\Go\),避免中文/空格路径。

  • 安装完成后,Go 会自动配置环境变量:

    • GOROOT:Go 的安装目录(如 C:\Program Files\Go\);
    • PATH:添加 %GOROOT%\bin(可直接在终端执行 go 命令)。
  • 验证安装

    按下 Win + R 输入 cmd 打开命令提示符,执行以下命令,输出版本号即安装成功:

    1
    2
    go version
    # 示例输出:go version go1.25.5 windows/amd64
  • 配置 Go 环境

    Go 的核心环境变量需要手动配置(推荐用 go env 命令设置,永久生效):

    1
    2
    3
    4
    5
    # 1. 设置模块代理(解决国内下载依赖慢的问题,必配)
    go env -w GOPROXY=https://goproxy.cn,direct

    # 2. 设置 Go 模块启用(Go 1.16+ 默认开启,可选确认)
    go env -w GO111MODULE=on
  • 把 Go 的全局缓存挪到 D 盘(C 盘真的顶不住)

    1. 先在 D 盘新建文件夹,比如: D:\GoCache\mod(路径自定义,建议简单易记,不要有中文/空格)

    2. 把 C 盘默认缓存目录的文件复制到新目录,避免重新下载所有依赖:进入原有缓存路径:C:\Users\你的用户名\go\pkg\mod(比如 C:\Users\张三\go\pkg\mod),全选所有文件,复制后粘贴到 D:\GoCache\mod 目录下。

    3. 修改环境变量(永久生效)

      • 进入环境变量

      • 在弹出的系统属性窗口中,点击环境变量

      • 新建:变量名:GOMODCACHE 变量值:D:\GoCache\mod

      • 点击「确定」保存,关闭所有窗口

    4. 验证是否生效

      1
      2
      go env GOMODCACHE
      # 输出 D:\GoCache\mod(你设置的路径)

2.重写服务

把整个 Node 服务,用 Go 完整重写了一遍,这里就不一一展示了,就是一个简单的服务。

大致包括:

  • SQLite 存储
  • IP 缓存
  • 地理位置解析
  • UV / PV 统计逻辑

访客服务重构之前node代码

访客服务重构后go代码

最后的结果

结果其实很简单:

  • 内存占用显著下降(内存从200m 下降到 20m,可以说是非常优秀了)

    go重写之后的内存占用

  • 服务稳定性更好

  • 非常舒服的部署体验(不依赖环境,打包的时候对应好就行)

  • 上线正常运行

    访客统计正常运行

而且最重要的是,我并没有牺牲多少开发效率。我的云服务器2核2G,内存非常宝贵,小服务的内存占用我必须控制在50m以内,不是说 Go 是最好的,但它是现在最适合我的。


2026.01.03 更新

元旦最后一天,在家闲着没啥事,心想我的图床后端服务是不是也能够使用 Go 重写一下,理论存在,实践开始。

我的图床服务之前是使用 Node 写的,没错,又是它,我一般小服务和 Demo 项目我都会使用 Node 先搭建一个可行性的版本验证。

这个简单的服务框架大致如下:

  1. 整体架构:
    • 使用Express框架搭建Web服务器。
    • 使用MinIO作为对象存储服务,用于存储上传的图片。
    • 使用SQLite数据库记录上传的图片信息(文件名、URL、大小、修改时间)。
    • 使用JWT进行身份验证,保护上传、列出和删除图片的接口。
  2. 主要功能模块:
    • 身份验证:通过登录接口获取JWT令牌,后续请求需要在Header中携带该令牌。
    • 图片上传:将图片上传到MinIO,并将信息存入SQLite。
    • 图片列表:分页获取图片列表。
    • 图片删除:从MinIO和SQLite中删除指定的图片。
    • 同步功能:启动时同步MinIO中的文件到SQLite数据库。

图床服务node代码

内存占用情况还行,是一个中规中矩的 Node 服务,大概 60m 左右。

图床服务node内存占用情况

然后就是使用 Go 来进行重写

图床服务go代码

重构之后,再来看一下内存的占用情况,发现内存占用下降到了 20m 左右,和访客统计服务的内存占用差不多,这个内存占用我还是非常满意的。

图床服务go内存占用情况