包阅导读总结
1. 关键词:
– B站通用详情页
– 多业务形态
– 代码复杂度
– 通用框架
– 稳定性保障
2. 总结:
本文主要讲述 B 站通用详情页的重要性、复杂度及面临的问题,设计了通用框架来解决复用和灵活等问题,并通过分阶段策略保障稳定性,包括开发阶段添加全链路日志等,最终形成问题发现与解决的闭环。
3. 主要内容:
– B 站通用详情页承载核心流量,代码复杂度高,维护成本高
– 此前三个业务团队各自维护,页面相互独立,能力无法复用
– 产品诉求是将三个详情页融合为一个,统一多业务形态
– 面临多业务形态不统一、迭代方式不统一、保障稳定性、新人上手等问题
– 设计通用详情页框架
– 分为业务层、组件层、框架层,解决复用性问题
– 加入依赖注入能力解决灵活性问题
– 保障稳定性
– 开发阶段添加全链路日志,核心流程修改强制添加 AB 降级方案
– 测试阶段添加监控和告警
– 灰度/线上阶段添加监控和告警,搭建数据报表和异常告警
思维导图:
文章地址:https://mp.weixin.qq.com/s/BHTGfO0e4r5KDoQBK6WyIA
文章来源:mp.weixin.qq.com
作者:大前端
发布时间:2024/7/25 0:05
语言:中文
总字数:2704字
预计阅读时间:11分钟
评分:87分
标签:详情页设计,业务整合,代码复用,依赖注入,稳定性保障
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
大家都知道,详情页承载了站内的核心流量。它的量级到底有多大呢?
我们来看一下,日均播放次数数亿次,这么大的流量,其重要程度可想而知。
在这样一个页面,每一个功能都是大量业务的汇总点。
作为用户核心消费场景,详情页不仅需要承接各种业务的转化,还要负责展示各业务在播放页的功能。
可以说,播放页的代码复杂度属于客户端最高的代码之一,这不仅因为播放页本身的功能复杂,还因为它需要融合大量外部业务功能。
复杂的功能自然会带来较高的代码复杂度,而高代码复杂度往往意味着高代码维护成本。
我们来看一下没有做这个项目之前的状态。如图所示,他们分别为三个业务团队各自维护。页面间相互独立。能力无法复用。
通过这个项目,我们要将他们融合成了一个页面。产品的诉求就是将他们融合为一个,来达到多业务形态统一的目标。
但是,这三个详情页并不像产品想象的那么简单。
每个业务都有自己的特殊形态,如大型活动态、主客态、播单态、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 var storeTable: 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
}
}
extension BlocStore {
public func bindBloc(bloc: Bloc) {
}
public func unbindBloc<T: Bloc>(_ blocType: T.Type) {
}
}
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)
register(service: XXXProtocol.self, to: BBloc.self)
let s: XXXProtocol = store.optional()
class VDScope {
public static let core = "store.core.scope"
public static let biz = "store.biz.scope"
}
public class BlocStore {
typealias ScopeTable = [String: String]
...
func bizTypeDidChanged() {
xxxx
xxx
}
}
这样,新人进入开发时无需关注当前业务形态或业务形态切换的问题,达到快速上手的目的。
在开发资源和测试资源不变的情况下,业务范围扩大了,我们该如何保障吞吐速度和质量的稳定呢?
我们可以将策略分为三个阶段:
1.开发阶段:
对于核心流程添加全链路日志,如果发现不符合预期的数据则直接抛出异常。
同时进行技术埋点上报。如果是对于核心流程的修改,强制添加AB降级方案。
2.测试阶段:
有些bug非常隐蔽,在用户体验上可能没有任何差异,但内部流程或数据可能已经发生异常。
对于类似问题,测试根本无法发现。导致此类问题流入线上的风险。我们可以通过添加监控和告警,让我们及时发现问题。
3.灰度/线上阶段:
我们可以通过添加监控和告警,让我们及时发现问题。
具体实施方案:
首先,我们对通用详情页里核心流程添加了全链路日志,并为日志服务添加了两项额外能力:
如果发现日志类型为Error,内部自动触发DEBUG弹窗提醒,并上报技术埋点,达到对线上稳定性的监控。
同时,搭建离在线数据报表和异常告警,进一步保障稳定性。
至此,搭建了通用详情页从发现问题到定向拉取再到快速定位的闭环。
本文由高可用架构转载。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