背景:
开发业务代码,需要获取企业所有部门内所有的人员。需要调用A接口(获取企业内所有部门id)先获取所有部门,然后遍历所有获取到部门id调用B接口(通过部门id获取该部门下的人员). 通过api调用后放入数据库构成 人员表.
问题:
- 线上环境调用A接口后获取的部门列表有10w个部门,则需要调用10w次B接口. 获取时间巨慢.
解决想法:
- 无疑便是萌发一次多生产者单消费者模型的公共组件的研发.
使用样例:
先定义一个生产者的interface, 实现生产货物的函数Produce:
type MultiProducerParams struct {
// Wg *sync.WaitGroup //must be pointer or can not be transfer into here
Begin int
End int
//SouceData *[]map[string]string
}
func (mpp *MultiProducerParams) Produce() (interface{}, error) {
time.Sleep(time.Millisecond * 1)
return mpp.Begin, nil
}
定义一个消费货物的函数Consume:
var countNum = 0
func Consume(ps []Product) {
time.Sleep(time.Millisecond * 1)
/*
fmt.Println("")
for _, p := range ps {
fmt.Printf("%+v,", p)
}*/
m.Lock()
defer m.Unlock()
countNum += len(ps)
}
进行使用:
关键函数:
wp := NewWorkPool(11, Produce,20) 创建工作池,第一个参数是工作池协程数,第二个是批量消费货物的函数,第三个是批量消费货物的数量
wp.Invoke(&mpp) 放入生产任务
使用代码:
var endNum = 1000
var m sync.Mutex
func Test_WorkPool(t *testing.T)
{
wp := NewWorkPool(11, Produce,20)
delta := 2
for i := 1; i <= endNum; i = i + 1 {
begin := i * delta
end := i*delta + delta
var mpp MultiProducerParams
mpp.Begin = begin
mpp.End = end
wp.Invoke(&mpp)
}
wp.Close()
if countNum != endNum {
t.Fatalf("not equal: countNum: %d, endNum: %d", countNum, endNum)
}
}
结合实际:
在produce定义请求http操作,在consume定义入库操作,即可完成。
基础组件解析:
组成模块:
- 协程复用和避免开启过多 (选择腾讯协程池"github.com/panjf2000/ants")
- slice操作tips
- 抽象架构设计,易用,通用
模块解析:
slice操作tips:
- 预分配空间
- 空间复用, cap空间保留.
预分配空间:
根据已知数组长度预先批量分配空间: ps = make([]Product, 0, bulkNum)
空间复用: 适当的由长数组截取成短数组能复用cap空间.(其实是一次阅读go-mysql-driver代码学习到的)
复用代码:
func BenchmarkWithArrayCap(b *testing.B) {
bulkNum := 10
ps := make([]Product, 0, bulkNum)
nowNum := 0
for i := 0; i < b.N; i++ {
if nowNum >= bulkNum {
ps = ps[0:0]
nowNum = 0
//b.Log(cap(ps), len(ps))
}
p := 1000
ps = append(ps, p)
nowNum = nowNum + 1
}
}
无空间复用:
func BenchmarkWithArrayNoCap(b *testing.B) {
bulkNum := 10
ps := make([]Product, 0, bulkNum)
nowNum := 0
for i := 0; i < b.N; i++ {
if nowNum >= bulkNum {
ps = ps[bulkNum:bulkNum] // or ps=make([]Product,0,bulkNum)
nowNum = 0
//b.Log(cap(ps), len(ps))
}
p := 1000
ps = append(ps, p)
nowNum = nowNum + 1
}
}
差异对比:
goos: linux
goarch: amd64
pkg: grutils/grpattern
BenchmarkWithArrayCap-8 77996929 13.6 ns/op 8 B/op 1 allocs/op
BenchmarkWithArrayNoCap-8 32943752 32.3 ns/op 40 B/op 1 allocs/op
PASS
附录: 实现代码:
https://github.com/gdgrc/grutils