重读一周代码(一)

Rob Pike曾说“复杂性是以乘积方式增长的”。 当我们在快速发布的压力之下,不断的增加系统功能、选项、以及配置,一点点的将系统的某个部分变的更加复杂,难以理解,最终变的不可维护!

因此,为了尽可能的前期少留坑,周末做了两件小事儿,收获挺多,整理分享!
1、重读一周的代码,完善README
2、重读《Go程序设计语言》

一、重读代码

coding了一周,疲于沟通,联调和开发。太多的战略和趋势的高谈阔论,总觉的越来越缺少,冷静下来,对关键细节深度思考

趁着周末,好好回顾了一下自己这一周开发的项目!

1.1 关于几个选型

1.1.1 uuid选型

项目中用到了唯一健,用了公司的ice,那么ice做的怎么样?选型是否有坑?

先贴结论:整体来看,目前ice完全够用,针对时钟回拨和趋势递增问题也可以简单封装解决,但在细节问题的实现上,不如百度的UidGenerator和美团的leaf!

uuid有四种通用解决方案(mysql自增、、伪随机本地hash、GUID/UUID方案、类snowflake方案)

具体细节就不做介绍了,都或多或少有些小问题。那么公司的ice是否解决了相关问题?

  • 实现方案:mysql自增+cache批量预取
  • 高可用方案:多IDC部署 + 超长预加载容灾+ 时间戳算法降级兜底 + 异地多活。
  • 性能问题:增加了预取,RPC提供(项目中不是瓶颈,实测在ms级别,可以自己加个预取cache提升)
  • 水平扩展:proxy负载均衡提供扩展能力。
  • 趋势递增问题解决:未解决(如果担心泄露信息,那就必须业务封装打乱位置
  • 时钟回拨问题解决:时钟回拨目前看也没有解决。(不确认是否有启动时钟检查,但是预取去重能部分解决)
  • 跨机房重复问题:机房前缀解决。

1.1.2 json解析的选型

go的json解析历来被认诟病,目前采用github.com/json-iterator/go,压测下。

先贴结论:json-iterator针对实际业务中的长json,性能在30us左右,短json在1us左右,完全满足要求。
但是:目前采用go 1.13.5 官方库的实际压测结果由于jsoniter,不折腾,切官方库!

jsoniter的一些优化点:
1、流式读取到内存,降低GC压力。
2、按照schema解析,减少if-else判断和反射消耗。
3、int直接解析,无需两遍处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
实际数据:
// 采用long json [实际json]
40162 33572 ns/op 10627 B/op 197 allocs/op
// 采用long json fuzzy情况下 [实际json]
28250 50821 ns/op 10627 B/op 197 allocs/op


// 采用短json
714550 1689 ns/op 720 B/op 16 allocs/op
// 采用短json fizzy
515376 2438 ns/op 720 B/op 16 allocs/op

// 官方库long json
41347 28630 ns/op 8242 B/op 152 allocs/op

1.1.3 go mod和glide

glide挺好,从使用上看有两个小问题。

  • 位置强指定:非标准库必须位于GOPATH指定的src下。
  • 偶尔bug:例如,出现几次glide update 不能更新的问题,需要强制glide cc。

既然用了1.13.5,那最好选用根红苗正的go mod。
从实际使用上看和IDE结合的很棒,开启GO111MODULE=on之后,基本上包管理完全无感知,所以到现在也不会几个go mod命令 ̄□ ̄||!

关于包管理的一些设计可以参考:package manager

1.2 关于项目的一些约定整理

只是最近这两周用到的一些内容的梳理,补充readme

1.2.1 common和util却别

项目中util和common两个文件如何使用?

utils 放通用的工具类,和业务没有任何关系,可以独立迁移,比如:time,json,math,hash等
common 放和业务可能有关系的通用类,比如:errors,log,request,conf,cache,db等,含有业务的局部配置信息!

1.2.2 关于变量/常量位置

系统级别的变量:写在conf,通过conf读取放入内存
请求级别的变量:写入context,随context value流转,同时,传输给下游时候,注意清理!

系统级常量:放common.consts
服务级常量:放对应的dao(否则整体的consts会很乱)

1.2.3 关于接口是否独立文件和单测

推荐每个接口一个独立文件,每个大类放在一个包里,清晰明了!
比如:

1
2
3
4
5
xxModel
├── getXX.go
├── setXX.go
├── deleteXX.go
└── xxx_test.go

1.2.4 状态位约定

没啥,必须采用int16,int4的1、2、3连续的状态位存在扩展风险,遇到过坑!
Exception和DONE 状态置高危,比如8XX,9xx

1.2.5 关于一些封装的约定

涵盖但不限于request,mysql,redis,http请求,httpserv,response的简单封装,不要过度,必须标明封装背景,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//GetReqBody 读取body体封装
//解决:body体很大时,需要多次内存扩容,耗费cpu,增加gc压力,而且最后可能还会浪费一半的内存空间的问题
func GetReqBody(ctx *context.Context,r *http.Request) ([]byte, error) {
var b []byte
var err error

cLen := r.ContentLength
if cLen > 0 {
b = make([]byte, cLen, cLen)
_, err = io.ReadFull(r.Body, b)
} else {
b, err = ioutil.ReadAll(r.Body)
}
_ = r.Body.Close()

// set request 2 context
*ctx = context.WithValue(*ctx,consts.CtxReqBytes,b)
return b, err
}

二、重读《GO程序设计语言》

每次读程序设计语言感觉都不一样,周末又翻了翻,还没翻完!
可能最近写博客的原因,整体更侧重于整体的设计布局,分层逻辑和重要细节!
暂时未写完,下周续上

崔小拽 wechat
欢迎您扫一扫上面的微信公众号,订阅小拽的博客!