新闻客户端信息流重构系统设计——从零到一

项目背景

接口调研及排期规划

新闻客户端旧版imcp接口相对较多,重构时采用先开发核心接口(访问量大的接口),再逐步迁移非核心接口。

接口优先级排序

根据线上一段时间的access日志进行统计,按访问量排序结果如下:

awk '{print $7}' access.log|sort | uniq -c |sort -nk 1 -r|head -30

  10354 HEAD /httpchk.php HTTP/1.1
   6518 GET /client_base_config
   5213 GET /ifengNewsRedidentPush
   3713 GET /userBehaviorRecord
   3081 GET /ifengNewsKeepLiveConfig
   2944 GET /commentEmojiConfig HTTP/1.1
   2064 POST /nlist
   1939 GET /thirdoutput
   1680 POST /getNewsRecList HTTP/1.1
   1190 POST /NewRelatedDocs
   1001 GET /api_phoenixtv_details
    944 GET /weishare_token_api
    826 POST /getNewsDocs
    734 GET /NewRelativeVideoList
    658 GET /its HTTP/1.1
    634 GET /client_search_hotword
    619 GET /android_api
    568 GET /totalProfile
    513 GET /api_vampire_article_detail
    481 HEAD /runaccess/android
    385 GET /phoneweather
    361 GET /ipadtestdoc
    327 GET /userInterestTag
    324 GET /interest_select
    321 GET /ClientNews
    313 GET /share_zmt_home
    302 GET /api_wemedia_index
    263 GET /client_share_bottom_number
    238 GET /burroughsnews
    209 GET /shareNews
    176 GET /nlist
    167 POST /irecommendList
    165 POST /NewRelativeVideoList
    142 GET /ipLocation
    128 GET /favicon.ico HTTP/1.1
    126 GET /newH5Weather
    118 GET /awakeIfengNews HTTP/1.1
    115 GET /client_share_news_recommend_test
    111 POST /ClientNews
    110 POST /ipadtestdoc HTTP/1.1

根据业务分析,决定一期先重构如下几个接口

信息流列表:nlist 
客户端正文:getNewsDocs/ipadtestdoc
文章相关:NewRelatedDocs
视频正文:api_phoenixtv_details
频道配置:totalProfile
视频相关:NewRelativeVideoList
客户端配置:client_base_config
专题正文:TopicApiForCmpp

人员安排,小组分工

人员安排

架构师 1人

业务开发组 2人
调优监控组 1人
Swoole服务组 3人

配置组 1人

业务组分工

接口范围

列表1个 正文3个

人员分工

列表:王爵, 正文:沐杉

排期细化

(共20天,排除其他工作任务的情况下):
1.流程梳理期(共同参与) 列表3天+正文3天 共6天
1.1细化接口流程
1.2梳理与主业务无关的逻辑
1.3梳理核实旧版兼容逻辑
2.数据源协议对接,确认一次请求和二次请求 2天
3.模块拆分设计,版本兼容设计 ,广告模块置入 4天
4.输入输出参数优化,返回字段优化 1天
5.接口开发,框架优化,测试联调 7天
6.相关文档整理-待定

日常开发制度

为保证开发进度和开发质量,不出现重大问题,在重构期间制定了相关制定

编码规范

新加入的团队成员初次合作,为保证编码风格统一,统一制定了php编码规范以及IDE的代码格式化规范,以统一成员间的代码风格

周例会

为了跟踪和梳理遇到的问题,每周固定时间(大概半小时)统一组织周例会,会上把大家遇到的问题和需要讨论的技术方案进行陈述,大家共同分析或者由架构师给出方案建议。同时制定出下一周(下一阶段)的重点工作。

周例会中要强调“里程碑”,即一段时间后的重要节点,需要实现哪些重要的功能模块,给大家信心。

code review

定期组织代码审查会,把大家的代码进行审查和讨论,确定优化方案

问题梳理

在重构初期,由于之前的业务逻辑复杂,因此自己花了很长时间去理解之前的逻辑,并画了流程图。

