Java老将转战Go,速度与效率双赢!

banq


我的 Java 之旅从 2011 年开始(哇,已经是 14 年前了!),那时候我在维也纳技术大学学计算机科学。刚开始写 Java 程序时,我用的是 jEdit 这种编辑器,还得通过命令行手动编译代码。我的第一个“大”项目是一个用 Java Swing 做的图形界面程序,还有一个用 JSP 和 Servlet 做的“老派”网页应用。

到了 2016 年,我开始正式写 Java 代码,当时我加入了一家公司,专门开发基于 Java 的网页应用。我们用了一堆经典技术:Java、Spring Boot、Hibernate 和 PostgreSQL 数据库。我特别喜欢 Spring 的各种功能,比如依赖注入、Spring Security 和 Spring Data JPA。虽然那个应用启动要花半分钟,而且一启动就吃掉几百兆内存,但谁在乎呢?内存又不贵,对吧?

接下来的几年里,我一直选 Java(后来也用了 Kotlin)来做新项目,因为我对它很熟悉,而且它的生态系统很大。我有点担心如果用 Golang 或者 Rust 这些新语言,开发速度会变慢。我们甚至用 Kotlin 写了 Kubernetes 集群的基础设施代码,用来自动配置应用。虽然现在这些代码还在用,但我有点后悔当初没用 Golang 来写。

现在回头看,这个决定好像挺明显的,但当时因为已经投入了很多时间和经验,所以没那么容易下决心。我有个经验法则,就是要么在不熟悉的领域做项目,要么用不熟悉的技术栈做项目。因为这是我们第一次做 Kubernetes 项目,所以不想在市场和产品风险之外再加个技术风险。

不过,我们很快就发现为什么现在很多工具都是用 Golang 写的。其他操作符几乎不怎么占内存和 CPU,而我们的操作符(加上一些工具)和整个技术栈,即使啥都不干,也会吃掉超过 2GB 的内存。

到了 2024 年,我们决定把第一个用 Kotlin 写的“包操作符”重写成一个更通用、更可扩展的包管理器。因为我们对 Kubernetes API 和操作符模式已经很熟悉了,所以这次选了Go 作为编程语言

虽然一开始有点担心学习曲线,但后来发现其实没那么难,开发速度也越来越快,几乎和写 Kotlin 一样快。

我们公司一直在开发新工具,2024 年底我们推出了 Distr——一个开源的软件分发平台,帮助软件供应商把产品部署到客户控制的环境中。因为我们对 Go 已经很熟悉了,而且有软件分发的经验,所以决定用 Go 来写 Distr,这是我们第一个用 Go 写的网页应用。

刚开始启动 Web 服务器的时候,我等了几秒钟,以为它还没启动,结果发现它早就准备好了,只是没打印新日志。这让我有点惊讶。

编译和启动
Java 的 JIT(即时编译)和 Go 的 AOT(提前编译)是两种完全不同的方式,所以很难直接比较。Java 支持增量构建和热代码重载,这让开发时的启动时间变短了。但代价是要用 Gradle 或 Maven,它们本身就很占内存,而且开发体验也不太好。

Go 则是把整个应用编译成一个二进制文件,每次改代码都得重新编译。

启动时间
启动一个真实的 Spring Boot 应用,输出大概是这样的:
...Started CoreApplicationKt in 8.201 seconds (process running for 8.726)

而 Go 的 Web 服务器不到 100 毫秒就能准备好接受连接。

作为一个开发者,如果我每小时只重启服务器两次,那用 Go 每年能省下一天的开发时间。而且,当所有副本都缩到零,需要快速重启时,Go 的优势就更明显了。

谁不喜欢速度快呢?反正我喜欢。

框架
在 Java 生态里,很多框架都是“全家桶”,从 Web 服务器到数据库层都包了。

Go 就不一样了,它有很多小库,比如 Web 服务器、路由器、数据库之类的,这些库不一定是一套的,但它们能很好地配合使用。你可以自己选喜欢的库,而不是像 Spring 或 Quarkus 那样用一套现成的解决方案。不过,这也意味着项目之间的差异会比 Java EE 或 Spring Boot 应用更大。

依赖注入
在 Spring 框架里,我习惯用 @Service 注解来标记服务,然后在其他地方轻松使用它们。Angular 也是这么干的。但 Go 不一样。

虽然 Go 的标准库里有单例服务,比如 http.DefaultClient 和 base64.StdEncoding,但依赖注入在 Go 里并不像在 Java 里那么常见,因为 Go 的运行时反射功能有限。

不过 Go 生态里也有解决方案,比如用 Context 来传递数据。依赖注入的“黑魔法”少了,这也是 Go 受欢迎的原因之一。

调试和 IDE
一开始,我对 Go 的调试能力有点怀疑,但后来发现调试 Go 应用、设置断点和评估表达式其实和调试 JVM 应用一样方便。

所以,IDE 的支持其实差不多。

不过,在异常和堆栈跟踪方面,我觉得 Java 生态更好一些。IntelliJ 对 Java 的支持更胜一筹,因为它能隐藏框架的跟踪信息,并且能直接跳转到源代码。我在 IntelliJ 里还没看到 Go 有这样的功能。

总的来说,调试和 IDE 支持对开发者体验很重要,两者差不多,但 Java 的堆栈跟踪和源代码结合得更紧密。

CI 和发布
发布总是和依赖管理、应用打包有关。在 Java 世界里,有 Gradle 和 Maven,Maven 是老牌工具,Gradle 是后来者。虽然 Java 生态已经很老了,但基本上没有新的工具出现。

在 Go 生态里,做同样事情的库不多。通常有一个不错的依赖管理方案,而 GoReleaser 是个很棒的工具。

总结
回想起来,从 Java/Kotlin 切换到 Go 一开始感觉是个大决定,但现在看来,这是个明智的选择。学习曲线没想象中那么陡峭,而且好处很快就显现出来了——启动时间快、资源消耗少、生态系统更轻量。

当然,Java 还是有它的优势,对某些项目来说,它仍然是个不错的选择。但对于云原生应用、Kubernetes 工具和我们自托管的软件分发平台来说,Go 感觉就是最合适的工具。

我还会再写 Java 吗?也许吧。但现在,我很享受 Go 的速度、简单和灵活。

说实话,我并不怀念 Java 和 Spring 背后的那些“黑魔法”。