去年我曾经用Go语言开发过一个网站。尽管这个网站可能永远不会面世,但我在此过程中收获颇丰,现在想与大家分享我的一些心得与笔记。
一、Go 1.22:路由支持的新飞跃我一直对Go语言的路由库(如gorilla/mux、chi等)持观望态度,更倾向于手动实现路由。例如:
// DELETE /records 请求处理 case r.Method == "DELETE" && n == 1 && p[0] == "records": if !requireLogin(username, r.URL.Path, r, w) { return } deleteAllRecords(ctx, username, rs, w, r) // POST /records/<ID> 请求处理 case r.Method == "POST" && n == 2 && p[0] == "records" && len(p[1]) > 0: if !requireLogin(username, r.URL.Path, r, w) { return } updateRecord(ctx, username, p[1], rs, w, r)然而,从Go 1.22版本开始,标准库对路由的支持有了显著提升,代码得以简化为:
mux.HandleFunc("DELETE /records/", app.deleteAllRecords) mux.HandleFunc("POST /records/{record_id}", app.updateRecord)当然,我们还需要一个登录中间件,可以像这样使用requireLogin:
mux.Handle("DELETE /records/", requireLogin(http.HandlerFunc(app.deleteAllRecords)))二、内置路由的陷阱:尾随斜杠的重定向在使用内置路由时,我遇到了一个棘手的问题:为/records/设置路由后,对/records的请求会被重定向到/records/。这导致POST请求被错误地转换为GET请求,因为请求体在重定向过程中被丢弃。幸运的是,Xe Iaso的一篇博客文章为我提供了解决这一问题的线索。
我认为,避免这个问题的最佳方法是使用POST /records这样的API端点,而不是POST /records/,这看起来也更符合常规设计。
三、sqlc:自动生成数据库查询代码的神器编写大量的SQL查询样板代码让我感到厌倦,但我又不想使用ORM,因为我喜欢直接控制SQL查询。这时,我发现了sqlc这个工具。它可以将SQL查询:
-- name: GetVariant :one SELECT * FROM variants WHERE id = ?;编译成Go代码:
const getVariant = `-- name: GetVariant :one SELECT id, created_at, updated_at, disabled, product_name, variant_name FROM variants WHERE id = ? ` func (q *Queries) GetVariant(ctx context.Context, id int64) (Variant, error) { row := q.db.QueryRowContext(ctx, getVariant, id) var i Variant err := row.Scan( &i.ID, &i.CreatedAt, &i.UpdatedAt, &i.Disabled, &i.ProductName, &i.VariantName, ) return i, err }我喜欢sqlc的原因是,它可以根据我编写的SQL查询自动生成Go代码,让我无需在ORM文档中苦苦寻找如何构造所需的SQL查询。
四、SQLite技巧分享在Mastodon上,我读到了一篇名为《为服务器优化SQLite》的文章。虽然我的项目规模较小,对性能要求不高,但这篇文章仍然给了我很多启发:
我创建了一个专门用于写入数据库的对象,并设置了db.SetMaxOpenConns(1)。我发现,如果不这样做,当两个线程同时尝试写入数据库时,我会遇到SQLITE_BUSY错误。为了提高读取速度,我考虑使用两个独立的数据库对象,一个用于写入,一个用于读取。文章中提到的其他技巧也很有用(如优化COUNT查询、使用STRICT表等),但我还没有尝试过。
另外,如果我有两个表且永远不会进行JOIN操作,我会将它们放在单独的数据库中,以便独立连接。
五、Go 1.19:设置GC内存限制的新方法我在内存有限的虚拟机(如256MB或512MB)上运行Go项目时,遇到了应用程序因内存不足而被杀死的问题。经过调查,我发现这可能是由于垃圾回收器配置不当导致的。
在Go 1.19中,我们可以设置GC内存限制来避免这种情况。我将GC内存限制设置为250MB后,应用程序因内存不足而被杀死的情况大大减少:
export GOMEMLIMIT=250MiB 六、我为何偏爱用Go语言制作网站我之所以对Go语言情有独钟,尤其在网站开发领域,主要得益于以下几个方面的优势:
部署高效便捷:Go语言的部署过程极为简便,只需一个静态二进制文件即可完成。更令人惊喜的是,通过embed功能,静态资源也能轻松嵌入到二进制文件中,进一步简化了部署流程。内置强大的Web服务器:Go语言内置了功能完善的Web服务器,直接适用于生产环境,无需额外配置WSGI或其他中间件,从而大大提高了开发效率和灵活性。工具链安装简便:Go语言的工具链安装过程同样简便快捷,只需执行如apt-get install golang-go的命令,随后使用go build即可轻松构建项目,无需复杂的配置过程。HTTP响应处理简洁:借助Serve(w http.ResponseWriter, r *http.Request)函数,Go语言能够轻松读取HTTP请求并发送响应,大大简化了Web开发的复杂度,提高了开发效率。标准库支持丰富:net/http包作为Go语言的标准库之一,无需额外安装任何第三方库,即可直接用于网站开发,为开发者提供了极大的便利。系统级编程能力强大:作为系统级语言,Go在处理如ioctl等底层命令时同样表现出色,展现了其强大的跨领域应用能力。总的来说,Go语言不仅让我在短时间内迅速开发出功能完善的网站,即便在长时间搁置后,也能轻松回归并继续开发。相比之下,我曾尝试学习Rails,但总是感到困惑和挫败。而对于充满重复样板代码的Go项目,我反而觉得更加容易理解和维护。这无疑是Go语言在网站开发领域中的独特魅力所在。