【案例分享】58房产列表页php服务化总结

作者介绍

李跃红,58房产用户技术部高级工程师。曾先后就职于凤凰网、360,2016年加入58房产技术部门,负责从JAVA迁移到PHP,3端(PC/M/APP)列表页架构工作。拥有8年discuz phpcms等开源系统二次开发经验。专注高性能高可用等架构设计,之前在360负责过每天17亿的项目优化和重构。

项目背景

随着用户持续不断增加以及业务的不断发展,历史遗留问题带来的红利越来越少,在系统复杂度、业务规模、参与人数、代码腐化程度都不断上升的情况下,你会发现整个项目正陷于泥潭,PM/RD/QA经常抱怨:

  • "改个小功能,怎么要拉这么多模块?"
  • "拉模块也就罢了,改的地方多,编译太慢了。"
  • "慢也就算了,关键不知道怎么改,这代码太丑陋了,算了,为了满足PM的排期需要,凑合来吧。"
  • "这块代码逻辑看不懂,可又不知道何年何月哪个技术改的,自己也不敢擅自删除修改,只能在已有复杂逻辑下继续添加自己的代码和逻辑,使得整个逻辑越来越复杂"
  • "凑合来了,QA发现bug,返工再返工,延期再延期。"
  • "上线了,oh my god,报case了,之前这块有特殊商业逻辑,开发并不知道"

透过现象看本质,我总结来看就这三点问题:

1、业务逻辑复杂耦合,开发维护成本高。

系统复杂度、规模、参与人数都和腐化程度成正比,单纯的靠模块化,后期来看会存在个别模块成为”大怪物“,臃肿不堪,粒度过粗,难以复用。

2、交付效率和质量低。

在敏捷和持续集成方法论、实践大行其道的现今,迭代的按期交付率、单测覆盖率都不尽如人意,线上问题频发。

3、非功能需求不达标。

非功能需求指标特指性能、可用性、可扩展性等方面,代码的腐化和缺少维护、重构,以及没有代码洁癖的人污染下,必然导致性能逐渐下降,甚至出现不同资源竞争的短板效应,造成整个系统crash,同时一个大怪物的伸缩性较差,不能随意横向扩展某个细分功能点。

58的技术历程如下:

【案例分享】58房产列表页php服务化总结插图

技术现状以及推演:

【案例分享】58房产列表页php服务化总结插图(1)

技术决策:技术3.0的核心思路

【案例分享】58房产列表页php服务化总结插图(2)

为此,向服务化的架构转型成为了几乎唯一的选择。作为一个已经有 10 多年历史的网站,58的服务化体系改造则显得更加困难。我们会分享我们在服务化改造实践中的决策历程,经验,教训,希望我们的工作能给同行一个可供参考的案例。房产采用JAVA服务化,php做上层业务,率先执行技术3.0升级迭代,我们开启了php服务化之路。

项目迭代

第一阶段:

我们在整个房产列表服务化过程中,是按照端和类别依次进行实现。当时PC端租房要进行新版改版,M端租房在16年时候由前端已经进行过一次模板重梳理,而二手房M端模板很老,PC端各种商业逻辑又很复杂。综合考量,我们最终选择在M端租房进行开发。

【案例分享】58房产列表页php服务化总结插图(3)

该阶段,我们主要旨在实现原有功能逻辑,并与JAVA服务化实现联调,等所有基础功能开发出来后,我们进行了性能压测,该页面加载完毕大概要6s,而且java服务方由于包含列表页所有信息的数据,网络带宽成为整个QPS的瓶颈。

第二阶段:

针对页面加载缓慢,我们通过定位问题后做的优化点:

1、城市区域商圈信息缓存化(减少3次调用,商圈-区域-城市)

2、默认商圈列表首页筛选信息缓存化

3、底部SEO相关推荐信息缓存化(减少3次调用,热门推荐-热门城市-周边城市)

4、广告信息缓存化(减少2次调用,顶部banner和底部app)

由于列表页数据除了商业需求还有很多个性化推荐,所以不适宜做缓存。而底部SEO相关推荐属于非核心业务,可按照服务降级思维去淘汰不显示。调整之后我们整体流程架构变成了如下:

【案例分享】58房产列表页php服务化总结插图(4)

调整优化之后,页面从load需要6s降低到400ms(之前老JAVA模板load在600ms),最终达到可上线状态。上线后,我们发现如下问题:

1、有部分流量来自爬虫,导致爬虫抓取时候QPS暴增,然后整个服务响应性能骤降。

2、由于JAVA服务的并发限制,流量波动时候经常出现首页无数据。

3、由于列表页数据包比较大,带宽也变成整个系统瓶颈点。

4、定位问题比较困难,我们不知道是php层入参问题还是java返回层问题。

第三阶段:

针对上述问题,我们做了一下优化:

稳定性:

1、增加nginx频率限制,细分业务类别和不同端频率设置

2、收集分析异常日志,对主要的非法URL进行枚举性屏蔽

3、引入防爬虫服务,根据用户的ua,cookie,clientip等属性识别

