Golang Go语言写业务的工程实践上的问题请教大家

发布于 1周前 作者 bupafengyu 来自 Go语言

自己是 Java 后台开发,尝试用 Go 写了个小项目,有几个工程实践上的问题想咨询下各位都是怎么处理的?

  1. 如何统一各组件的 log 输出?例如我用 echo 、xorm 、grpc 去构建业务,这三个库的日志格式各自不同,Go 生态中有没有类似 slf4j 的东西能让三者用同一种日志规范输出?
  2. 如何在 log 中附上当前每一个 http 请求的 requestId 呢?不能是靠 Context 层层往下传吧? Java 中是依赖于日志框架 MDC 实现的,相关 requestId 会绑定在线程上,线程模型可能会有性能问题,但对于开发者写代码来说还是挺友好的。
  3. 如何妥善的处理异常?业务代码中的常见操作就是读数据、执行业务逻辑、保存新数据,这个过程中任何一步有异常我都可以认为是业务处理异常,Java 中只需要把这三步全部 try catch 起来就行了(只是个例子,实际处理粒度不会这么大),但 Go 中只能每一步都冒泡,然后写很多次 if err != nil 么?

抛开工程上的这些问题,Go 还是蛮好的,找回了刚上学时候写 C 的快感,真就是单纯地在一行一行写代码,感觉每一行都能映射到汇编上,掌控性很强。感觉更适合强规范的基础设施开发和工具开发。

接下来想去看看 Rust ,不知道会不会遇到类似的问题。毕竟语言再好,最终还是要用来干活儿的。对于业务开发,CRUD boy 来说,写代码和查问题这种场景搞不定,语言再好也是麻烦。


Golang Go语言写业务的工程实践上的问题请教大家

更多关于Golang Go语言写业务的工程实践上的问题请教大家的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

21 回复

看到这么一篇文章,貌似日志规范这个事情在 2022 年还没有什么解决方案

