背景
最近负责的一个自研的 Dubbo 注册中心经常收到 CPU 使用率的告警,于是进行了一波优化,效果还不错,于是打算分享下思考、优化过程,希望对大家有一些帮助。
自研 Dubbo 注册中心是个什么东西,我画个简图大家稍微感受一下就好,看不懂也没关系,不影响后续的理解。

Consumer 和 Provider 的服务发现请求(注册、注销、订阅)都发给 Agent,由它全权代理 Registry 和 Agent 保持 Grpc 长链接,长链接的目的主要是 Provider 方有变更时,能及时推送给相应的 Consumer。为了保证数据的正确性,做了推拉结合的机制,Agent 会每隔一段时间去 Registry 拉取订阅的服务列表 Agent 和业务服务部署在同一台机器上,类似 Service Mesh 的思路,尽量减少对业务的入侵,这样就能快速的迭代了
回到今天的重点,这个注册中心最近 CPU 使用率长期处于中高水位,偶尔有应用发布,推送量大时,CPU 甚至会被打满。
以前没感觉到,是因为接入的应用不多,最近几个月应用越接越多,慢慢就达到了告警阈值。
寻找优化点
由于这项目是 Go 写的(不懂 Go 的朋友也没关系,本文重点在算法的优化,不在工具的使用上), 找到哪里耗 CPU 还是挺简单的:打开 pprof 即可,去线上采集一段时间即可。
具体怎么操作可以参考我之前的这篇文章,今天文章中用到的知识和工具,这篇文章都能找到。

CPU profile 截了部分图,其他的不太重要,可以看到消耗 CPU 多的是 AssembleCategoryProviders
方法,与其直接关联的是
2个 redis 相关的方法 1个叫 assembleUrlWeight
的方法
稍微解释下,AssembleCategoryProviders 方法是构造返回 Dubbo provider 的 url,由于会在返回 url 时对其做一些处理(比如调整权重等),会涉及到对这个 Dubbo url 的解析。又由于推拉结合的模式,线上服务使用方越多,这个处理的 QPS 就越大,所以它占用了大部分 CPU 一点也不奇怪。
这两个 redis 操作可能是序列化占用了 CPU,更大头在 assembleUrlWeight,有点琢磨不透。
接下来我们就分析下 assembleUrlWeight 如何优化,因为他占用 CPU 最多,优化效果肯定最好。
下面是 assembleUrlWeight 的伪代码:
1 | <span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/kMaz9nc8bgJhicT5MmKPhFp3L6wBYNY1jydRicsS18KEQdJDNP7Wa2D4mukpVYeJssiaLRiatNZUkKJYKvsZYEqOkOV3IxibsbcAW/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"/><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">func</span> <span style="color: #61aeee;line-height: 26px;">AssembleUrlWeight</span><span style="line-height: 26px;">(rawurl <span style="color: #c678dd;line-height: 26px;">string</span>, lidcWeight <span style="color: #c678dd;line-height: 26px;">int</span>)</span> <span style="color: #61aeee;line-height: 26px;">string</span></span> {<br/> u, err := url.Parse(rawurl)<br/> <span style="color: #c678dd;line-height: 26px;">if</span> err != <span style="color: #56b6c2;line-height: 26px;">nil</span> {<br/> <span style="color: #c678dd;line-height: 26px;">return</span> rawurl<br/> }<br/><br/> values, err := url.ParseQuery(u.RawQuery)<br/> <span style="color: #c678dd;line-height: 26px;">if</span> err != <span style="color: #56b6c2;line-height: 26px;">nil</span> {<br/> <span style="color: #c678dd;line-height: 26px;">return</span> rawurl<br/> }<br/><br/> <span style="color: #c678dd;line-height: 26px;">if</span> values.Get(<span style="color: #98c379;line-height: 26px;">"lidc_weight"</span>) != <span style="color: #98c379;line-height: 26px;">""</span> {<br/> <span style="color: #c678dd;line-height: 26px;">return</span> rawurl<br/> }<br/><br/> endpointWeight := <span style="color: #d19a66;line-height: 26px;">100</span><br/> <span style="color: #c678dd;line-height: 26px;">if</span> values.Get(<span style="color: #98c379;line-height: 26px;">"weight"</span>) != <span style="color: #98c379;line-height: 26px;">""</span> {<br/> endpointWeight, err = strconv.Atoi(values.Get(<span style="color: #98c379;line-height: 26px;">"weight"</span>))<br/> <span style="color: #c678dd;line-height: 26px;">if</span> err != <span style="color: #56b6c2;line-height: 26px;">nil</span> {<br/> endpointWeight = <span style="color: #d19a66;line-height: 26px;">100</span><br/> }<br/> }<br/><br/> values.Set(<span style="color: #98c379;line-height: 26px;">"weight"</span>, strconv.Itoa(lidcWeight*endpointWeight))<br/><br/> u.RawQuery = values.Encode()<br/> <span style="color: #c678dd;line-height: 26px;">return</span> u.String()<br/>}<br/> |
传参 rawurl 是 Dubbo provider 的url,lidcWeight 是机房权重。根据配置的机房权重,将 url 中的 weight 进行重新计算,实现多机房流量按权重的分配。
这个过程涉及到 url 参数的解析,再进行 weight 的计算,最后再还原为一个 url
Dubbo 的 url 结构和普通 url 结构一致,其特点是参数可能比较多,没有 #
后面的片段部分。