高可用性:

1、所有列表页筛选项默认商圈/区域/城市做了缓存

2、遍历所有城市默认列表首页数据,放在灾备wcache里。如果首页取不到数据,走灾备数据

3、所有列表页筛选项默认商圈/区域/城市做了缓存

4、添加流量波动、服务异常调用量、命中灾备量监控

5、底部SEO相关推荐模块、广告模块做了缓存,取不到直接服务降级,不显示

性能&效率:

1、列表页筛选ajax交互,局部刷新列表页数据

2、列表页数据编码转换并gzip压缩

3、增加trace开关,可在线快速输出服务器相关信息

4、增加cache开关,可在线快速对缓存数据进行更新删除

5、增加debug开关,可在线快速输出调用服务名称,入参,返回结果

至此我们的流程架构变成了如下:

【案例分享】58房产列表页php服务化总结插图(5)

至此,我们房产列表页php服务化主要工作完成。历时2个多月,先后完成了M端租房/合租/出租/二手房,PC端租房/合租/出租/二手房的php服务化。随着核心业务和模块逐步稳定,我们也在优化代码结构,完善业务遗失逻辑。

最终架构

【案例分享】58房产列表页php服务化总结插图(6)

数据对比

出于数据敏感性,需要同事可单独找我私聊

【案例分享】58房产列表页php服务化总结插图(7)

Q&A

Tips1: 使用wcache时候,不知道是cluster机制,以为是主从机制,会有一层proxy逻辑,导致缓存集中过期,服务穿透

Fix:对数据key进行md5散列,数据过期时间拉长到24小时+rand(1,10)。与DBA沟通,增加服务连接数(集群从1024到51200)

Todo:

1、预留服务降级控制开关,必要时候缓存取不到数据直接返回,而非服务穿透。

2、在SCF层,和wcache做长连接,并按照读写ip进行操作分离,以防集中更新cache数据导致wcache读性能的下降。

3、统计访问量日志top2000,城市区域商圈信息可以永不过期,定期更新即可。底部SEO相关推荐可以将3种数据合并在一起,并不主动过期,改为定时脚本更新。

Tips2:scf调用节点缓存,负载均衡策略有问题,导致会把流量集中打到某一台上,造成雪崩效应

Fix:调整scf读取配置策略,之前是按照权重分配。因为目前请求都是短链,配置缓存时间是一分钟,所以权重分配方案不如直接随机。我们只需要保证当前可用的机器负载平均即可。

Tips3:老模板结构混乱,逻辑复杂,很多无用代码,好多JS变量不知道怎么赋值

Fix:精简老模板结构,将之前每个筛选项一个模板碎片统一合并为一个filtet碎片。逻辑方面精简,能在control层做的逻辑判断和变量赋值,尽量放在control层,而不是在view层。因为之后我们会进行前后端分离,让前端技术尽量少做逻辑判断。比如之前JS就存在大量的根据当前url进行筛选行为判断,进行url追加替换,而这一层在我们control层也必须做,那么就和前端商量,把这些值整体改为赋值引用。对于无用的代码,我们选择临时删除。所谓临时删除就是在wiki上记录着之前模板什么地方,有什么代码,但是初步确认是无用的。这个无用包括一方面是代码本身逻辑判断就冲突,代码一定不会执行该片段,另一方面是之前老的运营需求或者特殊商业逻辑,但是现在已经确认下线。对于JS不知道怎么赋值问题,通常我们会对比线上模板,直接先写死,之后询问前端技术是否知道,而后再通过定位老代码,询问老JAVA技术同事来修改。列表页显示商业信息逻辑也特别复杂,经过和JAVA服务同事沟通,我们决定把这块各种房源不足小区推荐信息逻辑放在服务层来做。大家都知道java可以并行,而php不太适合,严格来讲,我们目前用的框架不支持。

Tips4:老页面打开缓慢,用户筛选响应缓慢,用户体检糟糕

Fix:我们在地铁和区域筛选上,做了数据接口拆分,即后端默认只加载该城市的地铁线路和区域名称。而区域下的商圈和地铁线路上的站点名称通过JS二次请求,这样大大提高了页面响应速度,节省了不少带宽。此外像M端二手房的,有更多高级筛选项的,我们默认不显示,用户点击时候,再触发JS请求,我们返回JSON数据由前端来负责把页面交互实现。此外,像翻页这种功能,我们做了预加载,列表页图片做了裁剪压缩后加载等

Tips5:像PC二手房那样,有10几个筛选条件选项时候,JAVA服务的后端数据包大,加载缓慢

Fix:根据PC二手房特殊模块,我们把核心的筛选放在后端,而非核心筛选条件直接放在php层来做。这样显然会加重我们的服务器负载,不过目前整个系统的瓶颈在java服务层,而非php业务层,php业务机器负载都不到30%。因为对于筛选条件逻辑判断,不仅仅要根据表象URL判断出哪些筛选项已经被选择,还有哪些筛选项url在下次选择时候的逻辑拼接组装,为此我特意封装了一个getParamsUrl公用基础函数。当然这块也会有一些坑,比如你筛选条件里有字母p,s,我们分页url参数是pn*,我们地铁url里参数是sub,这就需要你对特殊的情况做一些额外判断。

