大前端 哔哩哔哩技术 2024年07月12日 12:01 上海
背景介绍
大众都晓得,详情页承载了站内的核心流量。它的量级到底有多大呢?
咱们来看一下,日均播放次数数亿次,这么大的流量,其重要程度可想而知。
在这般一个页面,每一个功能都是海量业务的汇总点。
做为用户核心消费场景,详情页不仅必须承接各样业务的转化,还要负责展示各业务在播放页的功能。
能够说,播放页的代码繁杂度属于客户端最高的代码之一,这不仅由于播放页本身的功能繁杂,还由于它必须融合海量外边业务功能。
繁杂的功能自然会带来较高的代码繁杂度,而高代码繁杂度常常寓意着高代码守护成本。
知道需求
咱们来看一下无做这个项目之前的状态。如图所示,她们分别为三个业务团队各自守护。页面间相互独立。能力没法复用。
经过这个项目,咱们要将她们融合成为了一个页面。制品的诉求便是将她们融合为一个,来达到多业务形态统一的目的。
然则,这三个详情页并不像制品想象的那样简单。
每一个业务都有自己的特殊形态,如大型活动态、主客态、播单态、PUGV/OGV态等一系列业务形态。
每种形态都有自己的特殊规律,况且这些业务形态间还能够互相切换。
需求分析
为了更好地达成目的,咱们必须进行如下思考: 从业务方向:
要处理多业务形态不统一的问题。例如,制品既想要UGC大型活动的能力,又想要OGV的多视角功能。
但这两个能力在之前分别是两个业务团队各自研发的,没法复用,制品在业务选取上没法兼得。 从效率方向:
要处理迭代方式不统一的问题。例如,进度条体验优化需求,制品在给UGC团队提需求的同期,还要复制一份给OGV团队。
两个业务方的研发和测试都必须进入这个项目,并且双方的研发进度和排期可能不一致。倘若制品剧烈需求同一版本上线,还必须协调各方资源。 从质量方向:
要处理怎样保证稳定性的问题。例如,多团队协作,之前都是组内同事协作研发,此刻融入了两个新的业务团队,咱们该怎样保证稳定性。 从团队方向:
要处理怎样让新人快速上手的问题。正常状况下,新人想要进入研发必要对这个系统足够认识后才行。
更何况此刻变成为了三个业务融合的页面。有无一种手段,让新人无需关心繁杂的业务形态和业务规律,只必须关注自己的需求?
具体方法
针对以上问题,咱们能够总结出通用详情页框架必要满足以上三点,分别为:复用性,灵活性,稳定性
接下来咱们继续对多业务形态进行分析。
首要咱们从横向上进行拆解,经过对比,咱们能够发掘
多业务形态间其实有非常多的相同模块。如互动,弹幕发送框,关联举荐等。
从纵向上进行拆解,咱们亦能够发掘非常多相同模块,如弹窗管理器,主题组件,转场组件等。
那样从横向和纵向上咱们发掘,多种业务形态间其实有非常多能够复用的能力。
基于前面的思考,咱们设计了一套通用详情页的框架。将其分为三层: 业务层:将业务模块分为两类,能够在多业务间复用的模块抽象到通用业务,业务独有模块则由各业务自动负责。组件层:抽象出各样通用组件,业务方可自由选取和组装。框架层:抽象生命周期管理、数据管理等核心规律,以此来保准全部详情页的稳定性。这般咱们就初步处理了复用性的问题,然则随之而来的便是灵活性问题。
咱们以实质场景为例,关联举荐模块在课堂态不展示,然则在ugc和ogv下必须展示,另一他的点击事件在ugc和ogv下还会显现差异。
同期关联举荐模块还强依赖简介模块。由于简介模块亦是一个通用组件,业务方能够自由替换。
倘若哪天业务方替换了了简介模块,那关联举荐模块将没法正常运行。
从关联举荐这个例子咱们能够得出倘若想让业务模块复用,必要满足两个要求。 支持业务异化,即准许业务能插进自定义规律,否则此刻抽象的通用模块在迭代的过程必定会变成非通用,或里面掺杂各样if else规律来支持异化。必要保准模块间相互独立,由于所有业务规律这里框架下都变成为了模块,模块是能够由业务方自由选取的。
引入依赖注入
因此呢,咱们必须在流程和模块中加入依赖注入的能力,用于业务方实现差异化规律。
业务方可自动插进自己的业务规律,并选取或替换业务模块。来处理模块间的耦合。
定义依赖注入容器 public class BlocStore {
typealias StoreLock = RecursiveLock
typealias StoreTable = [String: BlocTable]
private let lock: StoreLock = StoreLock()
private lazy varstoreTable:StoreTable = [:]
}
extension BlocStore {
public func register<Service>(service: Service.Type = Service.self, to: Bloc.Type) {
let key = "\(service)"lock.lock()defer { lock.unlock() }
serviceTable[key] = to
}
@discardableResult
public func optional<Service>(service: Service.Type = Service.self) -> Service? {
let key = "\(service)"
lock.lock()
defer { lock.unlock() }
let service = resolve(bloc)
return s
}
}
// Bind and unbind
extension BlocStore {
public func bindBloc(bloc: Bloc) {
}
public func unbindBloc<T: Bloc>(_ blocType: T.Type) {
}
}
// BlocLifeCycle
extension BlocStore {
func onStart(bloc: Bloc?){
bloc?.onStart()
}func onPause(bloc: Bloc?) {
bloc?.onPause()
}
func onResume(bloc: Bloc?) {
bloc?.onResume()
}
func onStop(bloc: Bloc?) {
bloc?.onStop()
}
}组件注册 // 业务方按照业务规律能够注入区别的实现
register(service: XXXProtocol.self, to: ABloc.self) // A业务形态
register(service: XXXProtocol.self,to: BBloc.self) // B业务形态
组件解析 let s: XXXProtocol = store.optional()引入scope
scope分为页面级和业务级两种scope: class VDScope {
public static let core = "store.core.scope"
public static let biz = "store.biz.scope"
}定义 Scope 管理来管理模块的生命周期: Page scope的生命周期与页面保持一致,Biz scope与业务形态的生命周期保持一致。即在页面形态出现变化时,框架层会自动将bizscope下的所有模块进行销毁。public class BlocStore {
typealias ScopeTable = [String: String]
...
func bizTypeDidChanged() {
// 销毁上一个bizscope下所有模块
xxxx
// 初始化新bizscope下模块
xxx
}
}这般,新人进入研发时无需关注当前业务形态或业务形态切换的问题,达到快速上手的目的。
怎样保证吞吐速度和质量稳定?
在研发资源和测试资源不变的状况下,业务范围扩大了,咱们该怎样保证吞吐速度和质量的稳定呢?
咱们能够将策略分为三个周期:
1.研发周期:
针对核心流程添加全链路日志,倘若发掘不符合预期的数据则直接抛出反常。
同期进行技术埋点上报。倘若是针对核心流程的修改,强制添加AB降级方法。
2.测试周期:
有些bug非常隐蔽,在用户体验上可能无任何差异,但内部流程或数据可能已然出现反常。
针对类似问题,测试基本没法发掘。引起此类问题流入线上的危害。咱们能够经过添加监控和告警,让咱们即时发掘问题。
3.灰度/线上周期:
咱们能够经过添加监控和告警,让咱们即时发掘问题。
详细实施方法:
首要,咱们对通用详情页里核心流程添加了全链路日志,并为日志服务添加了两项额外能力:
倘若发掘日志类型为Error,内部自动触发DEBUG弹窗提醒,并上报技术埋点,达到对线上稳定性的监控。
同期,搭建离在线数据报表和反常告警,进一步保证稳定性。
至此,搭建了通用详情页从发掘问题到定向拉取再到快速定位的闭环。
-End-
作者丨凉茶
|