CPU 主要就消耗在这两次解析和最后的还原中,我们看这两次解析的目的就是为了拿到 url 中的 lidc_weight
和 weight
参数。
url.Parse 和 url.ParseQuery 都是 Go 官方提供的库,各个语言也都有实现,其核心是解析 url 为一个对象,方便地获取 url 的各个部分。
如果了解信息熵这个概念,其实你就大概知道这里面一定是可以优化的。Shannon(香农)
借鉴了热力学的概念,把信息中排除了冗余后的平均信息量称为信息熵
。

url.Parse 和 url.ParseQuery 在这个场景下解析肯定存在冗余,冗余意味着 CPU 在做多余的事情。
因为一个 Dubbo url 参数通常是很多的,我们只需要拿这两个参数,而 url.Parse 解析了所有的参数。
举个例子,给定一个数组,求其中的最大值,如果先对数组进行排序,再取最大值显然是存在冗余操作的。
排序后的数组不仅能取最大值,还能取第二大值、第三大值...最小值,信息存在冗余了,所以先排序肯定不是求最大值的最优解。
优化
优化获取 url 参数性能
第一想法是,不要解析全部 url,只拿相应的参数,这就很像我们写的算法题,比如获取 weight 参数,它只可能是这两种情况(不存在 #,所以简单很多):
dubbo://127.0.0.1:20880/org.newboo.basic.MyDemoService?weight=100&... dubbo://127.0.0.1:20880/org.newboo.basic.MyDemoService?xx=yy&weight=100&...
要么是 &weight=
,要么是 ?weight=
,结束要么是&
,要么直接到字符串尾,代码就很好写了,先手写个解析参数的算法:
1 | <span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/kMaz9nc8bgJhicT5MmKPhFp3L6wBYNY1jydRicsS18KEQdJDNP7Wa2D4mukpVYeJssiaLRiatNZUkKJYKvsZYEqOkOV3IxibsbcAW/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"/><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">func</span> <span style="color: #61aeee;line-height: 26px;">GetUrlQueryParam</span><span style="line-height: 26px;">(u <span style="color: #c678dd;line-height: 26px;">string</span>, key <span style="color: #c678dd;line-height: 26px;">string</span>)</span> <span style="line-height: 26px;">(<span style="color: #c678dd;line-height: 26px;">string</span>, error)</span></span> {<br/> sb := strings.Builder{}<br/> sb.WriteString(key)<br/> sb.WriteString(<span style="color: #98c379;line-height: 26px;">"="</span>)<br/> index := strings.Index(u, sb.String())<br/> <span style="color: #c678dd;line-height: 26px;">if</span> (index == <span style="color: #d19a66;line-height: 26px;">-1</span>) || (index+<span style="color: #e6c07b;line-height: 26px;">len</span>(key)+<span style="color: #d19a66;line-height: 26px;">1</span> > <span style="color: #e6c07b;line-height: 26px;">len</span>(u)) {<br/> <span style="color: #c678dd;line-height: 26px;">return</span> <span style="color: #98c379;line-height: 26px;">""</span>, UrlParamNotExist<br/> }<br/><br/> <span style="color: #c678dd;line-height: 26px;">var</span> value = strings.Builder{}<br/> <span style="color: #c678dd;line-height: 26px;">for</span> i := index + <span style="color: #e6c07b;line-height: 26px;">len</span>(key) + <span style="color: #d19a66;line-height: 26px;">1</span>; i < <span style="color: #e6c07b;line-height: 26px;">len</span>(u); i++ {<br/> <span style="color: #c678dd;line-height: 26px;">if</span> i+<span style="color: #d19a66;line-height: 26px;">1</span> > <span style="color: #e6c07b;line-height: 26px;">len</span>(u) {<br/> <span style="color: #c678dd;line-height: 26px;">break</span><br/> }<br/> <span style="color: #c678dd;line-height: 26px;">if</span> u[i:i+<span style="color: #d19a66;line-height: 26px;">1</span>] == <span style="color: #98c379;line-height: 26px;">"&"</span> {<br/> <span style="color: #c678dd;line-height: 26px;">break</span><br/> }<br/> value.WriteString(u[i : i+<span style="color: #d19a66;line-height: 26px;">1</span>])<br/> }<br/> <span style="color: #c678dd;line-height: 26px;">return</span> value.String(), <span style="color: #56b6c2;line-height: 26px;">nil</span><br/>}<br/> |
原先获取参数的方法可以摘出来:
1 | <span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/kMaz9nc8bgJhicT5MmKPhFp3L6wBYNY1jydRicsS18KEQdJDNP7Wa2D4mukpVYeJssiaLRiatNZUkKJYKvsZYEqOkOV3IxibsbcAW/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"/><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">func</span> <span style="color: #61aeee;line-height: 26px;">getParamByUrlParse</span><span style="line-height: 26px;">(ur <span style="color: #c678dd;line-height: 26px;">string</span>, key <span style="color: #c678dd;line-height: 26px;">string</span>)</span> <span style="color: #61aeee;line-height: 26px;">string</span></span> {<br/> u, err := url.Parse(ur)<br/> <span style="color: #c678dd;line-height: 26px;">if</span> err != <span style="color: #56b6c2;line-height: 26px;">nil</span> {<br/> <span style="color: #c678dd;line-height: 26px;">return</span> <span style="color: #98c379;line-height: 26px;">""</span><br/> }<br/><br/> values, err := url.ParseQuery(u.RawQuery)<br/> <span style="color: #c678dd;line-height: 26px;">if</span> err != <span style="color: #56b6c2;line-height: 26px;">nil</span> {<br/> <span style="color: #c678dd;line-height: 26px;">return</span> <span style="color: #98c379;line-height: 26px;">""</span><br/> }<br/><br/> <span style="color: #c678dd;line-height: 26px;">return</span> values.Get(key)<br/>}<br/> |
先对这两个函数进行 benchmark:
1 | <span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/kMaz9nc8bgJhicT5MmKPhFp3L6wBYNY1jydRicsS18KEQdJDNP7Wa2D4mukpVYeJssiaLRiatNZUkKJYKvsZYEqOkOV3IxibsbcAW/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"/><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">func</span> <span style="color: #61aeee;line-height: 26px;">BenchmarkGetQueryParam</span><span style="line-height: 26px;">(b *testing.B)</span></span> {<br/> <span style="color: #c678dd;line-height: 26px;">for</span> i := <span style="color: #d19a66;line-height: 26px;">0</span>; i < b.N; i++ {<br/> getParamByUrlParse(u, <span style="color: #98c379;line-height: 26px;">"anyhost"</span>)<br/> getParamByUrlParse(u, <span style="color: #98c379;line-height: 26px;">"version"</span>)<br/> getParamByUrlParse(u, <span style="color: #98c379;line-height: 26px;">"not_exist"</span>)<br/> }<br/>}<br/><br/><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">func</span> <span style="color: #61aeee;line-height: 26px;">BenchmarkGetQueryParamNew</span><span style="line-height: 26px;">(b *testing.B)</span></span> {<br/> <span style="color: #c678dd;line-height: 26px;">for</span> i := <span style="color: #d19a66;line-height: 26px;">0</span>; i < b.N; i++ {<br/> GetUrlQueryParam(u, <span style="color: #98c379;line-height: 26px;">"anyhost"</span>)<br/> GetUrlQueryParam(u, <span style="color: #98c379;line-height: 26px;">"version"</span>)<br/> GetUrlQueryParam(u, <span style="color: #98c379;line-height: 26px;">"not_exist"</span>)<br/> }<br/>}<br/> |
Benchmark 结果如下:
1 | <span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/kMaz9nc8bgJhicT5MmKPhFp3L6wBYNY1jydRicsS18KEQdJDNP7Wa2D4mukpVYeJssiaLRiatNZUkKJYKvsZYEqOkOV3IxibsbcAW/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"/><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">BenchmarkGetQueryParam-4 103412 9708 ns/op<br/>BenchmarkGetQueryParam-4 111794 9685 ns/op<br/>BenchmarkGetQueryParam-4 115699 9818 ns/op<br/>BenchmarkGetQueryParamNew-4 2961254 409 ns/op<br/>BenchmarkGetQueryParamNew-4 2944274 406 ns/op<br/>BenchmarkGetQueryParamNew-4 2895690 405 ns/op<br/> |
可以看到性能大概提升了20多倍
新写的这个方法,有两个小细节,第一是返回值中区分了参数是否存在,这个后面会用到;第二是字符串的操作用到了 strings.Builder
,这也是实际测试的结果,使用 +
或者 fmt.Springf
性能都没这个好,感兴趣可以测试下看看。
优化 url 写入参数性能
计算出 weight 后再把 weight 写入 url 中,这里直接给出优化后的代码:
1 | <span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/kMaz9nc8bgJhicT5MmKPhFp3L6wBYNY1jydRicsS18KEQdJDNP7Wa2D4mukpVYeJssiaLRiatNZUkKJYKvsZYEqOkOV3IxibsbcAW/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"/><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">func</span> <span style="color: #61aeee;line-height: 26px;">AssembleUrlWeightNew</span><span style="line-height: 26px;">(rawurl <span style="color: #c678dd;line-height: 26px;">string</span>, lidcWeight <span style="color: #c678dd;line-height: 26px;">int</span>)</span> <span style="color: #61aeee;line-height: 26px;">string</span></span> {<br/> <span style="color: #c678dd;line-height: 26px;">if</span> lidcWeight == <span style="color: #d19a66;line-height: 26px;">1</span> {<br/> <span style="color: #c678dd;line-height: 26px;">return</span> rawurl<br/> }<br/><br/> lidcWeightStr, err1 := GetUrlQueryParam(rawurl, <span style="color: #98c379;line-height: 26px;">"lidc_weight"</span>)<br/> <span style="color: #c678dd;line-height: 26px;">if</span> err1 == <span style="color: #56b6c2;line-height: 26px;">nil</span> && lidcWeightStr != <span style="color: #98c379;line-height: 26px;">""</span> {<br/> <span style="color: #c678dd;line-height: 26px;">return</span> rawurl<br/> }<br/><br/> <span style="color: #c678dd;line-height: 26px;">var</span> err error<br/> endpointWeight := <span style="color: #d19a66;line-height: 26px;">100</span><br/> weightStr, err2 := GetUrlQueryParam(rawurl, <span style="color: #98c379;line-height: 26px;">"weight"</span>)<br/> <span style="color: #c678dd;line-height: 26px;">if</span> weightStr != <span style="color: #98c379;line-height: 26px;">""</span> {<br/> endpointWeight, err = strconv.Atoi(weightStr)<br/> <span style="color: #c678dd;line-height: 26px;">if</span> err != <span style="color: #56b6c2;line-height: 26px;">nil</span> {<br/> endpointWeight = <span style="color: #d19a66;line-height: 26px;">100</span><br/> }<br/> }<br/><br/> <span style="color: #c678dd;line-height: 26px;">if</span> err2 != <span style="color: #56b6c2;line-height: 26px;">nil</span> { <span style="color: #5c6370;font-style: italic;line-height: 26px;">// url中不存在weight</span><br/> finUrl := strings.Builder{}<br/> finUrl.WriteString(rawurl)<br/> <span style="color: #c678dd;line-height: 26px;">if</span> strings.Contains(rawurl, <span style="color: #98c379;line-height: 26px;">"?"</span>) {<br/> finUrl.WriteString(<span style="color: #98c379;line-height: 26px;">"&weight="</span>)<br/> finUrl.WriteString(strconv.Itoa(lidcWeight * endpointWeight))<br/> <span style="color: #c678dd;line-height: 26px;">return</span> finUrl.String()<br/> } <span style="color: #c678dd;line-height: 26px;">else</span> {<br/> finUrl.WriteString(<span style="color: #98c379;line-height: 26px;">"?weight="</span>)<br/> finUrl.WriteString(strconv.Itoa(lidcWeight * endpointWeight))<br/> <span style="color: #c678dd;line-height: 26px;">return</span> finUrl.String()<br/> }<br/> } <span style="color: #c678dd;line-height: 26px;">else</span> { <span style="color: #5c6370;font-style: italic;line-height: 26px;">// url中存在weight</span><br/> oldWeightStr := strings.Builder{}<br/> oldWeightStr.WriteString(<span style="color: #98c379;line-height: 26px;">"weight="</span>)<br/> oldWeightStr.WriteString(weightStr)<br/><br/> newWeightStr := strings.Builder{}<br/> newWeightStr.WriteString(<span style="color: #98c379;line-height: 26px;">"weight="</span>)<br/> newWeightStr.WriteString(strconv.Itoa(lidcWeight * endpointWeight))<br/> <span style="color: #c678dd;line-height: 26px;">return</span> strings.ReplaceAll(rawurl, oldWeightStr.String(), newWeightStr.String())<br/> }<br/>}<br/> |
主要就是分为 url 中是否存在 weight 两种情况来讨论:
url 本身不存在 weight 参数,则直接在 url 后拼接一个 weight 参数,当然要注意是否存在 ?
url 本身存在 weight 参数,则直接进行字符串替换
细心的你肯定又发现了,当 lidcWeight = 1
时,直接返回,因为 lidcWeight = 1
时,后面的计算其实都不起作用(Dubbo 权重默认为100),索性别操作,省点 CPU。
全部优化完,总体做一下 benchmark:
1 | <span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/kMaz9nc8bgJhicT5MmKPhFp3L6wBYNY1jydRicsS18KEQdJDNP7Wa2D4mukpVYeJssiaLRiatNZUkKJYKvsZYEqOkOV3IxibsbcAW/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"/><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">func</span> <span style="color: #61aeee;line-height: 26px;">BenchmarkAssembleUrlWeight</span><span style="line-height: 26px;">(b *testing.B)</span></span> {<br/> <span style="color: #c678dd;line-height: 26px;">for</span> i := <span style="color: #d19a66;line-height: 26px;">0</span>; i < b.N; i++ {<br/> <span style="color: #c678dd;line-height: 26px;">for</span> _, ut := <span style="color: #c678dd;line-height: 26px;">range</span> []<span style="color: #c678dd;line-height: 26px;">string</span>{u, u1, u2, u3} {<br/> AssembleUrlWeight(ut, <span style="color: #d19a66;line-height: 26px;">60</span>)<br/> }<br/> }<br/>}<br/><br/><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">func</span> <span style="color: #61aeee;line-height: 26px;">BenchmarkAssembleUrlWeightNew</span><span style="line-height: 26px;">(b *testing.B)</span></span> {<br/> <span style="color: #c678dd;line-height: 26px;">for</span> i := <span style="color: #d19a66;line-height: 26px;">0</span>; i < b.N; i++ {<br/> <span style="color: #c678dd;line-height: 26px;">for</span> _, ut := <span style="color: #c678dd;line-height: 26px;">range</span> []<span style="color: #c678dd;line-height: 26px;">string</span>{u, u1, u2, u3} {<br/> AssembleUrlWeightNew(ut, <span style="color: #d19a66;line-height: 26px;">60</span>)<br/> }<br/> }<br/>}<br/> |
结果如下:
1 | <span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/kMaz9nc8bgJhicT5MmKPhFp3L6wBYNY1jydRicsS18KEQdJDNP7Wa2D4mukpVYeJssiaLRiatNZUkKJYKvsZYEqOkOV3IxibsbcAW/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"/><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">BenchmarkAssembleUrlWeight-4 34275 33289 ns/op<br/>BenchmarkAssembleUrlWeight-4 36646 32432 ns/op<br/>BenchmarkAssembleUrlWeight-4 36702 32740 ns/op<br/>BenchmarkAssembleUrlWeightNew-4 573684 1851 ns/op<br/>BenchmarkAssembleUrlWeightNew-4 646952 1832 ns/op<br/>BenchmarkAssembleUrlWeightNew-4 563392 1896 ns/op<br/> |
大概提升 18 倍性能,而且这可能还是比较差的情况,如果传入 lidcWeight = 1,效果更好。
效果
优化完,对改动方法写了相应的单元测试,确认没问题后,上线进行观察,CPU Idle(空闲率) 提升了10%以上

最后
其实本文展示的是一个 Go 程序非常常规的性能优化,也是相对来说比较简单,看完后,大家可能还有疑问:
为什么要在推送和拉取的时候去解析 url 呢?不能事先算好存起来吗? 为什么只优化了这点,其他的点是否也可以优化呢?
针对第一个问题,其实这是个历史问题,当你接手系统时他就是这样,如果程序出问题,你去改整个机制,可能周期比较长,而且容易出问题

第二个问题,其实刚也顺带回答了,这样优化,改动最小,收益最大,别的点没这么好改,短期来说,拿收益最重要。当然我们后续也打算对这个系统进行重构,但重构之前,这样优化,足以解决问题。
搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。也欢迎加我个人微信
MrRoshi
,围观朋友圈。
- END -