[聊聊 Go 应用输出日志的工程实践 | Tony Bai]( https://tonybai.com/2022/03/05/go-logging-practice/)

更多关于Golang Go语言写业务的工程实践上的问题请教大家的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


  1. 比如 xorm 有个 setlogger 的功能, 需要把你的 logger 按他的 interface 封装一下, 就可以接入统一的日志了, grpc 和 echo 同理. 一般都会有这个功能, 但是不排除某些第三方库不支持.

    2. 一般都是 context 传

    3. 多数情况就是 if err != nil,或者用 recover 也行吧.

感谢,那看来和我想到的差不太多。社区都认可这些实践嘛,总感觉差了点意思…

  1. 没有。不同包往往都有自己的日志输出逻辑,完全看包的作者

    2. requestId 就是 context 层层往下传。context 的作用之一就是这个这么做没关系的,算是 Go 的标准做法。并且你要知道,Go 协程、Go 协程,这里不是 Go 多线程。不同的网络请求很可能会跑在同一个线程里的

    3. err 一层层往上返回也是 Go 的标准做法,if err != nil 有人觉得麻烦有人觉得无所谓(我属于后者)。不过因为现在官方 go 的 errors 包不含调用栈信息,所以实际上用得最多的还是 github.com/pkg/errors 这个包

你说的 java 的是生态决定的吧,只是那些包都支持了那个日志组件。

1.基本正经的库都不会乱打日志,一般都是 error 级别的才有,捕获 wrap 往上抛出即可,业务代码中使用的是 zap ,提出全局的 logger 配置到公共库
2.我研究过好多 web 框架 requestId 都是通过 ctx 传递的,这种做法也还是合理的,毕竟一层层传递 ctx 不光是为了一个 requestId
3.v 站有很严重的争议问题,怎么说怎么有理,我选择优先处理错误: https://go.dev/blog/errors-are-values

rust 做的非常好,标准库只定义了 log trait ,然后让第三方库去自己实现,这样不管什么框架用的啥,都要回归到标准 log ,这样统一全局。

第一个问题是生态软件没有一统江湖的选择,然而事实上也不能这么依赖第三方的依赖,毕竟这个选择权归作者所有,作者不用就抓瞎。Go 的第三方库基本这个问题就是看是否有预定抽象接口,替换一下即可。不过这也是作者行为,作者不允许你改,你能做的只有重定向输出。

第二个问题建议使用 context 下传,后面很多东西比较方便处理。如果实在不想,也有 gls 这些第三方方案可以用。但是这些方案不保证兼容性。周边生态基本是 context 兼容的,如果使用 gls 很难和这些方案结合,比如分布式追踪。选择 gls 另外一个问题和 goroutine 有关,新 goroutine 的创建时就需要额外处理很多东西了。这个在并发时会经常遇到。

第三个问题无解,就是辣么烦。不过想偷懒的话,Go1.18 之后有第三方库快速处理一下,比如 https://pkg.go.dev/github.com/samber/lo#readme-try

看了下,感觉还不错,很规整,star 了

但是基本上 Java 生态里所有的三方组件都会支持这个日志组件,已经是生态里日志规范的事实标准了。Go 发展也有快十年了,出现这样一个社区标准也不意外




感谢三位,还是得再观望下社区接下来的发展了

这个也太 nice 了!简直是理想状态了,我去了解了解

我想补充一个问题:
Go 里面如何优雅的处理事务呢?最好能实现类似于 spring 那种事务管理,自动回滚,提交。并且事务方法之间的调用保证在同一个事务之中。

无解

其实 1,2,3 中的痛点都是 Go 的推荐做法 XD

我们以前是 web 框架用 gin ,事务封装成一个 gin 中间件,进入业务处理前先获取一个 db 事务对象,业务处理完成后提交就行。如果是无状态的业务同时有多个进程,中间可能要加个 redis 分布式锁,事务提交前要先检查锁是否过期,过期了就不能提交。

1 、各个组件的日志看组件有没有提供 Logger 接口,有的话一般是把全局的 Logger 单独实现一个组件的 Logger 然后传进去,但是其实我们以前公司是不允许组件输出太多 Log 的。不然很容易就导致日志量暴表。
2 、据我所知大部分框架 RequestId 之类的还真是靠 context 往下传的,context 其实在 go 里面真的很有用,因为协程的生命周期都需要用 context 来控制。基本上你可以认为 context 就是用来跟协程进行绑定的东西,你不用 context 往下传,协程处理的生命周期就会断开,导致一些未知的 bug.
3 、Java 也不是什么地方都可以随便 Try catch 的,正常业务异常都需要 throw 出去,不然可能会丢失原始的错误信息,导致出现 bug 的时候无法排查。少数比如网络重试之类的异常可以直接 catch 掉 重试,go 里面 你要想省事就直接 进入协程处理业务进去的时候写一个 recover ,然后业务里面出错直接 panic 。我们以前就这么干,web 业务应用无所谓的。但是基础组件,中间件,我们不允许 panic

苦一苦了,不过这几个点也可能是我目前思维问题,适应下 Go 的方式应该问题也不大

在Go语言进行业务开发的工程实践中,确实会遇到一些常见问题,以下是一些关键点及建议:

  1. 并发编程:Go语言以其强大的并发处理能力著称,但使用goroutine和channel时需谨慎,避免资源泄露和死锁。建议采用上下文(context)管理请求的生命周期,控制goroutine的生命周期。

  2. 错误处理:Go语言没有异常机制,错误处理通过返回值实现。建议统一错误格式,使用自定义错误类型,便于日志记录和错误追踪。

  3. 依赖管理:使用Go Modules进行依赖管理,确保项目依赖的版本一致性。定期更新依赖,避免安全漏洞。

  4. 代码规范:遵循Go语言的编码规范,如使用驼峰命名法,保持代码简洁明了。使用linter工具(如golint)检查代码质量,提高代码可读性。

  5. 测试:编写单元测试、集成测试,确保代码质量。使用mock对象模拟外部依赖,提高测试的独立性和稳定性。

  6. 性能优化:关注程序的性能瓶颈,使用pprof等工具进行性能分析。优化算法和数据结构,减少不必要的内存分配和CPU消耗。

  7. 文档和注释:编写清晰的文档和注释,便于团队协作和代码维护。使用godoc工具生成API文档,提高代码的可读性和易用性。

总之,Go语言在业务开发中表现出色,但需注意并发控制、错误处理、依赖管理等方面的问题,确保项目的稳定性和可维护性。

回到顶部