基础开发环境配置
官网下载go:https://golang.org/dl/ 推荐1.13-1.15
goproxy配置,使用go mod
git 配置: git config — global
包依赖管理:
go get xxx
使用命令 go mod vendor 配合go build -mod=vendor即可快速方便构建项目。
Go mod, vendor, gopath几种用法和优劣,最新推荐的方式要了解下
三方工具库:
redis:https://github.com/go-redis/redis
性能工具:
Go单测工具中的benchmark:
编写方法BenchmarkAppendFloat
测试:go test -run=main_test.go -bench=BenchmarkAppendFloat -cpuprofile=cpu.file ./
Go工具包自带pprof
使用Uber的go-torch
性能优化:
GO反射带来的性能消耗
tp := reflect.TypeOf(Abc{}),数据结构的类型编译耗时会比较长
大部分的性能消耗都在反射创建和反射赋值上,反射赋值我们可以尝试使用unsafe包直接操作内存:
var tp = reflect.TypeOf(Abc{})
val := reflect.New(tp)
val.Elem().Field(0).SetString("hello")
val.Elem().Field(1).SetInt(200)
val.Elem().Field(2).SetBool(true)
->
tp = reflect.TypeOf(Abc{})
of1 = tp.Field(1).Offset
of2 = tp.Field(2).Offset
val := reflect.New(tp).Interface()
of0 := (*emptyInterface)(unsafe.Pointer(&val)).word
*(*string)(of0) = "hello"
*((*int64)(unsafe.Pointer(uintptr(of0) + of1))) = 200
*((*bool)(unsafe.Pointer(uintptr(of0) + of2))) = true
优化官方hash包的案例来说明怎么使用工具优化内存的使用,包名hash/fnv
主要的性能消耗是在对象创建和write数据上,接下来分析一下源码,发现两次对象创建都没有必要使用堆内存,write的时候官方直接使用range操作[]byte,我们知道range操作每次迭代都是一次内存拷贝,尝试进行优化:
直接干掉对象的创建,使用函数代替
优化迭代方法,减少拷贝次数
const (
offset64 = uint64(14695981039346656037)
prime64 = uint64(1099511628211)
Init64 = offset64
)
func HashBytes64(b []byte) uint64 {
return AddBytes64(Init64, b)
}
func AddBytes64(h uint64, b []byte) uint64 {
for len(b) >= 8 {
h = (h * prime64) ^ uint64(b[0])
h = (h * prime64) ^ uint64(b[1])
h = (h * prime64) ^ uint64(b[2])
h = (h * prime64) ^ uint64(b[3])
h = (h * prime64) ^ uint64(b[4])
h = (h * prime64) ^ uint64(b[5])
h = (h * prime64) ^ uint64(b[6])
h = (h * prime64) ^ uint64(b[7])
b = b[8:]
}
if len(b) >= 4 {
h = (h * prime64) ^ uint64(b[0])
h = (h * prime64) ^ uint64(b[1])
h = (h * prime64) ^ uint64(b[2])
h = (h * prime64) ^ uint64(b[3])
b = b[4:]
}
if len(b) >= 2 {
h = (h * prime64) ^ uint64(b[0])
h = (h * prime64) ^ uint64(b[1])
b = b[2:]
}
if len(b) > 0 {
h = (h * prime64) ^ uint64(b[0])
}
return h
}
稳定性建设:
事前-压测、事中-流量控制、事后-日志监控
流量控制
Sentinel-go : https://sentinelguard.io/zh-cn/docs/golang/basic-api-usage.html?spm=ata.21736010.0.0.456468d2T3HG8C
日志监控
调用追踪trace
全链路压测:
压测标识别
压测标透传:go context传递
压测流量/数据隔离:
Db:影子表
Redis:路由到redis压测集群
基本概念:
错误和异常处理:
defer, panic, reoover — 类似java里的try-catch-finally,注意下recover只有定义在defer函数里才会生效,注意哪些错误能recover(比如slice越界,nul指针被引用,除零,写关闭的chan),thread limit不能revocer,concurrentmap竞争写不能recover;recover最佳实践:一般recover后会判断是否为err,有可能需要处理特殊的error,一般也需要打印日志或者告警
避免程序无法recover被干掉
错误处理的模型一般有两种,一般是错误码模型比如C/C++和Go,还有异常模型比如Java和C#。
推荐用github.com/pkg/errors这个错误处理的库
日志:
推荐阅读Kafka对于Log的定义,广义日志是可以理解的消息
日志要素:
traceid — 塞到context里,通过logger的通用函数打印(函数具体见使用的logger类)
耗时 — 类似java里的profiler
Go并没有支持类继承体系和多态,使用抽象函数并且在继承类里实现抽象方法是不可行的。推荐使用组合和接口
举例理解:
跨平台编译器,Go Composition: Compiler,代码如下所示:
package main
import (
"fmt"
)
// core structure design start
type SourceCollector interface {
collectSource()
}
type TargetCompiler interface {
compileToTarget()
}
type CrossCompiler struct {
collector SourceCollector
compiler TargetCompiler
}
func (v CrossCompiler) crossCompile() {
v.collector.collectSource()
v.compiler.compileToTarget()
}
// core structure design end
type IPhoneCompiler struct {
}
func (v IPhoneCompiler) collectSource() {
fmt.Println("IPhoneCompiler.collectSource")
}