找回密码
 会员注册
查看: 17|回复: 0

贝壳找房小程序从PHP到Golang的跃迁之路

[复制链接]

3

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2024-10-10 22:19:54 | 显示全部楼层 |阅读模式
贝壳找房小程序从PHP到Golang的跃迁之路 贝壳找房小程序从PHP到Golang的跃迁之路 吴强@贝壳找房 贝壳产品技术 贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容 2020年07月16日 22:32 1. 前言1.1 PHP是最好的语言PHP确实有非常强大的优势。对于中小型Web服务,业务具有高度不确定性,产品迭代速度是第一目标,非常适合使用PHP作为创业启动语言。1.2 使用PHP遇到的问题小程序前台项目的特点是:IO密集型服务小程序目前依赖众多下层服务,一个普通的小区接口依赖的下层API达到11个之多。在阻塞IO的模式下,所有等待延迟串行叠加,非常容易给前端造成比较高的后台等待,影响用户体验。特别是遇到个别服务的部分请求出现透传DB,SQL效率不高时,就更会雪上加霜,499突增,威胁到服务稳定性。这样一来,对于一个PHP的N层调用场景来说,任何一层服务的失败都会直接影响终端获取数据的成功率,当每层及时成功返回数据的概率为P,则它的最终成功率就会变成:P=(1-P1)*(1-P2)*(1-P3)*……*(1-PN)于是,当前服务层要想优化,比较好的选择是:增加cache。好处是减少向后请求,透传db的情况也会顺应得到一定的改善,耗时也会因为跳过了逻辑计算而得到收敛。其后果是牺牲了实时性,换取性能的提高。然而,这可能导致房屋标价,出现短时显示不稳定;租价相差几百,卖价相差几千,都会让客户抓狂。性能提高的程度要看缓存命中率;过期时间长短需要平衡,过久则时效性差,过短则命中率不高。核心城市的购房者和卖房者相对密度大一些,而二三线城市就没那么好了。多层cache情况下,每层设置cache过期时间为T,当有变更发生需要最终生效时,其最糟糕生效时间是:T=T1+T2+T3+……Tn综上,伴随着调用模块数量的增加,延迟超标的接口会越来越多;增加cache之后,信息展示的不确定性会增多。左右为难,然如何破局?1.3 选择Golang的原因了解过Golang的同学,以下部分特性可能比较吸引你:Goroutine协程多路复用Channel通道小程序团队主要考虑以下几个点:协程:将多个模块的拉取和格式化作为独立单元,进行单元级别并行异步IO:节省CPU,提高服务并发能力Channel通道:可以将一些轻量的异步动作用协程+channel实现,简化架构强大的标准库:可以实现和PHP等价的逻辑稳定性:常驻运行也依然稳定丰富的工具:解决单元测试、代码风险检查部署运行环境:贝壳已经支持线上运行和托管Golang服务最终的收益将会体现为:降低当前用户体验延迟遏制接口延迟持续恶化提高服务的稳定性有的人说,为什么不使用swoole呢?从BAT的应用情况来看,swoole并未受到追捧;且swoole也可能像hhvm一样,只是过渡期选择。有的人说,guzzle不香吗?跟协程能做的事情来讲,我们更看重模块级别的并行,而不仅仅是IO。1.4 Golang为什么快按照Golang布道师Dave Cheney在2014年Gocon大会上的分享来看,其中5点值得注意:变量的处理和存储一个int32的变量只占用4字节,但是在python中需要24字节,在Java中需要16-24字节。内存占用少,使得CPU的cache效率更高。数组变量紧凑的内存结构,避免了无谓的指针跳转。函数内联虽然增加了包大小,但是减少了函数调用的开销,特别是很多短小的函数。垃圾回收通过逃逸分析方法,使得更多的变量申请空间可以在栈上得到分配,减轻了堆上资源垃圾回收的压力。一般程序员不需要关心是在栈上还是堆上。协程在利用线程对进程来优化的思路基础上,进行了延伸。协程非常轻量,再加上协程切换一般发生在阻塞、系统调用、垃圾回收等时机,从而减少了等待。每个Go进程只需要少量系统线程,Go的runtime会将协程放到空闲操作系统线程上运行。Goroutine的栈管理通过去掉guard page,在函数调用时增加栈空间检查的机制,并在必要时自动分配更多空间的方式,使得初始的空间可以很小,goroutine可以很轻量。而函数调用带来的频繁扩缩容,利用连续栈分配更大空间而得到解决。2. 实践2.1 目标当时域名下114个API,大部分的延迟都不高,在调用量比较大,延迟比较突出的二手接口中,我们锁定了部分接口作为改造对象。目标二手业务8个接口投入人力3人时间成本6周编码量约2w行2.2 实施步骤2.2.1 业务框架没有业务框架,直接进行业务重构就是耍流氓。但19年,公司还没有出品Golang业务框架,我们得自己搞定。先向杨宇(比克)学习了沙场项目,获得了在贝壳服务器安装、运行、部署Golang服务的基本经验。宇哥多次指导,深表感谢。使用涉及的工具有:go mod:搞定代码包管理go proxy:解决拉取内外仓库代码的差异问题gin:搭建基础Golang web服务的简洁框架2.2.2 跑通第一个接口万丈高楼从地起,我们进行了以下关键实践:协程并行封装:避免每个人单独封装,单独调试的麻烦。对接部署环境:封装build逻辑,实现打包编译、环境变量管理。超时管理:进行区分环境的不同超时设置,包括框架超时和http调用超时;将connect超时和read超时区分开。签名验签:解决前后端验签实现,解决http调用验签实现。日志分级:完成warning、fatal、panic、access等日志的分封。监控告警:利用日志收集到fast,通过看板和日志报警管理稳定性,最为重要的是内存监控和协程数监控。单元测试:利用go test实现基础代码的单测,避免偶现bug在未来长时间内不定时跑出来,并产生灵异现象。local cache:在全局下发的一些公共数据上,可以达到比较好的加速效果。环境区分:通过不同环境加载配置,实现环境隔离。配置治理:将环境变量、命令行参数、配置文件统一规范为配置文件,保持单一入口和拉起程序的简化。打包机联调:将环境差异干预配置到打包机,避免同一个文件在git仓库管理多个版本。Diff平台:通过diff工具进行接口字段对比,避免逻辑丢失和差异。自动部署:通过CI平台的Jenkins来实现代码到测试环境的自动部署,快速提高bug的解决和验测节奏。热编译:通过对开源项目gowatch的二次开发,实现了代码自动编译和附带工具运行,避免写一行编一次。目录管理:主要分离了base、data、model、controller几层,最大限度保持大家写PHP的习惯迁移。服务保活:和OP一起适配systemd服务,在服务宕机时可以秒起进程。在我们完成第一个接口nearby的逻辑编码的过程中,以上80%的特性也一起附带实现了,整个过程为10个整天(对语言不熟悉的话,需要适当添加时间;剩下20%是陆续边改造边实现的)。另外7个接口的逻辑重构工作,就可以据此为模版展开了。但此时接口还没有增加redis缓存,新接口延迟比老PHP接口明显高出一截。2.2.3 优化接口加缓存可能是接口优化最直接有效的手段了,而且成本一般都不是问题;但请神容易送神难。丢失的实时性很难找回,带来的问题很难定位。数据显示错误时,是哪一层缓存错了,没有谁能说得清。所以我们打算在不加缓存的情况下,去优化延迟。Golang最大的特性是协程并发,所以我们迫不及待开始用协程来试试效果。在nearby接口中,我们分析出了2层逻辑可并行的场景:getErshouList、getNewfangList、getZufangList实现并行SearchResblockHouseSell和GetResblockSell实现并行这样一来,解决的延迟就轻松超过了100ms。在后来的xiaoqu接口中,有更加优异的表现,整体实现了4层逻辑并行。并行代码难写吗?并非如此。封装一层是降低代码复杂度的有效武器;如果不是,就再封装一层。base包下实现一个GoWait方法,并行调用就会非常优雅:关于缓存,我们斟酌再三,最后还是在房源列表增加了cache,理由为:假如,用户在列表看到10条房源信息,当他点击第一条,进入页能看到最实时的信息,时效性是以页为准,所以体验没有问题;当用户返回列表点击第二条时,列表未刷新,则列表展示的第二条可能已经过时;如果第二条已经被删掉,点击会出现404;删除的那条,用户不一定会点击;此时我们如果更新列表,则可能发生位置或者或多或少的变化,用户感受到了不稳定;所以最终,我们在房源列表这里对列表房源信息增加了redis的cache,保证了底层页的时效性和列表页的稳定感。2.2.4 性能收集一个事物,如果你不能准确的观察它,那你就很难控制它。Golang服务容易暴露出线程安全、panic、泄露、空指针引用等问题,最为常见的就是协程泄露。监控协程数,是区分协程泄露还是句柄泄露的关键举措。其次是资源泄露,如果服务频繁宕掉,极可能是panic未捕获,或者内存过高引起的oom。panic的问题可以通过添加默认recover的方式解决。内存过高被杀会影响服务稳定性,好在内存不是一分钟就涨上去的,我们可以在达到一定限度时就开始介入,避免宕机才后知后觉。幸运的是,Golang的runtime可以轻松实现协程数和内存(并非实际内存)的获取。利用fast天眼平台的看板视图,很容易就做到服务的监控走势图:如果是每天周期性波动,可以暂时松一口气。部分边界问题可能后期暴露,从而打破平静,建议增加阈值告警,避免上升为故障才后知后觉。这方面已经有不少先例了,痛过的人自然明白。2.2.5 配套工具为了更好的保证golang服务的质量,尽早发现问题,我们还引入了以下工具,大家可以根据情况选用:go generate自动生成代码go fmt代码格式化go mod tidy精简代码包go mod vendor拷贝包到vendor目录go vet检查代码错误golint检查代码风格goswaggerSwag文档工具gowatch热编译工具配合起来,就能够实现如图的连续编码体验:大概就是三个节点的循环,体验跟PHP开发不相上下:好的体验就能促就更多的人参与,目前项目里面5个人可以熟练进行Golang开发,新需求优先选择Golang服务实现。这样就不会因为个别人跑路而导致项目无人维护的了。大家都非常喜欢Golang这种标准、简洁、稳定、强大、易上手的语言。2.2.6 灰度上线这将是我们团队年度最重大变更之一,需要严格把控风险。把控风险最好的方式,就是将功能做成可灰度,避免一刀切。我们在前端启动接口中下发配置,允许前端在命中灰度的情况下将调用域名切换到新接口。而新接口在调用方式上完全等同老接口,包括path、method、参数结构、验签、header规则。所以仅有域名差异,前端比较容易实现。通过apollo实现离线配置,实时控制灰度程度。当前端发现新域名不可用时,兜底使用老域名容错。2.3 最终收益通过此番改造,产生了以下效果:稳定性提升0.02%左右平均延迟降低15.6%TP90延迟降低37.5%而这个收益是在我们没有利用redis来给业务模块大面积覆盖cache而损失时效性的基础上得到的,同时未来也会因为可以使用并行,从而接口延迟快速上涨的情况得到很大的遏制,从而也减少了因为延迟上涨到不可接受,而被迫进行的重构工作。服务上线8个月以来,一直稳定运行,且保持了健康快速的迭代,没有故障,这让我们有底气将经验分享给小伙伴们。2.4 进一步规划使用公司统一框架,完成组件标准化。3. 结语Golang是一门比较新的语言,用好了可以在不明显增加负担的情况下,带来不错的效果。希望在能给业务带来明显收益的前提下,更多的项目能够试用,并从中尝到甜头。上到Web服务,下到框架、组件、中间件,Golang都有很不错的应用,前途看好。 预览时标签不可点 GO6后端27小程序5GO · 目录#GO上一篇Go语言channel源码分析下一篇gin框架之路由前缀树初始化分析修改于2020年07月17日关闭更多小程序广告搜索「undefined」网络结果 修改于2020年07月17日
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-28 06:23 , Processed in 0.650634 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表