今天又带来一次性能优化的分享,这是我刚进公司时接手的祖传(坏笑)项目,这个项目在我的文章中屡次被提及,我在它上面做了很多的性能优化,比如[《记一次提升18倍的性能优化》]这篇文章,比较偏向某个细节的优化,本文更偏向宏观上的性能优化,可以说是个老演员了。
为了新朋友能快速进入场景,再描述一遍这个项目的背景,这个项目是一个自研的Dubbo注册中心,上一张架构图
这里的Registry
就是今天的主角,熟悉Dubbo的朋友可以把它当做是一个zookeeper,不熟悉的朋友可以就把它当做是一个Web应用,提供了注册、注销、订阅接口,虽然它是用Go写的,但本文和Go本身关系不大,也会用一些伪代码来示意,所以也可以放心大胆地看下去。
在做性能优化之前,我们得回答几个问题,性能优化带来的收益是什么?为什么一定要做优化性能?不优化行不行?
性能优化无非有两个目的:
如果只是为了降低成本,最好做之前估算一下大概能降低多少成本,如果吭哧吭哧干了大半个月,结果只省下了一丁点的资源,那是得不偿失的。
回到这个注册中心,为什么要做性能优化呢?
Dubbo应用启动时,会向注册中心发起注册,如果注册失败,则会阻塞应用的启动。
起初这个项目问题并不大,因为接入的应用并不多,而当我接手项目时,接入的应用越来越多。
话分两头,另一边集团也在逐渐使用容器替代虚拟机和物理机,在高峰期会用扩容的方式来抗住流量高峰,快速扩容就要求服务能在短时间内大量启动,无疑对注册中心是一个大的考验。
而导致这次优化的直接导火索是集团内的一次演练,他们发现一个配置中心的启动依赖,性能达不到标准而导致扩容失败,于是复盘下来,所有的启动依赖必须达到一定的性能要求,而这个标准被定为1000qps。
于是就有了本文。
如果不能度量,就没法优化。
首先是把几个核心接口加上metric,主要是请求量、耗时(p99 / p95 / p90)、错误请求量,无论是哪个项目,这点算是基本的了,如果没加,得好好反思了。
其次对项目进行一次压测,不知道现在的性能,后面的优化也无法证明其效果了。
以注册接口为例,当时注册的性能大概是40qps,记住这个值,看我们是如何一步一步达到1000qps的。
压测成功的请求标准是:p99耗时在1秒以内,且无报错。
性能优化的最关键之处在于找到瓶颈在哪,否则就是无头苍蝇,到处瞎碰。
注册接口到底干了什么呢?我这里画个简图
从这个流程图就能看出来,瓶颈大概率在锁上,这是个悲观锁,而且粒度是App,把整个流程锁住,同一时刻相同应用的请只允许一个通过,可想而知性能有多差。
至于MySQL如何实现一个悲观锁,我相信你会的,所以我就不展开。
为了证明猜想,我用了一个非常笨但很有效的方法,在每一个关键节点执行之后,记录下耗时,最后打印到日志里,这样就能一眼看出到底哪里慢,果然最慢的就是加锁。
在优化锁之前,我们先搞清楚为什么要加锁,在我反复测试,读代码,看文档之后,发现事情其实很简单,这个锁是为了防止App、Cluster、Endpoint重复写入。
为什么防止重复写入要这么折腾呢?一个数据库的唯一索引不就搞定了?这无法考证,但现状就是这样,如何破解呢?
这个其实是可以的,原因在于客户端具有重试机制,如果并发冲突了,则发起重试,我们堵这个概率很小。
上面两条优化下来只解决了部分问题,还有的表实在无法添加唯一索引,比如这里App、Cluster由于一些特殊原因无法添加唯一索引,他们发生冲突的概率很高,同一个集群发布时,很可能是100台机器同时拉起,只有一台成功,剩余99台在创建App或者Cluster时被锁挡住了,发起重试,重试又可能冲突,大家都陷入了无限重试,最终超时,我们的服务也可能被重试流量打垮。
这该怎么办?这时我想起了刚学Java时练习写单例模式中,有个叫「双重校验锁」的东西,我们看代码
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
private static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
再结合我们的场景,App和Cluster只在创建时需要保证唯一性,后续都是先查询,如果存在就不需要再执行插入,我们写出伪代码
app = DB.get("app_name")
if app == null {
redis.lock()
app = DB.get("app_name")
if app == null {
app = DB.instert("app_name")
}
redis.unlock()
}
是不是和双重校验锁一模一样?为什么这样会性能更高呢?因为App和Cluster的特性是只在第一次时插入,真正需要锁住的概率很小,就拿扩容的场景来说,必然不会走到锁的逻辑,只有应用初次创建时才会真正被Lock。
性能优化有一点是很重要的,就是我们要去优化执行频率非常高的场景,这样收益才高,如果执行的频率很低,那么我们是可以选择性放弃的。
经过这轮优化,注册的性能从40qps提升到了430qps,10倍的提升。
经过上一轮的优化,我们还有个结论能得出来,一个应用或集群的基本信息基本不会变化,于是我在想,是否可以读取这些信息时直接走Redis缓存呢?
于是将信息基本不变的对象加上了缓存,再测试,发现qps从430提升到了440,提升不是很多,但苍蝇再小,好歹是块肉。
上一轮的优化效果不理想,但在压测时注意到了一个问题,我发现Registry的CPU降低的很厉害,感觉瓶颈从锁转移到了CPU。说到CPU,这好办啊,上火焰图,Go自带的pprof就能干。
可以清楚地看到是ParseUrl占用了太多的CPU,这里简单科普下,Dubbo传参很多是靠URL传参的,注册中心拿到Dubbo的URL,需要去解析其中的参数,比如ip、port等信息就存在于URL之中。
一开始拿到这个CPU profile的结果是有点难受的,因为ParseUrl是封装的标准包里的URL解析方法,想要写一个比它还高效的,基本可以劝退。
但还是顺腾摸瓜,看看哪里调用了这个方法。不看不知道,一看吓一跳,原来一个请求里的URL,会执行过程中多次解析URL,为啥代码会这么写?可能是其中逻辑太复杂,一层一层的嵌套,但各个方法之间的传参又不统一,所以带来了这么糟糕的写法,
这种情况怎么办呢?
我选择了第二种方式,因为这样对代码的改动小,毕竟我刚接手这么庞大、混乱的代码,最好能不动就不动,能少动就少动。
而且这种方式我很熟悉,在Dubbo的源码中就有这样的处理,Dubbo在反序列化时,如果是重复的对象,则直接走缓存而不是再去构造一遍,代码位于org.apache.dubbo.common.utils.PojoUtils#generalize
截取一点感受下
private static Object generalize(Object pojo, Map<Object, Object> history) {
...
Object o = history.get(pojo);
if (o != null) {
return o;
}
history.put(pojo, pojo);
...
}
根据这个思路,把ParseUrl改成带cache的模式
func parseUrl(url, cache) {
if cache.get(url) != null {
return cache.get(url)
}
u = parseUrl0(url)
cache.put(url, u)
return u
}
因为是会话级别的缓存,所以每个会话会new一个cache,这样能保证一个会话中对相同的url只解析一次。
可以看下这次优化的成果,qps直接到1100,达到目标~
可能有人看完就要喷了,这哪是性能优化?这分明是填坑!对,你说的没错,只不过这坑是别人挖的。
本文就以一种最小的代价来搞定对祖传代码的性能优化,当然并不是鼓励大家都去取巧,这项目我也正在重构,只是每个阶段都有不同的解法,比如老板要求你2周内接手一个新项目,并完成性能优化上线,重构是不可能的。
希望通过本文你能学到一些性能优化的基本知识,从为什么要做的拷问出发,建立度量体系,找出瓶颈,一步一步进行优化,根据数据反馈及时调整优化方向。
今天到此为止,我们下期再见。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/1kOq73_CSZvBXWEeh1DpXg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。