同时,在开发新接口前,还需要向以前负责接口的同事咨询一些业务细节,如下是当时询问的问题列表:

本次开发基于全新版本,不用考虑历史逻辑,但要考虑后续的版本差异

整理头条和正文、其他信息流哪些是公用的模块

梳理流程中无关的(不做的)流程(如行为上报,push弹窗等)  梳理后去掉

梳理哪些逻辑是废弃逻辑,本次重构不再考虑的  列表,找相关人核实,去掉

整理接口输入参数,返回参数中已废弃的参数,优化输入输出结构   整理 优化

广告模块后期置入,基于原类微调改造 可暂不考虑优化

新数据源的提供形式,分类,数据结构(旧版mongo中的数据被什么所取代?)  直接对接数据层

确认哪些流程和数据是数据源直接可以提供的,保证一次请求可能拿到全部数据  直接对接数据层,或者二次发起请求

======信息流其它疑问及建议======
程序设计建议:
5.推荐接口调用前,编辑数据固定条数 待定
3个引擎,1个头条,1个上下拉,1个非上下拉 ok
nlist page和action模式 二选一 ok
chconfig从rule中抽离,在配置中心单独成立配置项 ok
rule里的下拉不展示(downNoShow)独立成频道规则 ok
cmpppod 和 oldpod获取数据源后的格式化操作(统一字段名) 整合成一套 ok
aqudata中,2320中ids中有list的和没有的  统一成一个 ok
小列表递归,返回值list ok
专题正文会调信息流里的 getTopicTreeNews 专题套专题 ok
接口鉴权保持目前一致 ok
参数预处理处理频道号 ok
财经头条接口下线,自媒体商品信息不需要考虑 ok
cmpp数据全部获取过来后,解析实例化的性能开销,实际只用1条数据

重构设计疑问(需要确认的):
列表频道id参数多个变1个,由配置中心统一处理,(旧版id顺序会影响返回结果)
上划每次都返回置顶top数据,为了兼容客户端展示用,改版后建议客户端修改
列表整体结构是否缓存(内容组织层)
评论数的获取能否在数据源一次返回,不做二次请求
推荐接口的focus内容 能否单独给,不混在流数据中
rule里推荐位的多个数据源 是否能整合成1个? 
列表是否支持其他平台的调用?(非客户端,其他平台可能传其他参数,单独返回定制化数据)
推荐接口返回的specialParam字段能否给成json对象
推荐返回的other字段中,包含很多信息,如缩略图,给过来是半成品,能否推荐直接对应到相应的字段中去,或者能否推荐只给id,其他信息我们自己查
焦点图先从推荐给的数据中分离,再合并到顶部,再分离出列表?其他频道?
视频信息调接口,二次请求,能否一次获得
财经频道数据源接口特殊接入 后续能否不支持列表外的数据源
横排内的子元素都要走外面的大流程 循环执行 性能低(取自媒体号)
正文文章id位数能否统一
正文的数据源能否统一
为方便开发和测试,需要一份包含目前会用到的全部模块组成的一个信息流,全流程覆盖的,可以新旧版本对比的一套数据源(推荐位和上下拉模式各一种)。

实现机制理解:
rule里的index为第一页的pageSize,other为非首页的pageSize
推荐数据与编辑数据的混合机制,类似广告
二级导航rule,otherrule代表模块样式
问题汇总:
正文:
正文内容属性整合,和广告信息,相关信息并列,格式见wiki:http://know.mnews.ifeng.com/pages/viewpage.action?pageId=15508510


推荐:
推荐给的docid都统一成ucms id?  x
确认推荐给的docType在用的都有哪些列表? ok
头条的前2屏取default数据,为何需要将pullup强转为default  ok去掉逻辑
推荐接口的focus内容 能否单独给,不混在流数据中 可以提供
推荐接口返回的specialParam字段能否给成json对象			保持
推荐返回的other字段中,包含很多信息,如缩略图,给过来是半成品,能否推荐直接对应到相应的字段中去,或者能否推荐只给id,其他信息我们自己查			
lastdoc 上报机制确认

