1、项目概况
横版式已经持续使用了约2年,内容排布、主题细分、露出效率、操作便利等方面都有一些不足。而瀑布流则能很好的解决这些不足,在业界已经是一种通用的做法。
下图是横版式和瀑布流的对比图:
图1 2.x VS 3.x
可以看出,瀑布流有几个明显的优势:
- 下翻比右移更顺畅,更符合用户的使用习惯;
- 导航栏置顶,更醒目,处在内容和状态栏中间,也起到更好的桥梁作用;
- 内容承载能力更高,可在一定程度上缩短用户找片的路径;
- 行级别内容聚合,有利于在频道内进行细分主题内容的运营。
这次瀑布流改版,除了内容排布这块交互上的变动外,还有一系列的优化需求,例如,从对比图中就可以看到VIP相关的一些变化。
该项目从2017-06-19启动产品策划,2017-10-20白名单内部灰度发布,再到2017-11-20云视听极光全量发布,总体时间上,跨了五个月。产品、设计、开发、测试投入了大量心血,仅开发这一个环节,从2017-08-07启动开发,到2017-09-18进入功能测试,就耗时6周,投入了5个开发人力。
通力合作下,取得了很好的效果:
- VV转化效用和用户留存显著提升;
- 会员转化率增长,超越腾讯视频其他各终端,位列第一;
- 导航栏置顶提升了频道渗透率;
- 瀑布流布局增加了页面内容的丰富度,路径缩短,找片更容易;
- Z轴快捷方式显著提高了用户的使用效率。
2、新旧异同
和之前的横版式相比,瀑布流依然面临着相同的问题:
1.大环境没有变,依然处在多牌照、多合作厂商、多分发渠道的复杂环境中;
2.硬件环境依然苛刻,盒子电视的配置相对于手机和pc来说,说是“渣渣”都感觉有辱该词,软件系统也不容乐观,拿android操作系统来说,普遍要落后一到两个大版本,甚至更离谱。
所以,当前依然面临着严峻的兼容性和扩展性问题。同时,瀑布流也带来了一些新的问题:
1.数据量变大,但就运营性内容来看,瀑布流的内容容量是横版式的5~6倍;
2.内容个性化,推荐内容增加,无限下拉的支持更是让内容容量比更高,对于个性化内容在一定时间段内的稳定展示也是一个问题;
3.UI调整,除了整体版式由横向变为竖向外,每个格子的呈现模式也有很大变化,新增加了不少表现形态的格子,对于现有的形态也做了较大的调整;
4.内容容量增大,主题模式的运营需求,需要配置系统更加灵活。
3、实施方案
3.1、系统架构
图2 系统架构图
针对架构图上的内容,有几点先提一下:
1.频道列表导航和频道精选内容是核心逻辑,由首页逻辑层负责直出;
2.VIP面板信息,由独立的VIP面板信息接口,客户端会在获取到核心数据之后,再进行异步拉取;
兜底cache是一个容错层,在底层服务出现故障的情况下,依然可以提供有损服务,核心逻辑层已接入,而VIP面板逻辑则没有;
首页逻辑层下面调用的系统,CMS系统和其他系统独立开来,是因为CMS系统是首页服务的核心和枢纽,其他系统是对这些核心的内容的扩展。
3.2、重后台轻前端
前面已经提到,电视和盒子面临的软硬件环境比较差。我了解到的原因有:
1.电视方面,主要成本在显示屏,摊到CPU、内存上的投入自然就低了,所以会尽量裁剪这两部分;
2.盒子方面,价钱摆在那里,就几百块钱的东西,你还想咋滴;
3.低硬件配置,跑高版本的android版本,往往会卡,所以,厂商就用老的系统版本而不太愿意用新的系统。
鉴于此,我们采用“重后台轻前端”的模式,将一些逻辑后置,减轻客户端的负担。这样,后台就需要理解客户端的一些东西。我们采用了“七层结构”和“控件化”来对客户端页面内容进行抽象,下面是页面抽象示意图:
图3 页面内容抽象示意图
3.2.1、七层结构
针对精选内容,做了一个七层结构的抽象:
1.Page-页面
每一个频道的精选内容看作一个页面,这个在上面的页面抽象示意图对应的是某个频道导航tab里面的整个页面。
2.Section-段落
Page下一层结构,一个Page是由多个Section由上向下排布组成。Section是Group的包裹,一个Section内可以存在多个Group,这些Group从左到右的方向排布。上面的页面抽象示意图中做了简化,只体现了Group,没有体现Section,因为当前没有做包含多个Group的section,Section当前的作用更多的是为了扩展性预留的一层结构。
3.Group-分组
Group是对运营内容进行分组的单元,定义一个主题,将与该主题相关的内容组合起来,放到统一个分组内。分组内,可以决定是否展示标题,以及标题的展示样式,还可以配置背景图片等。
4.Line-行
Group内的内容,通过Line来组织,当前绝大部分的Group都只有一个Line。
Line具有不同的填充模式,例如观看历史的填充,由客户端完成,普通的运营媒资内容则由后台填充数据。不同的Line类型的高度不同。
5.Component-组件
针对Line里面各个组成单元的不同,定义了不同的Component。Component决定了组成单元内的“Grid-格子”个数,以及Grid的的排布模式。
例如:上面的页面抽象示意图中的 Group1 -> Line2 中的有四个专辑内容,每个Component里面有一个格子。
又例如:下图中的比赛场次,该组件由4个格子组成,从上往下依次排布。
图4 近日赛事Component示意图
6.Grid-格子
Grid里面可以有多条“Item-控件”内容,不同的Grid类型,决定了里面Item的组织模式。
例如:上面的页面抽象示意图中的 Group1 -> Line2 中的每个Component里面的格子中,都只有一条专辑内容,每一条专辑内容会由一个Item来表达。这种Grid的Item组织模式定义为GM_SINGLE。
又例如:上面的页面抽象示意图中的 Group1 -> Line1 -> Component2 中只有一个Grid,不过该Grid里面有多条专辑内容,并且可以上下滚动选取。这种Grid的Item组织模式定义为GM_MULTI_SWITCH。
7.Item-控件
这里的控件,是对Grid里面的Item的抽象,Item由View、Action、Report三部分组成,具体的,如下节“控件化”所述。
3.2.2、控件化
控件是一条内容的基本抽象单元,由三部分组成:
1.View-展示形态
上面的页面抽象示意图,展示了其中一种最常用的海报View。
还有其他不同的View类型,这个根据需求,随着版本迭代不断新增进来,也会对现有的一些内容进行调整。
View的抽象,将具体的内容和展示分割开来,客户端只需要关注某域的展示,而不需要关注该域内容的来源处理逻辑。例如,对于专辑进行海报View展示的三级文字域,对于电影可能要展示看点简短描述,对于电视剧可能要展示最新更新剧集的简介,对于综艺则可能要展示更新时间,客户端并不关心三级文字域的具体内容,只负责把字段内容描绘渲染,后台来处理内容的选择。这样不光减轻了客户端的负担,还提供了很好的灵活性,在需要变更展示内容的时候,修改后台逻辑就可以轻松做到
2.Action-执行动作
Action由两部分组成,一部分是ActionId,一部分是要执行动作的相关参数。下表是一些示例:
3.Report-上报信息
上报的内容,是一系列的Key-Value对。
首页上的上报信息,一部分是定位信息,上面提到的页面七层结构,每一层的结构都会有对应的标识,在上报信息中填入,客户端会向后台上报。
另一部分,是对于具体内容的上报。例如,对于专辑媒资内容,则会把“cover_id-专辑id”上报,对于专题内容,则会把“topic_id-专题id”上报。
控件化的抽象设计,提供了一种跨场景的复用能力。设计实现一个控件,不仅首页上可以用,在列表页,专辑详情页等页面也都可以使用。
3.3、CMS配置系统设计
通过上面“七层结构”和“控件化”的抽象,首页的脉络已经清晰了:
- “七层结构”,我们称为布局,表达内容组织结构;
- “控件化”,表达内容的长相颜值;
- 最重要的是内容,我们将内容分为两种,一种是媒资类型的内容,例如专辑,单视频之类的,我们定义了“数据源”来处理;另一种是入口类型的,例如列表页入口,我们定义了“入口产品库”来处理;
- 还有和内容独立开的,顶部导航栏;
- 另外还有一个部分,上面系统架构图中有提到的列表干预,可以配置干预项,在列表中插入、删除特定内容。
借助CMS配置系统,来进行这几个部分的管理,下图是CMS配置示意图,主要聚焦在内容的组织上,导航栏和干预就忽略了:
图5 CMS配置内容示意图
3.3.1、布局配置化
选取了“七层结构”中靠中间的Component作为CMS上的页面基本构造元素,这是基于元素库维护成本和布局构造灵活性做出的折衷考量:
1.上层的Group、Line自身组成比较简单,但是里面包含的结构可以很复杂,如果作为基本构造元素,会面临元素库爆炸问题;
2.下层的Grid、Item粒度太细,Grid定义好几种基本的类型之后,可以通过拼装组成各种不同的Component,但是,从UI设计角度来看,Component里面的Grid组织模式,并不需要很强的灵活性;Item则与内容具体相关,不同类型的内容使用的控件是不一样的,这一层是与内容打通的粒度,不适合在布局中做过多设计。
上面CMS配置示意图中,“Component-组件”拼装布局部分,图示了几种组件的示意图,并给出了组件的三要素属性,其中:
1.归属行类型属性,是桥联“七层结构”中上层Line的属性,一个Line中可以往后添加各种不同的Component,但是前提是这些Component的行高需要一致,这个属性可以用于布局组织的匹配性检查和有效性校验;
2.内含格子类型属性,是桥联“七层结构”中下层Grid的属性,Component里面可以有一个或多个格子,不同格子的宽高是可以不同的;
3.组件类型属性,这个是指导客户端进行Component渲染描绘的属性,其实上面的归属行类型属性可以确定了Component的高度,内含格子类型属性可以确定Component内Grid的排布,再根据Grid内包含的“Item-控件”信息,已经完全可以进行渲染描绘了,由于Component的扩展数量并不多,定义一个组件类型属性,可以让客户端根据一个简单的类型就可以决定怎么进行渲染描绘,而不需要理解其中牵扯到的其他属性。
以CMS配置示意图中的四格横条Component为例来看这几个属性:
图6 四格横条Component属性示意图
布局的配置步骤:
1.确定需要用到的Component,在新版本迭代中,随着新功能的添加,设计师往往会有新的UI出来,这个时候需要在CMS上构建新的Component,否则使用Component库中现有的内容即可;
2.添加Group,添加好的Group在CMS上有一个Group列表配置页面,在上面可以添加、删除、修改的操作编辑Group,还可以调整Group的相对位置;
3.编辑Group,在其中根据需要插入Line,可能有一个或多个Line;
4.在添加好的Line中,添加对应的Component。
3.3.2、内容配置化
布局和内容的关联,有两处:
1.Group关联数据源内容
前面已经提到,Group是对运营内容进行分组的单元,该主题的内容来源是媒资类型的专辑或单视频等,我们抽象为数据源,数据源可以来自各种不同的接口,有运营人员手动配置的列表,有媒资列表服务的数据,有个性化推荐的数据。
这种关联模式中,布局中的格子首先是空的,在通过数据源定义好的接口拉取到数据之后才按顺序填入数据。
数据源抽象为两部分:数据源类型,对应获取不同的数据获取接口;数据源参数,对应着数据接口获取不同内容的参数。
下图是一个Group关联数据源的示例:
图7 Group关联数据源内容
2.Grid关联入口内容
某些格子需要放置特定的入口,该入口可能是活动入口,可能是某个频道的入口,也可能是轮播功能的入口等等。
在这种关联模式中,布局构造好之后,直接针对格子进行配置,选取合适的入口内容填入。
入口内容抽象为两部分:入口内容类型,对应不同用途的入口;入口参数,用于进行View、Action、Report控件的实现。
下图是一个Grid关联入口内容的示例:
图8 Grid关联入口内容
3.3.3、控件配置化
- 布局配置,确定了页面的骨架结构;内容配置,为骨架填入各个器官;控件配置,则是完善器官的发肤血脉。
- 控件配置,为不同类型的内容关联上不同的View、Action、Report内容。
- 后台控制的配置系统,可以在客户端发布出去之后,动态调整View的展示,Action跳转所需要的参数和需要Report的内容。
控件配置,是与内容紧密关联的,我们通过如下方式,来做到灵活把控:
1.抽象内容数据结构,内容类型ctype,和内容参数ctype_args,所有的内容都以key-value对进行组织,方便进行View、Action、Report的参数关联;
2.ctype分别与相关的ViewType、ActionId、ReportId关联,并将View、Action、Report的参数域ctype_args_key简历关联;
3.在具体进行客户端协议输出的时候,将View、Action、Report的参数域对应的ctype_args_key,去内容参数中找对应的value;
4.ctype和View的关联有点特殊,是需要和GridType一起才能确定具体的View类型,这是因为相同的内容,在不同的格子上,可能会有不同的展现,下图是同样跳转电视剧入口的不同展示形态:
图9 相同内容不同格子上的不同展现
下图是一个列表入口内容的控件配置示例:
图10 控件配置示例
3.3.4、兼容性配置化
客厅的业务所处的环境比较复杂,原因主要有如下几点:
1.牌照管控,电视上的内容管控很严格,总局下辖7大牌照方,各个牌照方的管控程度往往不同;
2.依赖于多家硬件厂家,包括盒子厂家、电视厂家、投影仪厂家,硬件厂家当下的话语权很强,都想做自己的特色,所以和手持设备的app分发不一样,各厂家会有各自的要求,对于功能的支持节奏也不一样;
3.硬件厂家除了厂家的功能要求不同之外,还有硬件能力参差补齐的问题;
4.app的分发渠道错综复杂,除了当贝,沙发这些大的渠道,每个电视厂商都会做自己app应用中心;
5.还有其他版权方的压力,以及合作模式的不同,相同的内容,可能在不同的设备或者渠道上的露出规则是不一样的。
这就导致了客厅需要解决复杂的兼容性问题,下面是首页上面临的一些典型的案例:
1.某些厂家的不同配置的盒子,露出的内容有差异,有的可以露出4k和杜比内容,有的则不可以;
2.不同牌照放管控程度不一,可能导致一个电视剧,或者一个产品功能(例如轮播),在A产线上可以播,在B产线上不能播;
3.NBA内容,可以在创维盒子上露出,在小米上面不能。
关于媒资类型内容的兼容性,主要通过在媒资内容中种下相关控制字段的方式实现。这里主要描述一下入口内容的兼容性。
设计了一个根据BoxId、PT、CHID、版本多种因素的通用选择器,如下图所示:
图11 通用选择器示意图
几点说明:
1.通用选择器由一系列控制组构成,控制组互相之间是“或”的关系,只要命中其中任意一个控制组,就认为是符合条件;
2.控制器有四类:BoxId控制器、PT控制器、CHID控制器和版本控制器,控制组下面的各类控制器是“与”的关系,需要满足所有控制器才符合条件;
3.PT是产线的概念,一般来说,谈妥一项厂商级别的合作之后,就会确定一个新的产线PT代号,BoxId是对于一系列同质化的PT的合集,控制粒度比PT粗;
4.CHID是指渠道号,版本则是只客户端迭代的版本;
5.为分组的布局和入口内容,会关联一个通用选择器,通过配置相应的控制器,来实现版本兼容。
注:当前有一些需求,需要根据机型能力对一些内容进行过滤,当前将机型能力以cookie的形式种到客户端,客户端在请求后台接口的时候带上对应的机型能力cookie值,可以扩展通用选择器,增加一种机型能力控制器,提供更灵活完善的兼容性支持。
3.4、首页后台服务拆分
3.4.1、处理流程
结合前面提到的系统架构图,以及“七层结构”、“控件化”的抽象和配置支持,首页服务的处理流程就很清晰了。
下图是首页逻辑层的处理流程图:
图12 首页逻辑层处理流程图
几点说明:
1.主流程中的获取频道列表和获取数据这两个步骤,抽了两个子流程来进行说明;
2.获取频道布局比较简单,就是做了一下cms svr的布局接口调用,获取到相关的布局数据;
3.组装数据步骤是根据布局和数据列表,组装成和客户端约定好的协议数据,该协议数据表达了“七层结构”和“控件化”的内容;
4.获取频道子列表中对于功能配置数据获取之后进行了一系列特殊频道的过滤,这个也是 一种兼容性的配置化实现,AB测试功能,则为产品上线一些特殊频道和功能入口提供了线上对照验证效果的能力;
5.获取数据子流程,对布局中关联的数据源内容抽取出来(数据源内容定义的是对应布局填充数据的获取方式),根据数据源内容拉取对应的服务获取填充数据,对于个性化内容和非个性化内容做了不同的对待,具体的参见下一节“动静分离”。
3.4.2、动静分离
动静分离指的是数据的动态和静态进行区分对待的意思。
- 静态数据
是指不频繁变动的数据,例如运营同学手工从媒资库中挑选出来的精选内容,对媒资库内容根据条件整理出来的列表内容等。
- 动态数据
主要是指个性化的内容,这些内容对于单个用户来说在一定时间段内是固定的,不过随着内容消耗会不断变化;而对于后台服务来说,虽然对应的接口拉取参数是固定的(例如推荐的内容拉取标识source_key),但是用户身份标识是动态变化的,每个用户的身份标识都不同,对应的,后台服务拉取得到的内容都是动态变化的。
上一小结中的处理流程中,已经有提到获取数据列表的时候有区分是否个性化数据做不同的处理,这里具体来看一下是如何做动静分离的,先来看一下静态数据和动态数据的处理的细节,如图13所示:
图13 动静分离示意图
从图中可以看到:
1.静态数据由静态数据系统做了一层预处理,通过对数据源列表中的各个数据源进行预拉取,同时预拉取Union数据,用Union的数据完善从数据源接口获取到的ID化的数据,保存下来备拉,这个轮询预拉支持10s级别的更新粒度;
2.个性化数据,则由首页逻辑层来拉取,并由首页逻辑层调用Union接口来完善数据。
注:Union是针对媒资内容封装的一个数据接口。
静态数据系统为什么不把推荐接口也包起来呢?
主要是因为:
1.静态类型的数据源都对应这确定的布局,所以,我们可以自己搭建一套静态数据系统来做,自己可控的,可以根据自身需要灵活变更;
2.推荐数据是供客厅这边调用的下层服务,不会来理解上层的业务逻辑和实现,例如推荐数据接口不会感知更不会理解客厅这边做的“七层设计”和“控件化”,更不会知道“布局”的存在,其接口提供数据的模式,也不只是对应都确定的布局,会有一个布局的分裂扩散,具体在下面的“翻页设计”中会提到;
3.静态数据系统聚焦在提供更高效的数据获取服务,去理解布局是不合适的,特别是布局是逻辑层进行翻页的根据的情况下;
4.逻辑层需要做更多的事情,其实还有一些活动和干预之类的逻辑在这篇文章中没有提到,理想的效果是逻辑层做的复杂,周边系统提供尽量单一的接口共逻辑层调用。
3.4.3、轻重分离
前面系统架构图中,从客户端到后台,有两条数据通路:一条是列表导航和精选内容到首页逻辑层的;另一条是VIP面板信息到VIP面板逻辑层的。轻重分离,说的就是这两条数据通路的区分对待,首页逻辑层处理的是主要核心的逻辑,VIP面板逻辑层处理的是特定场景下的需要,也可以说是“主次分离”。
先来看一下VIP面板信息的展示场景,如图所示:
图14 VIP面板信息展示场景
VIP面板信息有其特殊性:
1.从图14中可以看到VIP面板需要用到的会员信息在多处展示;
2.不同区域的展示模式有很大差别;
3.用户身份是否VIP,又会有不同的展示,这个在图14中没有体现出来;
4.每一个区域内的展示元素控制粒度比较细,例如每个区域里面的展示内容的部分高亮,各种细节logo,还有根据用户身份以及登录状态的不同切换,这些用“控件化”的思路来表达,会大大增加控件设计的复杂度;
5.客户端启动时候的默认呈现页面为“精选”tab,VIP面板信息的展现不是首当其冲的。
这部分内容数据较简单,但是展示设计相当复杂,没必要非得用统一的模式来支持,将其从主逻辑中分离出来,用独立的接口进行异步拉取,逻辑更清晰,前后端的设计复杂度都降低了。
这种分离准则,不光适用于首页这一个场景,对于前后端交互的各种场景,当涉及到数据比较独立,呈现层次有明显的轻重主次分别的时候,往往都适合祭出这一招。
3.5、翻页设计
3.5.1、通用翻页模式剖析
对列表数据进行翻页,一般来说,有如下几种模式:
1.页码+页大小模式
接口调用方将页码page_num和页大小page_size开放给调用方,让调用方根据需要指定page_num和page_size调用接口翻取数据。这种模式有如下特点:
- 在列表中数据稳定的情况下比较适用,可以很方便的进行跳页操作。
- 当需要对列表中完整数据进行过滤再返回的时候,就不大合适,要么对完整列表进行统一过滤,然后根据页大小进行分页,这个对于长列表有较大的计算开销;要么就需要接受不同页码返回的数据内容条目不一致的结果。
2.起始位置+翻页数量模式
接口调用方将起始位置start_pos和翻页数量req_num开放给调用方,让调用放根据需要指定start_pos和req_num调用接口翻取数据。这种模式有如下特点:
- 列表数据稳定的情况下,和上一种翻页模式基本等价,start_pos+req_num可以完全对照到page_num和page_size。
- 对于需要过滤的情况,进行完整过滤后进行分页或者接受不同页码返回的数据内容条目不一致的结果这两点也和上面一种基本等价。
- 在放弃跳页便利的情况下,这种模式,接口提供方可以通过告知调用方一个next_start_pos来做到页大小稳定的顺序翻取,这个是page_num+page_size做不到的。
3.分节顺翻模式
接口调用方给客户端有一个起始的数据拉取方式,每次返回的数据中带上下一次请求的完整参数next_page_args,翻页参数完全由接口提供方把控,接口调用方只能一页一页的挨页顺序拿到数据。这种模式有如下特点:
- 适用于列表数据不稳定,即内容个数不可预知,可能是处理过程中动态变化的,对于这种列表,自然也就没有了跳页的需要。
- 接口提供方有很好的把控,可以有效避免一些客户端乱填参数的坑。
- 在需要做一些兼容性的事情的时候,可以后台灵活增加相关参数来做到,对客户端透明,无缝兼容。
3.5.2、首页翻页的特点
首页的瀑布流翻页,有自己的一些特点:
1.翻页的内容粒度大
首页抽象为“七层结构”,显然需要以Section作为翻页条目粒度,而当前Section下面包含的Group,是由一组内容组成的内容集合,而为了更及时的让用户感知到最新的数据,Group内容集合有独立更新的需要。
2.翻页数据量比较大
由于以Section的粗粒度作为翻页基准元素,自然单次翻页的数据量就会比较大,数据量与Group下面内容运营配置的规模紧密相关,后台设计需要慎重考虑服务器流量问题。
3.内容呈现时效高
首页作为TVapp的第一内容露出屏,重要程度可想而知,对于内容更及时的展示,更快的刷新有较高的要求,所以我们在设计的时候会把内容组织形式和内容数据一次性下发,而不会采取先拉取Section的框架,然后框架内的Group内容集合再另外异步拉取的模式,以规避额外的开销带来的不良体验(特别的,是在比较贫瘠的硬件环境下)。
4.过滤逻辑的影响
前面兼容性部分,已经提到,在Group的布局上会根据运营和迭代需要关联对应的通用选择器,来做兼容性支持。这种情况,在翻页的时候,由于客户端请求参数的不同,翻页得到的数据是不一样的,需要在翻页内容是否完整以及缺页是否需要补充上做取舍。
图15是顶部导航栏上“频道”tab内的分类入口的配置,位置1上给的是完整导航入口,这个是对顶部导航栏的一个补充,由于3.2.0有较大的版本改动,所以导航要做版本区分,下面的位置2到位置6,这是3.2.0新增的一些入口。
图15 Section过滤示意图
5.个性化内容的影响
个性化内容在首页上的组织模式比较特殊,会根据用户的标签来返回不同内容集合,和配置的Section(Group)进行匹配,个性化的内容对于每个用户都是不一样的,一个是内容池的容量不一样,还有就是内容池中内容的消耗速度不一样,这就导致在具体请求数据之前,无法知道到底有多少个性化内容集合。这种情况下,想通过先统一过滤,得到稳定的列表都是不现实的。
对于这种个性化的内容,实际的内容个数并不相同的情况,我们用了Section扩散的模式来解决,对于同一类的推荐内容,推荐接口那边定义了一个推荐的source_key,我们为一类source_key配置一个占位Section,并配置扩散数量,实际能有多少个Section则根据接口的具体拉取情况确定。
针对个性化内容,有一个无限下拉的需求,采用个性化组块扩散推荐占位Section的模式来做到“伪无限”的效果,例如一个个性化组块配置扩散占位100个,基本上手已经翻酸了
下图是动态个推内容的扩散示意图。
图16 个性化扩散示意图
3.5.3、首页翻页方案
根据首页自身特点,结合了上面提到的“页码+页大小”和“分节顺翻”两种模式,具体的细节如下:
- 页码+页大小来对Section列表进行分割,我们会先对Section进行通用选择器过滤,然后进行页面分割,在有个性化内容扩散,或者数据源拉取失败的时候,会有页内内容不够page_size的情况,我们接受这种情况,在客户端上表现为若某个Section下面的Group内容为空,则不展示;
- 借鉴“分节顺翻”的翻页后台把控,页面数据的获取方式,客户端不感知,客户端只需要知道每一次翻页回来的数据是第几页(即页码page_num),用于对数据内容进行组装,后台控制页大小,可以有效把控由于运营状况出现变化的情况下可能出现的流量问题;
- 提出一个页面骨架的概念,骨架是指翻页基准Section和内容基准Group的位置顺序关系,以一棵树来类比,骨架相当于一棵树的树干,Group里面具体的内容,则相当于树干上的枝桠、树叶和果实,每次翻页请求都返回完整的骨架,以及对应页面内的Group数据内容;
- 客户端根据骨架的比对,以及骨架上Group数据内容的有无,进行页面的渲染呈现;
- 客户端会维护Group对应的数据获取方式,在获焦的时候触发更新,以及时获取最新数据。
文字描述好难,我怀疑自己过一段时间来看都看不懂自己打了写什么字,还是看图吧,如图17是首页翻页方案的示意图。
图17 翻页方案示意图
3.5.4、分组内容拉取和更新
图17之后,我已经用完了自己的洪荒之力,眼看着图太大,页面都要容不下了,关于分组内容的更新还没有提到,不行,撸不住,我要开死门,出夕象了。来吧,分组内容更新专场走起!翻页唠叨完,马后炮补一发客户端和和后台数据的交互模式:
1.在初次安装启动App的时候,会进行一次多tab批量协议拉取,获取前N个(后台配置可控)tab的第一页数据,批量请求仅此一次,这是为了保证用户启动app后切换tab的丝滑体验,在切换过程中会进行N个tab之后的tab内容预拉;
2.在tab内操作下翻的过程中,调用翻页接口顺序下拉;
3.光标停留在某内容时,会计算内容所在Group的获焦时间间隔和停留时间,以及上次数据拉取时间,判断是否要进行数据更新,如果满足更新的条件,则使用当前内容归属Group所关联的翻页接口更新数据;
4.由于分组内容的更新,需要光标获焦,这个对于一些分页内没有任何有效内容的分组是不公平的,现在没有有效内容,不代表以后也没有有效内容,所以,需要提供一种不获焦也能进行数据更新的机制,这个通过判断光标所在分页的下一页是否全为空,如果全为空,那么在更新本分页内容的同时,连带下一分页的内容也更新。
还是看图说话比较轻松,下图是对于数据拉取和更新的剖析示意。
图18 分组内容拉取和更新示意图
图释:
1.后台给客户端返回的每一页数据都包含当前页码和下一页页码,客户端将这两个信息保存在PageInfo中;
2.S1、S2、S3、S9是普通的Section;
3.RS4-1 ~ RS4-8,是由个性化组块扩散出来的8个占位推荐Section,对应的RG281-1 ~ RG281-8是对应的扩散占位推荐Group,推荐池内容有限,只够填满RS4-1和RS4-2,其余的RS4-3 ~ RS4-8内容都是空的;
4.当某个Section获焦的时候,查询出当前Section所在的page_num,根据page_num调用翻页接口拉取数据,进行页面数据更新;
5.在更新某个页面数据的时候,会检查当前页面的下一页内容是否为空,如果为空,则根据next_page_num进行下个页面的数据更新,需要一直找到一个内容不为空的分页;
6.P4内容为空,依然作为P3的下一页,而不跳过,这是为了防止推荐池内容更新后,内容充足,可以填充更多的占位推荐Section,保留着空P4在页面链条中,让P4里面的RS4-4和RS4-5有重见天日的时候;
7.P5内容为空,并不作为P4的下一页,直接跳过了,这是因为在P4页请求推荐池的时候,已经明确知道推荐池内容已空,可以肯定后面的占位推荐Section也是填不满的,如果继续将P5保留在页面链条中,则会导致无谓的请求,特别是在支持无限下拉的情况下,一个个性化推荐的组块会扩展出来上百个占位Section,无谓的请求开销就有点大了,通过跳页则可以省掉这些无谓的开销;
8.当P4中内容不为空的时候,后台返回的分页数据会将P4的next_page_url由P6更改为P5,动态内容的重新填充,会重构页面链条,让原本被跳过的内容重获新生。
3.6、据说聊下健壮性会长高
3.6.1、缓存
提升处理效率,减少开销的神器,同时还可以提供一定的容错功效。首页的缓存分为如下几块:
1.客户端缓存
顶部导航栏,和每个导航下对应的精选内容,都有做缓存。在首页接口拉取失败的时候,虽然不能及时展现最新的数据给用户,但是不会引发严重的体验问题。
2.后台缓存
拿首页逻辑层来说,访问诸多接口,包括频道列表,频道布局,数据列表,干预,功能配置,A/B测试等等。
对于数据变动不频繁的内容,可以做本地缓存,减少接口调用,提升处理速度。例如频道列表,频道布局。
对于列表数据,分为个推和非个推列表,非个推列表数据,我们用静态数据系统来做缓存。对于个推数据,本来是每次拉取都会获取到不同数据的,不过产品期望用户看到的个推数据在一段时间内(例如半小时),由于缓存key规模过大,用后台来做缓存成本过高,我们只使用客户端的缓存。
3.缓存更新
首页做一个内容的展示模块,对数据的实时性要求不高,最大的需要体现在追剧的需要,例如某个热剧的上线或更新时间已经定好了,我们不能让用户在提前知晓的时间打开app却迟迟看不到想要的内容。
首页上,最重要的内容就是精选内容,对于精选内容中,编辑手工运营的内容,发布到app可见,基本是秒级的。非个推的列表数据,使用静态数据系统已缓存的方式提供服务,静态数据系统写服务定期轮训更新缓存,更新的时间粒度在当前配置在30秒级别,这个可以自由调配的,支持秒级无压力。
对于个推的数据,后台无缓存,只把控更新频率。对于精选内容的更新,是依据缓存数据版本和缓存数据更新时间来进行的。具体的逻辑参
图19 缓存更新逻辑
3.6.2、容灾容错
1.数据相关
CMS发布数据的DB,提供访问的Redis采用iStore.webdev.com提供的数据托管服务。天然支持多机备份,跨IDC异地容灾,省了不少心。
2.逻辑相关
逻辑容灾容错比数据层简单,轻量,首页又是无状态服务,进行多机异地容灾相当轻松,也可以很方便的进行水平扩展。
除了部署上的,当前客厅还有两个重要的措施:兜底cache和熔断机制。
- 兜底cache
从上文系统架构图中可以看到,在接入层和首页服务之间有一层“兜底cache”层,各业务方可以根据需要接入,兜底cache层会根据需要把业务服务接口返回的数据保存到托管Redis。
兜底cachey有两种作用:加速和兜底。加速模式,兜底cache层会先检查保存的缓存数据,判断是否有效,有效的话则直接返回,无效才请求底层。兜底模式下,兜底cache层先请求接口,如果接口请求失败,则捞取保存的缓存数据进行返回。
- 熔断机制
熔断机制,是业务检测到自己所调用的其他接口有异常的情况下,根据配置的规则进行链路断开的机制。当前可以配置的规则包括每分钟失败次数和每分钟失败率。
熔断状态有三种:熔断关闭,熔断开启,熔断半开启。默认状况下是处于关闭状态,即不熔断链路;当错误次数货错误率超过配置阈值,则进入熔断开启状态,熔断链路;熔断开启状态中,会检查开启持续时间,如果持续时间超过配置值,则进入熔断半开启状态,半开启状态下会启动重试,如果重试次数、重试失败次数、重试失败率达到预先配置的阈值,则今日熔断关闭状态,接口链路回归正常,否则重新进入熔断开启状态,保持接口链路关闭。
首页服务后端有访问诸多接口,正在打算接入熔断机制,对于非核心路径接口进行有效的熔断管理,尽量减少后端接口异常对首页的影响。
如果您觉得我们的内容还不错,就请转发到朋友圈,和小伙伴一起分享吧~