Tips6:url里对额外参数枚举不足,导致部分不该走缓存逻辑,走了缓存,进而页面筛选项错乱

Fix:之前我们只对url里打点参数PGTID, ClickID进行了去除。像更多筛选项&filter=true,诚信房源isreal=true,分页segment=true等这种url,我们没有进行充分收集,导致这些请求都走了缓存。后来我们干脆只要url里有query参数我们就不走缓存了。

Tips7:pc二手房,不同城市界面不同,展示数据不同,SEO信息根据筛选条件联动问题

Fix:对于pc二手房,我们一方面是参考已有的JAVA模板参数,另一方面很多个性化城市我们走了config配置数组,并且在模板里注释表明该数据来源于配置,而非后端获取。对于SEO信息联动先后展示问题,我们采取方式是尽可能多的选择所有筛选条件,对比不同的情况下SEO的配置格式。后来联系到SEO部门同事,直接按照现有的后台配置格式研发了公共基础函数getPCSEOInfo,这样我们读取时候只需要把核心数据当参数传过来,非核心数据预留array形式,这样达到可扩展。

Tips8:老模板混乱,很多变量无法跟踪,无法编写新模板

Fix:我们采用了最拙笨的办法,就是先copy线上模板,并把他重新模块化,根据线上数据我们先进行开发,之后再回归老模板,进行逻辑跟踪。这就要求必须尽可能多的选择所有筛选条件,防止某些特殊逻辑没执行到,导致线上展示模板层缺失。这种由表推理,先填充后回归的办法非常实用。会让你避开读取老模板各种逻辑和变量的僵局,快速进行模板嵌套。当然,最后在老模板对比回归时候,发现不懂的区域,就找老技术同事询问,确认无用后注释删除,不确定的先wiki标记。并周知产品运营前端测试组同事。让大家都回忆并了解,达到上线后重点关注这些情况。

Tips9:如何兼容3级分类,像PC二手房的地铁周边,私家别墅等类别?

Fix:我们在做底层架构时候就预留了这样的机制。$cate=array('id'=>12,'listName'=>'ershoufang','localName'=>'二手房','pageType'=>'esf');对应的如果存在3级分类,则变为$cate=array('id'=>7872,'listName'=>'ditiezhoubian','localName'=>'地铁周边','pID'=>12,'pListName'=>'ershoufang','pLocalName'=>'二手房','pageType'=>'esf');这样在3级类页面依然通过一个变量就可以获取到类别相关所有信息。另外判断是否3级类也很简单,只需要判断if($cate['pID']) 即可。同理在商圈区域城市,$cityLocal=array('pID'=>1,'pListName'=>'bj','pLocalName'=>'北京','pAreaID'=>1142,'pAreaListName'=>'chaoyang','pAreaLocalName'=>'朝阳','localID'=>5779,'listName'=>'shuangjing','localName'=>'双井');判断是否是商圈只需要判断if($cityLocal['pAreaID']) 即可。如果if($cityLocal['pID']==cityLocal['localID']),即可判断是1级城市。

Tips10:你们上线时候遇到过哪些坑,如何进行类别流量灰度

Fix:我们在做底层架构时候也预留了这样的机制。比如搜索服务升级,我们M端格式就是m.58.com/bj/zufang/sou/?key=xxx。 而我们在做pc端时候url变为bj.58.com/zufang/sou/?key=xxx 在开发环境下run是OK的,但是在沙箱环境出问题了,因为整个集群nginx配置sou是统一跳转到了公用的搜索服务集群,在询问运维得知现在nginx集群已经不支持单独业务逻辑配置,没办法我们只能调整为bj.58.com/zufang/?newSearch=1&key=xxx方式。好在我们和前端做搜索服务升级时候已经约定,接口url和流量灰度我们来控制,前端只需要根据变量 ____json4fe.newSearch="1"走新搜索逻辑, ____json4fe.searchUrl进行搜索词追加,拼接搜索url。这样不仅在可维护性上做了伸缩,也大大减轻了前端获取当前url在解析当前url的额外开销,也不需要每次调整类别流量灰度都需要JS上线来配合。

项目总结

最后,此项目是我们组同事们的协同努力的结果。感谢团队里每一位夜以继日的同事,感谢我的领导把此项目交给我们来负责。作为团队里的新人,自己不知道时候就多问。还是那句话,技术圈里不存在丢不丢人,闻道有先后术业有专攻。多和同事们交流,多找大牛请教,多找老人们询问商业逻辑,会让你事半功倍。也欢迎大家多交流,一起成长。

资料来源:

1、邢宏宇——GTLC大会:透过未来看现在.ppt

2、张旭——InfoQ:谈谈后端业务系统的微服务化改造

3、王晓阳——HBG 基础平台部:房产服务化项目总结.docx

没有账号? 忘记密码?

社交账号快速登录