内容方面的疑问:
1.评论数的获取能否在数据源一次返回,不做二次请求
2.推荐位的多个数据源 是否能整合成1个
3.列表的视频信息调接口,二次请求,能否一次获得
4.财经频道数据源接口特殊接入 后续能否不支持列表外的数据源
5.获取自媒体信息随列表一并给出(包括各种号信息)
6.正文的数据源能否统一(如统一到ucms)
7.文章的阅读数和评论数从基础数据直接给到
8.数据接口支持批量获取

关于测试需求:

特殊用户群体,特殊地域情况,头条频道特殊业务逻辑验证,
新旧接口降级切换流程 ,业务模块降级流程 业务方面暂时补充这些

技术方案

demo讨论

由于重构项目从0开始,框架的使用和核心流程的设计需要确定一套方案。当时采用的是3个团队成员分别根据自己对项目的理解,写一套demo,之后开会介绍自己demo实现的思路和想法。

最终通过讨论,自己设计的这套开发框架得到了认可和执行,最终的方案以此demo为基准。

框架调研

最初打算用php的 Laravel框架来进行开发,后来考虑到该框架较重,不适合前端API的性能,因此从Laravel框架中抽离了几个核心组件(路由,自动加载)进行了自定义框架的配置

正式开发

业务组和服务组基于调研后的框架进行开发

开发模式采用 敏捷开发+螺旋模式+里程碑 的混合模式

php7 与 swoole

框架方案确定后,整体的业务开发方案也基本确定。模式为:

web -> swoole -> 第三方API

之所以在web和第三方api间加入一个swoole层,目的是:

  1. 逻辑清晰,将请求数据部分单独成立一个项目,便于管理
  2. 重复利用swoole的并发协程机制,提高请求效率
  3. 更好更专注的将监控,熔断,降级等策略应用到请求第三方接口上

重构项目使用了php7+swoole+docker的开发模式,与之前php+fpm的模式不同

性能调优

随着重构功能开发的进行,性能的问题也在逐步显现出来,因此对如下几个方面进行了优化

空跑框架

新项目由于使用了自研框架(路由部分及自动加载部分取自了laravel的相关组件),框架本身会有一些性能损耗,因此进行了空框架的性能测试,测试输出hello world

压力测试 ab

对业务相对复杂的头条接口进行了ab压力测试

第三方服务化(tars)

针对之前的http接口,获取内容接口进行了服务化改造,新的接口使用tcp服务,效率提升了很高,响应时间控制在了几十毫秒

批量接口

由于web端的“批量预请求结果缓存机制”,需要将批量的请求单独组合成数组的方式发给Swoole(而不能用手动拼接逗号分隔的形式),需要Swoole端进行参数转换,转换后发送一次请求(之前的处理方式是发送多次协程来请求,效率较低)

xhprof性能查看

在测试环境部署了一套xhprof,可以查看调用的耗时,便于排查问题

调试debug入口

在url中增加debug参数,可以打印出本次请求Swoole的耗时,参数,返回值以及真实调用第三方的url,便于排查问题。

同时,debug模式会使缓存失效,更方便跟踪过程

客户端对接,历史遗留问题解决

历史遗留问题主要在数据源推荐处,当时约定的一些字段有的已经废弃,有的功能重复等

头条推荐接口优化
1.焦点图分离,考虑与documentList同级新增字段返回
2.others 里面的 url=http://api.iclient.ifeng.com/ipadtestdoc?aid=ucms_7oF3QfFDYTA,拆分出aid字段
3.specialParam和ext->specialParam统一,值为json字符串
4.编辑固定位置条数先获取,再计算size。编辑固定数据能否推荐统一出,包括列表和焦点图。
5.确认头条推荐接口外层的字段都有 flagMap,documentList 字段
6.if($jsonData['flagMap']['flag_jpType'] == 'jpFilter') => 丢弃编辑固定位数据,确认该逻辑是否还存在,和size参数的逻辑冲突
7.前2次上拉,pullup改为default,推荐接口已实现?
8.头条接口本次设计的字段调整,在其他频道推荐接口同步更新
9.提供各模块测试数据,之前只提供了一两个测试的
客户端:
列表,正文url会变,是否有影响?ok
新输入返回参数格式见wiki:http://know.mnews.ifeng.com/pages/viewpage.action?pageId=15508171
列表:
移除push弹框,登录弹框,绑定手机弹框,事件上报相关功能ok

