使用buffered channel实现对象池
sync.Pool对象缓存
sync.Pool对象获取
-
1> 尝试从私有对象获取
-
2> 私有对象不存在, 尝试从当前Processor的共享池获取
-
3> 如果当前Processor共享池也是空的,那么就尝试去其它Processor的共享池获取
-
4> 如果所有子池都是空的,最后就用用户指定的New函数产生一个新的对象返回
-
5> 如果私有对象不存在则保存为私有对象
-
6> 如果私有对象存在,放入当前Processor子池的共享池中
图
pool := &sync.Pool {
New: func() interface{} {
return 0
},
}
array := pool.Get().(int)
...
pool.Put(10)
sync.Pool对象的生命周期
- 1> GC会清除sync.pool缓存的对象
- 2> 对象的缓存有效期为下一次GC之前
sync.Pool总结
- 1> 适合于通过复用,降低复杂对象的创建和GC代价
- 协程安全,会有锁的开销
- 生命周期受GC影响,不适合于做连接池等,需自己管理生命周期的资源的池化
单元测试
内置单元测试框架
-
1> Fail, Error: 该测试失败,该测试继续,其它测试继续执行
-
2> FailNow, Fatal: 该测试失败,该测试中止,其它测试继续执行
-
代码覆盖率
- go test -v -cover
-
断言
Benchmark
func BenchmarkConcatStringByAdd(b *testing.B) {
//与性能测试无关的代码
b.ResetTimer()
for i := 0; i < b.N; i++ {
//测试代码
}
b.StopTimer()
//与性能测试无关的代码
}
go test -bench=. -benchmem
-bench=<相关benchmark测试>
Windows下使用go test命令行时,-bench=.应写为-bench="."
BDD (Behavior Driven Development)
让业务领域的专家参与开发
用业务领域的语言来描述
BDD in Go
项目网站 https://github.com/smartystreets/goconvey
安装
go get -u github.com/smartystreets/goconvey/convey
启动WEB UI
$GOPATH/bin/goconvey
reflect.TypeOf vs reflect.ValueOf
- 1> reflect.TypeOf返回类型(reflect.Type)
- 2> reflect.ValueOf返回值(reflect.Value)
- 3> 可以从reflect.Value获得类型
- 4> 通过kind来判断类型
判断类型--Kind()
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
...)
利用反射编写灵活的代码
按名字访问结构的成员
reflect.ValueOf(*e).FieldByName("Name")
按名字访问结构的方法
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)})
Struct Tag
访问StructTag
if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok {
t.Error("Failed to get 'Name' field.")
} else {
t.Log("Tag:format", nameField.Tag.Get("format"))
}
Reflect.Type 和 Reflect.Value都有FieldByName方法,注意区别
DeepEqual
比较切片和map
关于反射
- 1> 提高了程序的灵活性
- 2> 降低了程序的可读性
- 3> 降低了程序的性能
"不安全"行为的危险性
i := 10
f := *(*float64)(unsafe.Pointer(&i))
架构模式
Pipe-Filter架构
-
1> 非常适合与数据处理及数据分析系统
-
2> Filter封装数据处理的功能
-
3> 松耦合: Filter只跟数据(格式) 耦合
-
4> Pipe用于连接Filter传递数据或者在异步处理过程中缓冲数据流
进程内同步调用时,pipe演变为数据在方法调用间传递
Filter和组合模式
示例
Micro Kernel
特点
- 1> 易于扩展
- 2> 错误隔离
- 3> 保持架构一致性
要点
-
1> 内核包含公共流程或通用逻辑
-
2> 将可变或可扩展部分规划为扩展点
-
3> 抽象扩展点行为,定义接口
-
4> 利用插件进行扩展
图
示例
内置的JSON解析
利用反射实现,通过FeildTag来标识对应的json值
type BasicInfo struct {
Name string `json:"name"`
Age int `json:"age"`
}
type JobInfo struct {
Skills []string `json:"skills"`
}
type Employee struct {
BasicInfo BasicInfo `json:"basic_info"`
}
更快的JSON解析
EasyJSON采用代码生成而非反射
安装
go get -u github.com/mailru/easyjson/...
使用
easyjson -all <结构定义>.go
Default Router
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux //使用缺省的Router
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
路由规则
-
1> URL分为两种, 末尾是/: 表示一个子树,后面可以跟其他子路径, 末尾不是/,表示一个叶子,固定的路径
以/结尾的URL可以匹配它的任何子路径,eg: /images会匹配/images/cute-cat.jpg
-
2> 它采用最长匹配规则,如果有多个匹配,一定采用匹配路径最长的那个进行处理
-
3> 如果没有找到任何匹配项,会返回404错误
更好的Router
https://github.com/julienschmidt/httprouter
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!
", ps.ByName("name"))
}
func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe(":8080", router))
}
构建Restful服务
面向资源的架构(Resource Oriented Architecture)
性能分析工具
准备工作
1. 安装graphviz
sudo apt install graphviz
2. 将$GOPATH/bin 加入 $PATH
Mac OS:在.bash_profile中修改路径
Linux
3. 安装go-torch
go get github.com/uber/go-torch
下载并复制flamegraph.pl(https://github.com/brendangeregg/FlameGraph)至$GOPATH/bin路径 将$GOPATH/bin加入$PATH
通过文件方式输出Profile
- 1> 灵活性高,适用于特定代码段的分析
- 2> 通过手动调用runtime/pprof的API
- 3> API相关文档https://studygolang.com/static/pkgdoc/pkg/runtime_pprof.htm
- 4> go tool pprof [binary] [binary.prof]
通过HTTP方式输出Profile
- 1> 简单,适合于持续性运行的应用
- 2> 在应用程序中导入import _ "net/http/pprof", 并启动http server即可
- 3> http://
: /debug/pprof/ - 4> go tool pprof _http://
: /debug/pprof/profile?seconds=10(默认值为30秒) - 5> go-torch -seconds 10 https://
: /debug/pprof/profile
性能调优过程
常见分析指标
- Wall Time
- CPU Time
- Block Time
- Memory allocation
- GC times/time spent
别让性能被"锁"住
- 减少锁的影响范围
- 减少发生锁冲突的概率
- sync.Map
- ConcurrentMap
- 避免锁的使用
- LAMX Disruptor: https://martinfowler.com/articles/lmax.html
sync.Map
-
1> 适合读多写少,且Key相对稳定的环境
-
2> 采用空间换时间的方案,并且采用指针的方式间接实现值的映射,所以存储
空间会较built-in map大
Concurrent Map
- 1> 适用于读写都很频繁的情况
GC友好的代码
避免内存分配和复制
- 复杂对象尽量传递引用
- 数组传递
- 结构体传递
打开GC日志
go tool trace
普通程序输出trace信息
pacakage main
import (
"os"
"runtime/trace"
)
func main() {
f, err := os.Create("trace.out")
if err != nil {
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil {
panic(err)
}
defer trace.Stop()
//Your program here
}
测试程序输出trace信息
go test -trace trace.out
可视化trace信息
go tool trace trace.out
避免内存分配和复制
- 1> 初始化至合适的大小
- 自动扩容是有代价的
- 复用内存
面向错误的设计
隔离
隔离错误-设计
隔离错误-部署
重用 vs 隔离
逻辑结构的重用 vs 部署结构的隔离
冗余
单点失效
限流
慢响应
不要无休止的等待
错误传递
断路器
面向恢复的设计
健康检查
- 注意僵尸进程
- 池化资源耗尽
- 死锁
Let it Crash!
defer func() {
if err := recover(); err != nil {
log.Error("recovered panic", err)
}
}()
构建可恢复的系统
- 1> 拒绝单体系统
- 2> 面向错误和恢复的设计
- 在依赖服务不可用时,可以继续存活
- 快速启动
- 无状态
与客户端协商
服务器:"我太忙了, 请慢点发送数据"
Client: "好, 我一分钟后再发送"
Chaos_engneering
"if something hurts, do it more often!"
* 如果问题经常发生人们就会学习和思考解决它的办法
Chaos Engineering 原则
-
Build a Hypothesis around Steady State Behavior
-
Vary Real-world Events
-
Run Experiments in Production
-
Automate Experiments to Run Continuously
-
Minimize Blast Radius
相关开源项目
https://github.com/Netflix/chaosmonkey
https://github.com/easierway/service_decorators/blob/master/README.md
是结束, 更是开始!
The master has failed more times than the beginner has tried.
图书推荐
* Go程序设计语言
* 面向模式的软件架构
* 计算机程序的构造和解释
lisp 函数式编程