为了让go不仅仅是一个demo,我们需要Golang在生产环境中实践。这个指南是一位一年级菜鸟后端工程师踩出的各种坑,充满了血与泪;尤其是错误处理和泛型方面折腾了好久。
参考:https://draveness.me/golang-101.html
代码规范
代码风格:
- https://github.com/golang/go/wiki/CodeReviewComments
- https://golang.org/doc/effective_go.html
- https://github.com/uber-go/guide/blob/master/style.md
面向接口
通过接口暴露接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package post
type Service interface {
ListPosts() ([]*Post, error)
}
type service struct {
conn *grpc.ClientConn
}
func NewService(conn *grpc.ClientConn) Service {
return &service{
conn: conn,
}
}
func (s *service) ListPosts() ([]*Post, error) {
posts, err := s.conn.ListPosts(...)
if err != nil {
return []*Post{}, err
}
return posts, nil
}
目录结构
https://github.com/golang-standards/project-layout、
辅助工具
- 格式化:goimports = gofmt(自动格式化) + import(依赖包管理)
- 静态检查:golint + golangci-lint
- 自动化:
- 在 GitHub 上我们可以使用 Travis CI 或者 CircleCI
- 在 Gitlab 上我们可以使用 Gitlab CI
错误注入
gofail,例如在事物的时候可以用来测试
https://github.com/lkk2003rty/notes/blob/master/gofail.md
从面向对象到Go
Go的interface能满足90%以上的OOP需求,但又没有C++的种种陷阱;执行速度足够快。
泛型
Go不支持泛型,解决方法:
- interface{}:使用 interface{},它由值和类型组成,与 Java 中的 object 类似。不推荐使用这种方法
- 反射
1
2
3func Load(result interface{}, id string) error
var result *Foo
err := Load(&result, "id")
函数重载
Go 不支持函数重载。我们推荐使用更加清晰明了的写法:1
2func foo(a int) {}
func fooWithB(a int, b string) {}
继承
1 | type A struct { } |
链式调用
1 | type Query struct { |
数据连接
redis
连接池优化
mongo
rpc
库加速
编译优化
运行优化
maxprocess
竞态检查
超时控制
context
优雅退出
日志
spring生产环境中使用logback作为日志模块,golang里有没有对应的包能提供类似的日志分割的功能呢?找到一个logrus,https://www.jishuwen.com/d/2Ek8
Hook模式
例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73package logutil
import (
"github.com/sirupsen/logrus"
"github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"time"
"path"
"github.com/pkg/errors"
"runtime"
"strings"
"fmt"
)
var Log *logrus.Logger
var funcpre = "code.sohuno.com/mp-recommend/mp-rec-trove/server/"
var filepre = "/root/go/src/code.sohuno.com/mp-recommend/mp-rec-trove/server/"
func init() {
Log = logrus.New()
Log.SetLevel(logrus.InfoLevel)
Log.SetReportCaller(true)
// for terminal
Log.Formatter = &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2006-01-02 15:04:05.000",
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
funcname := strings.Replace(f.Function, funcpre, "", -1)
filename := strings.Replace(f.File, filepre, "", -1)
return fmt.Sprintf("%s()", funcname), fmt.Sprintf("%s:%d", filename, f.Line)
},
}
ConfigLocalFilesystemLogger("log", "std.log", time.Hour* 24 * 30, time.Hour * 24)
}
func ConfigLocalFilesystemLogger(logPath string, logFileName string, maxAge time.Duration, rotationTime time.Duration) {
baseLogPaht := path.Join(logPath, logFileName)
writer, err := rotatelogs.New(
baseLogPaht+".%Y%m%d",
rotatelogs.WithLinkName(baseLogPaht), // 生成软链,指向最新日志文件 todo solic
rotatelogs.WithMaxAge(maxAge), // 文件最大保存时间
rotatelogs.WithRotationTime(rotationTime), // 日志切割时间间隔
)
if err != nil {
Log.Errorf("config local file system logger error. %+v", errors.WithStack(err))
}
// for log file
lfHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: writer, // 为不同级别设置不同的输出目的
logrus.InfoLevel: writer,
logrus.WarnLevel: writer,
logrus.ErrorLevel: writer,
logrus.FatalLevel: writer,
logrus.PanicLevel: writer,
},
&logrus.TextFormatter{
DisableColors: true,
TimestampFormat: "2006-01-02 15:04:05.000",
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
funcname := strings.Replace(f.Function, funcpre, "", -1)
filename := strings.Replace(f.File, filepre, "", -1)
return fmt.Sprintf("%s()", funcname), fmt.Sprintf("%s:%d", filename, f.Line)
},
})
var hook *HostNameHook
Log.AddHook(hook)
Log.AddHook(lfHook)
}
MyHook1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package logutil
import (
"github.com/sirupsen/logrus"
"os"
"fmt"
"runtime"
"strings"
"strconv"
)
var hostname= "HOSTNAME"
func init() {
hostname = os.Getenv("HOSTNAME")
fmt.Println("get hostname:", hostname)
}
type HostNameHook struct {
}
func (hook *HostNameHook) Fire(entry *logrus.Entry) error {
entry.Data["hostname"] = hostname // so call ip
entry.Data["goroutine_id"] = Goid() // 性能较差
entry.Data["goroutine_num"] = runtime.NumGoroutine()
return nil
}
func (hook *HostNameHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func Goid() int {
var buf [64]byte
n := runtime.Stack(buf[:], false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
id, err := strconv.Atoi(idField)
if err != nil {
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
}
return id
}
debug
goroutine_id
https://liudanking.com/performance/golang-%E8%8E%B7%E5%8F%96-goroutine-id-%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/
错误处理
- if err != nil { return nil, err }
- 不是所有panic都能被捕获
- recover
单元测试
https://mp.weixin.qq.com/s/oD96OEv92oX0ypAYLbYFyA
- _test.go
- Suite
- BDD
- Mock 方法
压测
性能优化
pprof工具
- 减少临时对象
- 循环并发
- 编译优化
go tool compile -S main.go
线程debug
循环引用
提高并发度
https://blog.csdn.net/Jeanphorn/article/details/79018205