输入参数删除:ok
installTime //安装时间 时间戳
openNum //打开次数
pushStatus //push 打开状态 1/0
closeWinType //关闭的窗口类型
closeWinTime //关闭的窗口时间
closeWinCount //关闭的窗口次数

返回参数确认:
syRetainOldNew		下拉后清除历史数据,保留条数
downHideNews		下拉后自动向上隐藏新闻条数
downHideFocusNav	下拉是否需要隐藏置顶(1:是,0:否)
这3个参数功能是否还需要? 如果需要,能否放到chConfig中返回?ok

其他逻辑:
列表上划每次都返回置顶top数据,为了兼容客户端展示用,改版后建议客户端修改 待定??
财经频道指数模块特殊处理 ok

第三方对接

统一接口,参数格式化

客户端信息流列表需要对接推荐部门的接口,目前的现状是 推荐的头条接口,频道接口,其他小列表接口和打底接口给出的数据格式均不完全一致,因此为了保证对接的统一性,多次和推荐沟通,将全部接口统一成同一种格式,方便对接和维护。

在过渡期swool端临时进行了数据格式的中转,目的是让web端的逻辑保持统一。

后期推荐将只返回id,其他详细信息将通过后续的业务接口(ucms)进行获取。

配合测试部门梳理历史业务

有些历史业务开发同学也不是特别清楚了,需要测试部门的资深同学进行讲解和梳理,服务端进行及时的整理和细化,将废弃的逻辑大胆删除,简化。

大家好才是真的好

对于很多历史遗留问题,有的不光是一个部门进行优化就能起作用的,需要多个部门配合才能完成,因此需要大家开会讨论,将重构的利弊说清楚,只有大家充分理解重构的优势,才会一起配合,向着最终的方向共同前进。此时需要一定的沟通技巧,将各个业务线的负责人都统一战线,才能取得最终的胜利。

重构的验证

服务端API的重构,相对于客户端调用方来说,是透明的,因此,客户端只需要配合服务端做一些工作即可。

而真正的挑战在于重构后结果的正确性。因为之前的业务逻辑相对复杂,因此最终生成的结果无法简单的进行验证,所以在开发过程中使用了各种方法尽量将最终的数据保持正确。

数据正确性验证

对比小模块生成的json结果

小模块生成的json数据短小,且具有可对比性,修改起来排查也时分方便,借助于网上web端的对比工具也十分方便

JSON对比工具
https://www.sojson.com/jsondiff.html

P.S. BeyondCompare高版本的工具中也内置了json比较的功能,但相对不是很智能且BeyondCompare工具本身对正版的验证较严格,最终放弃。

开发json比对工具

上面提到的web对比工具,在对比长json文档的时候会导致浏览器卡死,无法进行。因此整个信息流最终的输出结果无法直接对比,考虑到这一点,想到手工开发一个比对工具

此工具的实现思路是,将一段json数据的每一个key按字母表顺序排序后,重新进行输出,每一个层级均按此规则实现,于是便有了下面这个递归算法:

/**
 * json数组按key名排序
 * @param $jsonArray
 * @return array
 */
function ksortArray($jsonArray) {
    if (!is_array($jsonArray)) {
        return $jsonArray;
    }
    foreach ($jsonArray as $key=> $value) {
        if (!is_array($value)) {
            continue;
        }
        ksort($value);
        $jsonArray[$key]=ksortArray($value);
    }
    return $jsonArray;
}

原始日志记录

