
[[414231]]uG环球娱乐城
本文转载自微信公众号「薯条的编程教诲」,作家圭臬员薯条。转载本文请关连薯条的编程教诲公众号。
组内的数据系统在贯串一个业务需求时无法得志性能需求,于是针对这个场景作念了一些优化,在此写篇著述作念记载。
业务场景是这么:调用方一次得到某个用户的几百个特征(可以把特征清醒为属性),特征以 redis hash 的相貌存储在耐久化 KV 数据库中,特征数据以天级别为更新粒度。条目 95 分位的蔓延在 5ms 傍边。
宝马会色碟
这个数据系统属于无景色的作事,为了增大隐隐量和裁减蔓延,从存储和代码两方面进行优化。
存储层面存储层面,一次调用一个用户的三百个特征原决策是用 redis hash 作念表,每个 field 为用户的一个特征。由于用户单个央求会得到几百个特征,即使用hmget作念兼并,存储也需要去多个 slot 中得到数据,着力较低,于是对数据进行归一化,即:把 hash 表的通盘 filed 打包成一个 json 情势的 string,举个例子:
// uG环球娱乐城优化前的特征为 hash 情势 hash key : user_2837947 127.0.0.1:6379> hgetall user_2837947 1) "name" // 特征1 2) "薯条" // 特征1的值 3) "age" // 特征2 4) "18" // 特征2的值 5) "address" // 特征3 6) "China" // 特征3的值 // 优化后的特征为 string json情势 string key: user_2837947 val: { "name":"薯条", "age":18, "address":"China" }
特征进行打包后惩处了一次央求去多个 slot 得到数据时延较大的问题。关联词这么作念可能带来新的问题:若 hash filed 过多,string 的 value 值会很大。当今预想的解法有两种,一种是按照类型将特征作念细分,比如蓝本一个 string 内部有 300 的字段,拆分红 3 个有 100 个值的 string 类型。第二种是对 string val 进行压缩,在数据存储时压缩存储,读取数据时在圭臬中解压缩。这两种步调也可以聚首使用。
如果这么仍不成得志需求,可以在耐久化 KV 存储前再加一层缓存,缓存失效时分左证业务脾性设立,这么圭臬交互的经由会酿成这么:

接着来优化一下代码。领先需要几个器用去协助咱们作念性能优化。领先是压测器用,压测器用可以模拟真确流量,在预估的 QPS 下不雅察系统的进展情况。发压时瞩目渐进式加压,不要一下次压得太死。
然后还需要 profiler 器用。Golang 的生态中有关器用咱们能用到的有 pprof 和 trace。pprof 可以看 CPU、内存、协程等信息在压测流量进来时系统调用的各部分耗时情况。而 trace 可以检察 runtime 的情况,比如可以检察协程诊治信息等。本次优化使用 压测器用+pprof 的 CPU profiler。
底下来看一下 CPU 运行耗时情况:
右侧主如果 runtime 部分,先忽略

火焰图中圈出来的大平顶山皆是可以优化的地点,
创新技术
这里的三座平顶山的主要皆是json.Marshal和json.Unmarshal操作引起的,关于 json 的优化,有两种念念路,一种是换个高性能的 json 成见包 ,另一种是左证业务需求看能否绕过成见。底下永诀来先容:
高性能成见包+少量黑科技这里使用了陶师父的包github.com/json-iterator/go。看了他的 benchmark 截止,比 golang 原生库照旧要快许多的。我方再写个相比顺应咱们场景的Benchmark看陶师父有莫得骗咱们:
package main import ( "encoding/json" jsoniter "github.com/json-iterator/go" "testing" ) var s = `{....300多个filed..}` func BenchmarkDefaultJSON(b *testing.B) { for i := 0; i < b.N; i++ { param := make(map[string]interface{}) _ = json.Unmarshal([]byte(s), ¶m) } } func BenchmarkIteratorJSON(b *testing.B) { for i := 0; i < b.N; i++ { param := make(map[string]interface{}) var json = jsoniter.ConfigCompatibleWithStandardLibrary _ = json.Unmarshal([]byte(s), ¶m) } }
运行截止:

