这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
抖音--服务架构
性能体验方向
主要工作;
- 服务性能优化
- 降低资源占用成本
在工作中,或者优化服务方面还有哪些经验呢?
- 如何编写更简洁更清晰的代码
- 常用Go语言程序优化手段
- 熟悉Go程序性能分析工具
- 了解工程中性能优化的原则和流程
课程:
- 高质量编程
- 高质量编程的见解
- 编码规范
- 性能优化建议
- 性能调优实战
- 性能调优简介
- 性能分析工具pprof实战
- 性能调优案例
一、高质量编程
\
1.1 简介
推荐阅读 clear code
\
边界条件是否稳定
异常情况的处理,稳定性的保证
易读易维护
编程原则:
- 简单性----代码简洁
- 可读性----代码可读
- 生产力----提高整个团队的效率
1.2 编码规范
google具有编码规范,可以阅读
- 代码格式
- 注释
- 命名规范
- 控制流程
- 错误和异常处理
1.2.1 代码格式
- 使用gofmt自动格式代码
比如golang 自动内置格式化
- 比如goimports自动对依赖包进行管理
\
1.2.2 编码规范----注释
\
注释应该做的:
- 解释代码作用
- 注释应该解释代码如何做
- 注释应该解释代码实现的原因
- 注释应该解释代码什么情况出错
\
- 公共符号一定要注释
但是像接口这种,不需要提供核外的信息,没有用的注释不需要
总结:
- 代码是最好的注释
- 注释最好能够提供代码为表达的信息
1.2.3 编程规范---命名规范
- variable
简洁胜于冗长 比如利用i 而不是index
缩小一定犬大小 比如serverHTTP而不是serverHttp
*
用1,因为调用的时候是http.serve
- package
只用小写字母
简短但是包含一定的上下文
总结
- 核心在于降低阅读理解代码的成本
- 重点考虑上下文信息,设计简洁清晰的名称
1.2.4 编码规范--控制流程
重要
- 避免嵌套,保持正常流程规范
正常流程是return nil,而且不需要else 复杂冗余
if foo{
return x
}else{
return nil
}
if foo{
return x
}
return nil
- 尽量保证正常代码路径为最小缩进
- 也就是有限处理错误、特殊情况,尽早返回或者继续循环来减少嵌套
小结
- 复杂和循坏的语句,容易出现错误
1.2.5 编码规范---错误和异常处理
简单错误
- 指仅出现一次的错误,其他地方不需要捕获该错误
- 优先使用errors.New来创建匿名变量来直接表示简单错误
- 如果有格式化需求,使用fmt.Errorf
func defaultCheckRedict(via []*Request) error{
if len(via)>=10{
return errors.New("stopped after 10 redirects")
}
return nil
}
复杂错误---提供错误的包装(wrap)和解包装(unwrap)的能力
- 错误的wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
- 在fmt.Errorf中使用:%w关键字来将一个错误关联至错误链中
if err!=nil{
return fmt.Errorf("reading srcfiles list: %w",err)
}
错误判定---错误和异常处理
- 判定一个错误是否为特定错误,使用errors.Is
- 不同于使用==,使用该方法可以判定错误链上的所有错误是否含有特定的错误
data,err=lockfile(targ)
if errors.Is(err,fs.ErrNotExist){
return []byte{},nil
}
return data,err
- 在错误链上获取特定种类的错误,使用errors.As
if _,err:=...;err!=nil{
var pathError *fs.PathError
if errors.As(err,&pathError){
fmt.Println("failed at path",pathError.Path)
}else{
fmt.Println(err)
}
}
- panic 会导致业务的出错
不建议在业务中使用panic
但是如果程序在启动阶段发生不可逆转的错误,可以在init和main函数中使用panic
- recover----对应panic
recover 只能被defer在函数中使用
嵌套无法生效
只在当前goroutine生效
defer语句是后进先出的
1.3 性能优化的建议---benchmark
go 中利用benchmark来查看性能
补图
slice
如果容量足够,放入;如果不够,会进行扩容,从而降低性能
map
如果有预分配,map的性能会更好
- 不断的向map添加元素会触发map的扩容
- 提前分配好空间可以减少内存拷贝和rehash的消耗
- 建议根据实际需求提前预估好需要的空间
字符串处理
- stringbuilder----------最短
- +----------性能最差
- bytes.Buffer的方式----------性能和stringBuilder差不多
空结构体----节省空间的最好用法
- 空结构体struct{}实例不占据任何内存空间
- 可作为各种场景下的占位符使用
-
- 节省资源
- 空结构体本身具有很强的语义,这里不需要任何值,仅作为占位符
- 实现set,可以使用map来替代
- 对于这个场景,主需要用到map的键,而不需要值
- 即使将map设置为bool,也会多占据一个字节空间
性能优化
2.1 简介
性能调优的原则:
- 依靠数据而不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化