总结:
(1) go 的 map读,写是线程不安全的
image.png
(2) sync.map 用空间换时间的思想,适合读多写少的场景,读,写是线程安全,但遍历是线程不安全的,运行几个小时以后,出现性能巨降的情况。刚开始18w条/分,后来1w条/分;
(3) orcaman/concurrent-map的思路是把大内存的map划分为若干小内存map
(4)后面redis替换内存后,13w条/分,开了四个goroutine
每天亿条的数据量
image.png
背景
数据经过filebeat ---->logstash--->kafka--->按一定维度聚合计算------>写入mysql---->grafana展示
聚合计算的数据结构
k, v的map内存存储
计算的数据:
最小,最大,平均,总数,成功,失败, 耗时........
内存版本
go 的 map 不加锁
type dataStruct struct {
cost int64
}
var mapData = make(map[string]dataStruct)
func write(){
fmt.Println("write")
//if mapData["cost"].cost != 0 {
// fmt.Println("exit")
//}
mapData["cost"]=dataStruct{10}
}
func read(){
fmt.Println("read")
v := mapData["cost"]
fmt.Println(v)
}
运行结果:死锁,说明map读,写线程是不安全的
image.png
go map 加锁
type simpleLock struct {
mu sync.Mutex
mapData map[string]string
}
var l simpleLock
func write(){
fmt.Println("write")
l.mapData = make(map[string]string)
l.mu.Lock()
l.mapData["cat"] = "hobb"
l.mu.Unlock()
}
func read(){
l.mapData = make(map[string]string)
fmt.Println("read")
l.mu.Lock()
v := l.mapData["cost"]
l.mu.Unlock()
fmt.Println(v)
或者这样的写法
var counter = struct{
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}
counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)
counter.Lock()
counter.m["some_key"]++
counter.Unlock()
运行的结果
image.png
sync.map
ar syncMap sync.Map
func write(){
fmt.Println("write")
syncMap.Store("hugo", "boss")
}
func read(){
v, _ := syncMap.Load("hugo")
fmt.Println(v)
}
运行结果
image.png
以上总结
上面的写法,适合,量少,读多写少的场景
大量的读写,还得看redis, mq
通过实践,用redis替代内存后,每分钟的数据由原来的2w条/分达到了 12w条/s。
这性能还可以再优化,通过跟踪,时间有三分之二耗在了消费kafka数据,1条/3ms
image.png
image.png
实践的结果
积压的3亿条数据经过一天的消费,只剩下条4000w数据
image.png
参考
深度解密 Go 语言之 sync.map
Golang:一文解决Map并发问题
https://cloud.tencent.com/developer/article/1539049
go 分段锁ConcurrentMap,map+读写锁,sync.map的效率测试
https://blog.csdn.net/yzf279533105/article/details/98636679
网友评论