这个包易用性也很强,在蓝本 json 代码成见的上头加一滑代码就可以了:
博彩平台免费赌博var json = jsoniter.ConfigCompatibleWithStandardLibrary err = json.Unmarshal(datautil.String2bytes(originData), &fieldMap
还有一个可以优化的地点是string和[]byte之间的转化,咱们在代码里用的参数类型是string,而 json 成见经受的参数是[]byte,是以一般在json成见时需要进行转化:
err = json.Unmarshal([]byte(originData), &fieldMap)
那么string转化为[]byte发生了什么呢。
package main func main(){ a := "string" b := []byte(a) println(b) }
咱们用汇编把编译器偷偷作念的事持出来:

来看一下这个函数作念了啥:

这里底层会发生拷贝时局,咱们可以拿到[]byte和string的底层结构后,用黑科技去掉拷贝过程:
皇冠世界杯源码func String2bytes(s string) []byte { x := (*[2]uintptr)(unsafe.Pointer(&s)) h := [3]uintptr{x[0], x[1], x[1]} return *(*[]byte)(unsafe.Pointer(&h)) } func Bytes2String(b []byte) string { return *(*string)(unsafe.Pointer(&b)) }
底下写 benchmark 看一下黑科技好不好用:
package main import ( "strings" "testing" ) var s = strings.Repeat("hello", 1024) func testDefault() { a := []byte(s) _ = string(a) } func testUnsafe() { a := String2bytes(s) _ = Bytes2String(a) } func BenchmarkTestDefault(b *testing.B) { for i := 0; i < b.N; i++ { testDefault() } } func BenchmarkTestUnsafe(b *testing.B) { for i := 0; i < b.N; i++ { testUnsafe() } }
运行速率,内存分拨上后果皆很赫然,黑科技果真黑:
博彩平台排名张艺兴ck内裤广告
加 cache,空间换时分
神志中有一块代码矜重处理 N 个央求中的参数。代码如下:
for _, item := range items { var params map[string]string err := json.Unmarshal([]byte(items[1]), ¶ms) if err != nil { ... } }
在这个需要优化的场景中,上游在单次央求得到某个用户300多个特征,如果用上头的代码咱们需要json.Unmarshal300屡次,欧博注册官网这是个无须且极端耗时的操作,可以加 cache 优化一下:
认识程德培已有近40年了。20世纪80年代中期,听说我也学写文学评论电影评论,他介绍我到他曾经参加过的虹口影评组。到虹口影评组写了一篇反思上海电影的长文后,我们借乍浦路的解放剧场会议室举行例会活动,他闻讯后赶来参加。后来作协叫他创办《文学角》,他请我在他手下打了3个月临工。90年代初他又负责《海上文坛》,后来听说他“下海”搞了一个会员制的新世界读书会。再后来又看到他频频发表文学评论文章。
9月29日,位于新疆乌鲁木齐市二道桥的新疆国际大巴扎举行开街仪式,吸引众多市民及游客围观。即日起,该景区每天将举办隆重盛大的开街仪式,充满民族特色的音乐和舞蹈,以及时装走秀、巡游等环节,让游客近距离感受新疆的“热情”。
paramCache := make(map[string]map[string]string) for _, item := range items { var params map[string]string tmpParams, ok := cacheDict[items[1]] // 莫得成见过,进行成见 if ok == false { err := json.Unmarshal([]byte(items[1]), ¶ms) if err != nil { ... } cacheDict[items[1]] = params } else { // 成见过,copy出一份 // 这里的copy是为了细心并提问题 params = DeepCopyMap(tmpParams) } }
这么表面上不会存在职何的放大时局,读者一又友如果有批处理的接口,代码中又有雷同这么的操作,可以看下这里是否有优化的可能性。
for { dosomething() }
替换耗时逻辑

火焰图中的 TplToStr 模板函数雷同占到了相比大的 CPU 耗时,此函数的功能是把用户传来的参数和预制的模板拼出一个新的 string 字符串,比如:
入参:Tpl: shutiao_test_{{user_id}} user_id: 123478 复返:shutiao_test_123478
在咱们的系统中,这个函数左证模板和用户参数拼出一个 flag,左证这个 flag 是否换取算作某个操作的绚丽。这个拼模板是一个极端耗时的操作,这块可以平直用字符串拼接去代替模板功能,比如:
入参:Tpl: shutiao_test_{{user_id}} user_id: 123478 复返:shutiao_test_user_id_123478
优化完之后,火焰图中一经看不到这个函数的平顶山了,平直节俭了 5%的 CPU 的调用百分比。

皇冠体育
prealloc还发现一些 growslice 占得微量 cpu 耗时,本认为预分拨可以惩处问题,但作念 benchmark 测试发现 slice 容量较小时是否作念预分拨在性能上相反不大:

package main import "testing" func test(m *[]string) { for i := 0; i < 300; i++ { *m = append(*m, string(i)) } } func BenchmarkSlice(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() m := make([]string, 0) b.StartTimer() test(&m) } } func BenchmarkCapSlice(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() m := make([]string, 300) b.StartTimer() test(&m) } }

关于代码顶用到的 map 也可以作念一些预分拨,写 map 时如果能证明容量尽量用 make 函数对容量进交运行化。

package main import "testing" func test(m map[string]string) { for i := 0; i < 300; i++ { m[string(i)] = string(i) } } func BenchmarkMap(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() m := make(map[string]string) b.StartTimer() test(m) } } func BenchmarkCapMap(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() m := make(map[string]string, 300) b.StartTimer() test(m) } }
这个优化照旧相比灵验的:

接口经由中有一些不影响主经由的操作十足可以异步化,比如:往外发送的统计职责。在 golang 中异步化等于起个协程。
皇冠客服飞机:@seo3687追忆一下套路:
代码层面的优化,是 us 级别的,而针对业务对存储进行优化,可以作念到 ms 级别的,是以优化越围聚期骗层后果越好。关于代码层面,优化的要领是:
压测器用模拟场景所需的真确流量
pprof 等器用检察作事的 CPU、mem 耗时
锁定平顶山逻辑,看优化可能性:异步化,改逻辑,加 cache 等
局部优化完写 benchmark 器用检察优化后果
全体优化完回到要领一,重新进行 压测+pprof 看后果,看 95 分位耗时能否得志条目(如果无法得志需求,那就换存储吧~。
有传言称,在某一场重要的博彩赛事中,皇冠体育的赔率和实际结果出现了巨大的偏差。这个消息立刻引起了业内人士的关注和调查,而皇冠体育方面则一直没有对此作出任何回应。
另外推选一个可以的库,这是 Golang 布说念师 Dave Cheney 搞的用来作念性能调优的库,使用起来极端便捷:https://github.com/pkg/profile,可以看 pprof和 trace 信息。有趣味读者可以了解一下。