由于列表中的数据多数来自推荐,导致每一次刷新每个用户的内容是不同的,因此很难再次复现同一条数据。因此为了保证回溯问题,我在接口层面添加了一系列白名单用户,这些用户的请求原始接口的信息会记录详细的返回结果日志,方便后续的比对和验证

由于访问量大,无法全量记录日志排查,仅能针对部分用户做此功能

手机模拟

在抓包工具中进行请求的中转,在实际手机中查看新接口返回的数据显示是否有缺失,测试人员也配合进行多次验证

新旧项目同步开发

人员沟通,代码同步

在重构项目和原有imcp项目并行开发的阶段,由于版本迭代,新加的功能需要在两个项目中同步开发,负责开发的同学也不太一样,实现的方式也略有不同,因此需要及时沟通,同步两个项目的修改

代码更新通知机制

由于部分小需求及bug修改不在版本需求列表中,可能会随时修改上线,因此新项目中为保证不丢失这一部分的更改,在原有项目的上线更新列表中进行监控和通知,有文件变动及时通知到重构项目的开发,保持两端一致

上线方案

上线及回退方案

重构列表上线方案

上线时间安排:

上线时间 上线的列表频道 量级
2019-11-12 推荐列表,关注列表 全量
2019-11-13 头条列表 1%

上线方式:

列表名称 上线方式 上线客户端版本 回退方案 回退生效时间
推荐,关注 在cmpp后台频道管理中操作频道api配置,升version版本号 v6.7.21 cmpp后台操作回滚 用户下次启动app重新获取频道配置后
头条 在现有头条接口(nlist)中 进行路由跳转,将原有请求转发到重构接口上 v6.7.21 去掉转发逻辑 立即生效,再次刷新头条列表即可

逐步灰度放量

列表逐步从1%,5%,10%到更多进行切换,方式为在原接口中做302跳转

#region 新头条列表切换
$whiteList=[
    '白名单用户id'
];

$hideList=[
    '黑名单用户id'
];
if ((version_compare($version, '6.7.21') >= 0 && $idstr == 'SYLB10,SYDT10')) {
    if (in_array($uid, $whiteList)||(crc32($uid) % 100 < 5&& !in_array($uid, $hideList))) {
         header("Location: https://nine.ifeng.com/headline?" . http_build_query($_REQUEST));
         exit;
    }
}

#endregion 新头条列表切换

因为原接口中有部分post参数,之前考虑使用307跳转,但是安卓客户端不支持,因此只能使用302跳转,将post参数自动变为get参数提交到新接口中*

列表放量历史

时间 客户端类型 客户端名称 上线频道说明 备注
2019-09-02 17:30 ios 专业版,探索版 “fun来了” 和 “暖新闻” 2个频道 推荐位类型频道
2019-09-25 17:40 ios 专业版 推荐频道 推荐频道
2019-10-11 15:56 ios 探索版 推荐频道 推荐频道
2019-10-11 17:14 android 快头条 FUN来了 推荐位类型频道
2019-10-16 16:38:25 ios 探索版,专业版 关注频道 推荐频道类型+特殊处理逻辑
2019-10-16 17:16:28 ios 探索版,专业版 视频频道 推荐频道类型
2019-10-17 17:51:39 ios 专业版 头条 头条
2019-10-28 16:39:26 ios 探索版 头条 头条
2019-11-12 16:42:29 ios 主线版 推荐频道,关注频道 推荐频道类型+特殊处理逻辑
2019-11-12 16:56:34 android 主线版 推荐频道,关注频道 推荐频道类型+特殊处理逻辑
2019-11-14 15:46:37 android/ios 主线版 头条 上线2%,v6721+,增加白名单
2019-11-19 10:14:54 android/ios 主线版 头条 上线5%
2019-11-22 15:29:12 android/ios 主线版 头条 上线10%
2019-11-25 15:00 android/ios 主线版 新正文重新上线 全量
2019-11-26 14:21:45 android/ios 主线版 头条 上线5%
2019-11-27 09:43:49 android/ios 主线版 头条 上线15%
2019-11-28 10:12:27 android/ios 主线版 头条 上线25%
2019-12-02 10:29:13 android/ios 主线版 头条 上线50%
2019-12-04 16:47:26 android 主线版 头条 安卓切换10%,ios 50%
2019-12-06 09:17:18 ios 主线版 头条 ios 80%
2019-12-11 11:34:18 android/ios 主线版 头条 安卓切换40%,ios 100%
2019-12-12 16:48:46 android/ios 主线版 头条 android 40%,ios降到了 30% 版本支持6721-6741版本
2019-12-16 14:06:24 android/ios 主线版 头条 全部60% 6721-6750
2019-12-18 15:05:35 android/ios 主线版 头条 全部80%
2019-12-20 10:26:01 android/ios 主线版 头条 90%
2019-12-23 10:52:53 android/ios 主线版 头条 100% 6721-6750

上线后问题处理

2019-11-14 主线版第一次上线头条列表,上线1%,逐步上线后发生过一些问题

redis依赖导致的问题

由于最初使用redis对swool端的降级熔断进行计数等操作,在调用真正业务的api前,会依赖redis的结果进行打底验证,一旦redis发生异常则swool无法对外提供服务。

当时排查到,熔断的逻辑可能有bug 导致进入了死循环,最终redis拒绝连接。

回退新老接口对应

新列表上线前,旧的头条列表中焦点图更多跳转的热点列表,全部使用了重构接口,因此一旦重构项目异常,则无法切换回备用方案。

解决办法是在旧项目中,临时开发了一个数据格式相同的热点列表接口,一旦新项目异常,马上进行切换。

499过多问题排查

逐步放量后,统计监控平台发现日志中499的情况相对较多(安卓端的情况大量多于ios端),于是进行了相关排查。

出现499的原因是由于“客户端”主动断开连接导致,但是实际情况是,服务端API面对的“客户端”不仅仅指真正的手机客户端,期间还好经历负载均衡服务器,代理服务器等的处理,因此需要协调运维的同事一起进行排查。

最终运维给出的答复是:真正的客户端主动断开连接的可能性较大。

于是,我们调研了客户端的相关行为,推测出如下结论:

499的问题级可能是安卓客户端双击返回和crach相关。
在安卓客户端的实现逻辑,在任何频道按一下back键执行下拉刷新,连续按两次back键,直接退出客户端。但是连续两次back的第一次执行下拉刷新,导致这次请求没有结束就关闭客户端了。
另一个原因可能是crach:目前安卓客户端加载文章后存在内存存在不清除的情况,打开多篇文章,大图文章,复杂广告的文章就会引起操作系统级别的内存回收(一个启动的app安卓系统要求在200M以内运行,而我们的客户端启动后大概占140-160M),从而引发crach,这种情况多出现在正文回到列表的时候。

有同事找了几个有499的用户验证了一下,绝大多数499为该用户的最后一条请求。并且 安卓头条499中 86.7%的为down操作,12.3%的为default

最后调查发现,开机配置中,安卓有配置关于back键返回的动作,是导致客户端主动断开的根本原因。

优化与完善

重构头条上线后,基本功能正常,后续还需要进行性能的优化和完善

目前的性能指标

重构头条列表(nine)新版本切量100%后(6721-6750版本)

私有云 QPS:最高270

金山云 QPS:最高170

总共:450 左右

接口平均响应时间 400ms左右

第三方接口优化

列表依赖的主要第三方接口为推荐接口,推荐接口平均响应时间在200ms左右,因此急需优化该接口的响应时长

需要优化的方面:

1.删除服务端不需要的字段

2.简化参数结构,移除字符串json格式的数据

3.协同内容部门接口,改变推荐接口的数据结构,未来考虑只返回id,具体内容详情从内容接口获取

公有云优化

腾讯云的响应延迟还比较大,后续还应继续优化跟进

原文地址:https://www.cnblogs.com/dannywang/p/12092225.html