作者 | 芃苏
点击查看视频
我是来自阿里巴巴文娱事业群的芃苏,目前在大麦做前端开发。很高兴能够在多样化专场中,给大家带来《10万级大型场馆背后的绘选座技术》这个主题。题目名字比较长,大家也可能会感觉比较陌生。没有关系,接下来我会从以下四个部分展开介绍:
-
前置的行业信息、业务链路的背景信息。
-
针对“10万”,我会对绘座、选座两个关节环节拆开来讲,从问题的分析,到性能、数据上一些思考和解法。
-
未来的一些展望,关于WebGL、WebAssembly的一些看法。
其实我接触这技术方向,也是最近两年的事情。2018年初加入阿里后,先后在影业、大麦做B类的产品,一直面向商家、企业,为他们提供日常经营的能力和相关服务。这其中会有很多围绕票务展开的技术能力,绘选座就是其中一个。当然除了这个技术方向,我也像很多同学一样,在持续关注中后台的工程能力和效能提升上的沉淀。
说到票务行业,喜欢出行旅游的小伙伴,当你坐上世界最大的民航飞机A380,大概座位有861 个;喜欢看电影的小伙伴,如果你走到一个 30排 x 30列的 IMAX大厅,座位数也就 900 个。那么,如果…..
如果当我们要去看演唱会,或者是看大型的体育赛事、开闭幕式,需要走到一个巨大的场馆中。拿大家熟知的鸟巢体育馆为例,固定座位有8万多个,如果是安排临时座位可以达到11万。这样的场景,就存在于演出行业中。
全国大概有5000多个面向剧院演出、演唱会和体育赛事的场馆,在2019年,有近5000万的人次走进这些大大小小的场馆,去体验现场娱乐的魅力。这也产生了一个超过200亿 GMV的巨大市场。在这个行业中,大麦服务过2019年的男子篮球世界杯、武汉世界军人运动会,也将为2022年的亚运会提供票务服务,同时大家熟悉的开心麻花和摩登天空,也是我们服务的对象。
从商家后台到消费端,每一个项目需要先对座位图进行绘制,基于座位还要匹配上对应的票档、销售属性,最终在线下可以由售票员进行售出,或是大家熟悉的,通过像大麦这样的票务平台进行在线的选座下单。那么对这10万级超大规模座位的绘选,如何去呈现,又有哪些问题的思考和解法呢?
10万绘座
编辑器能力,提效是核心。
绘制,首先它是一个编辑器的能力,既然要编辑,大家都希望能够快速、准确,提效也就成了使用场景中的核心诉求。我找了三个比较典型的问题,当做绘座编辑器问题的切口:
-
编辑器开发是否用我们开发其他业务页面的思路也可以呢?比如,我们是否同样需要考虑组件化?当然,这是基本的抽象,比较容易想到,但在编辑器这个场景中做组件会有些不同。另外,耦合什么、解耦什么,业务数据和视图之间的关系如何?
-
我们目前还是集中的在使用Canvas,因为核心是平面2D的场景。有意思的事情是,我们遇到过Canvas擦除擦不干净的情况,也有因为重绘导致卡顿的问题。
-
像鸟巢这种椭圆形的场馆,肯定会有带弧度的区域,我们在这样的看台区域里怎么绘座呢?
接下来我们依次来看看技术细节,先看一下功能场景,有一个直观的体感。
这个界面是商家后台的操作界面,正要在一个svg底图上去绘制座位。左下角的弹窗是添加座位,其中会有座位属性、座位号、排号、以及座位的递增规律等设置。在我们针对 10万场馆绘座这个场景下,有一点很重要:一个场馆有多个看台(也就是大家看到的区块),每一次绘制操作时,我们都是针对某单个焦点看台,其它的区域则是 Canvas快照(如右下角所示,是多个看台、密集座位渲染的一个状态呈现)。
“10万”的问题,在绘座这个场景就有了两个解法上的拆解:
-
业务中,单看台座位通常不会超过3500个,我们不用时刻面对“10万”去解题;
-
即便需要视图中展示,也可以提供Canvas快照。
如上所述,整个过程包含了svg底图、canvas快照等图形信息。那么我就来从视图组件化的层面聊一聊,它设计上和日常普通后台页面实现上的差异。
大家现在看到的这个四层结构,并非是绘选座能力最初的样子。当时的业务实现可能是扁平化的,强耦合的。这个结果的产生,代码的历史债有一个从低到高、再通过结构化设计降低的过程。客观的合理性需要从优化改进中得出。同时,我们需要把细节处理上的成熟经验延续下来。
-
第一层,关于原子和组件。大家能看到这个编辑器有多种能力:比如基本的座位显示,操作上的圈选、排选、块选、套索,还有后面将介绍到的变形工具,这些要被抽象。
-
第二层,图层,我们的页面实现,不再扁平,不同的前端属性内容,我们在不同的分层去实现,比如HTML layer、Canvas Layer,svg Layer,GridLayer等,分层后可以堆叠多层,也意味着当业务属性有差异、不需要的时候,也可以仅注入所需要的图层,不会有冗余。
-
前面讲到的图层,都会在容器中去做注册,也是我们对整个内容的初始化过程,其中还包括插件的注入、手势和滚动事件队列的注册。
-
最终,呈现到使用者眼前的业务视图,即包含了上述的所有能力。
说完了基本的分层设计,我们来看下第二个问题,也就是这么多座位在绘制过程中,我们怎么做批量的操作?
最初,每个座位在批量移动是,都是独立的去从Canvas画布上抹除,然后在新的画布位置上重绘,图例中 6x6 的36个座位,也就意味着36个独立单位要完成重绘,如果是面对(我前面讲到的)单个看台 3500 个座位,那就有点恐怖了,fps超不过 20,会有比较明显的顿挫感。电影要做到24fps,才能欺骗肉眼、消除视觉延迟,游戏中可能需要做到30-40fps以上才可以、因为需要更高的即时反馈,比如射击游戏或者是多人在线RPG游戏。但是游戏中会有动态模糊等3D渲染技术做视觉辅助,我们目前面对的2D视图会直观的体现到是否线性顺滑上,60fps更是要求输入相应具备40-50ms的响应效率。所以有了“临时块”复制的概念,从代码语义化的命名上我们称之为 “temp-canvas-layer”,其实每一次批量的拖动,就变成1对1的canvas重绘,不再是从n到n。
除了批量复制,还有就是我们提到的变形问题,每个看台的区域大小不同,边界也可能不规则,这需要我们编辑器有针对批量座位的变形能力。“区块”的操作,在这个场景下又出现了两个新的问题:
-
因为要考虑恢复还原的问题,记录的块级信息,我们如果想从0度变成30度,再变成60度,需要先从30度恢复成0度,再从0度变化为60度,这个过程细节是有些令人尴尬的。
-
约束还体现在了其它方面,所有的信息都是单个完整块级的,当面对多个块、或者是块的局部,都会被限制。
所以基于此,新方案中,开始计算每个座位自己的斜率比,让每个座位根据自己斜率比生成的轨道去进行移动变形。其中关于弧度变形里用到了二阶贝塞尔曲线,算是应用数学和图形学中的基本操作。
我们能看到多种变形能力已经基本具备,间距、弧度、倾斜这些都不成问题了,那还能做的更好么?当然可以,我们还可以一键实现变形。
基本已经可以满足业务的日常需求了,再往前走,可能就需要整个座位的录入动作全部智能化,具备座位图识别的能力,而操作人员仅需要做微调编辑。这个是我们未来努力的一个方向。
10万选座
对数据的思考,性能瓶颈的突破。
选座的问题和绘座截然不同。其中涉及的10万级数据场景,在这里我们需要正面面对。
首先,座位被绘制完,要售卖交易,一定具备多种属性,大家平时从演出的票纸信息上一定可以看到许多。这个对渲染性能的诉求是巨大的,但10万的数据信息一次要求我们请求完、渲染加载完,理想化的还要秒开,显然这个问题还需要再被拆解,不能一根筋的硬刚。这里面有非常多的突破,是可以和服务端同学一起来思考完成的。
我们对数据本身要做一个区分,即静态的座位信息,和动态的属性标签信息。有一些信息我们可以本地缓存,有一些数据可以实时更新。
第二点,我们前面提到的所有信息,包括座位地图、座位绘制,这些都是要加密保证安全性的,他们都属于商家的资产安全范畴。大麦对座位底图也有专门的保护技术,这里不展开,还是回到数据问题上。
大家平时每天接触的 JSON 数据格式,其实好处非常多,比如语义可读性强,或者是在Node这样的Web Server中也可以无阻传递。但这两点都不是我们需要的,实话说,可读性强也和安全这个诉求背道相驰。诸多问题让我们考虑到了一种新的结构化数据:来自Google的Protocol Buffer。对比 JSON ,大幅提升了加密的效率,也对于 JSON 处理不好的 int floats 数据类型有了更好的支持。因为业务属性复杂,对服务间的数据传递,跨不同语言框架用工具生成时,基于proto结构也更加方便。
整个数据拆分组合方式都比较清晰了,我们也做了非常多的数据比对,对于一个原始 2.5MB的座位数据进行Proto编码后,再进行无损压缩,体积比之前缩小了27%,在业务数据校验时,解压加上反序列化为对象的时间缩小了47%。
前面说了很多前端和服务端之间的数据连接。当数据来当前端时,我们每一个操作事件其实都不是直接作用在视图上的,中间会有一层DataProxy的设计,这种设计在很多的技术方向上都有应用。基本的目的是为了把我们业务属性的数据和分层结构、组件插件的能力解耦掉,因为底层原子能力都是原生JS和Canvas,有了这一层,我们其实也不再关心业务上的差异、语言框架React还是Vue的问题,对未来的能力迁移和复用,会更加有益。右图的代码示例里,展示了如何添加、删除、还原一个座位数据,以及如何更新一个或多个座位的缓存数据。
接下来,我们看下渲染性能。在绘座环节讲到了单看台的聚焦,其实选座有借鉴类似的思路,但不像绘图、我们可以增加一些限制,比如你画完一个区域、才能操作另一个区域。选座的时候,用户应该是随心所欲的。
基本的选座链路上,首屏加载完、聚焦单看台,这时我们会考虑分区懒加载的问题,因为时刻要准备好用户对于焦点看台周边看台的操作,一旦用户大幅度的跨越看台做了操作,回到之前操作过的分区,还会有重绘的动作。整体的耗时,使用Chrome Performance性能分析工具,可以分析看出整个的操作链路大概需要近10秒的时间。
其实解法的话,我简单说下其中三个重要的突破口:
-
是我们底层的原子组件和插件本身,在实例化过程中有很大的提升空间,做了对应的优化。
-
Service Worker,这个技术大家都知道,但是可能场景受限、使用并不普遍。当同步加载一个焦点看台周边的多个看台时,因为线程池能力的丰富,可以同步对数据的请求加载、座位初始化带来大幅的性能提升。
-
加载后的东西我们全部 Cache住,性能瓶颈的突破,也让我们对已加载过的区域不再需要重绘。
整个总链路的耗时缩短了 70%,基本可以做到 3-4s完成从首屏到单看台、到周边区域、再回到原看台的所有操作,每一步操作做到秒级响应。
在首次加载的环节,这么多座位的渲染,Canvas对CPU的压力实只是一方面,即便Chrome会自动开启少量的GPU加速,但瓶颈有一方面还来自于canvas画布尺寸的上线。所以我们对整个画布做了Grid块级的切分,这为我们多个体验性能提升动作带来了突破性的变化。其实依然是一种拆分的策略。
下图中,大家可以看到在首次分区加载B看台2000个座位时,密密麻麻的效果,我们现在来看一下分区加载的技术细节。
实际上10万级大型场馆,大多数情况下视窗里都不止有一个看台,每个看台千百个座位,那么我们在懒加载时,会多计算1.2倍的视窗范围,也正是这样的余量的预处理,让体验上面带来了大幅的提升。
关于视窗,这里还有一个比较有意思的,就是鹰眼图。
鹰眼图就是选座页面右上角的“缩略图”,需要将黄色可视范围热区里的所有看台边界进行计算,就有了上面这么多绿色的框线。计算之后,我们需要等比的缩放至鹰眼图中,用来辅助用户判断自己当前所在的场馆位置。代码细节里面展示了滚动时,我们是如何转换坐标点位置,对宽高重新进行位置定位、并重绘的。
未来的展望
智能、VR、WebGL & 更多可能性
目前很多的工作是围绕基础能力沉淀建设展开的,希望能够赋能影演现场娱乐行业,去打造一个世界级的绘选座技术。
从开发者能力来讲,目前虽然并非开源项目,但依然从基础的版本管理,再对分层、插件的生态在持续做建设。我们也和交互设计同学一直紧密连接,对于插件生态和图形工具,去做通用的能力建设。文娱的B端C端产品业务,一直都希望能够助力到行业的数字化、智能化建设,在今天我们分享的主题中,绘座和票务属性中还都有很多智能化场景可以去探索。
对于前端同学来说,还有更多可以期待的:
-
比如VR,我们在去年其实已经在C端产品中落地,观众在选座时,还能够模拟看到实际的观看效果、对于座位选座有更直观的体感。2020年业界也推出了 WebXR,这方面的能力和硬件连接的更加紧密。
-
另一个就是 WebGL 方面的探索。比如在绘座和选座的时候,大家其实很希望知道自己在操作的是什么楼层的、更清晰的分层边界。能够立体的去看到。其实Canvas和WebGL已经不是近两年的产物了,也有非常多新的标准协议将取代原有的技术总承,如Canvas 2d,取代WebGL2.0的新方案等。
-
最后,WebAssembly其实也是突破性能瓶颈、在思考方式上的“重新出发”。面向多端、跨业务属性能力复用,使用离机器更近的语言,或Rust这样的高级语言,去重塑底层能力的边界,也能够具备module imported 能力的标准,都在慢慢浮出水面。
相信在未来,绘选座技术方向会有更多扎实的能力沉淀和输出、对业务场景上有更强力的支撑。也欢迎所有对这个技术方向感兴趣的小伙伴线下交流技术。
🔥第十五届 D2 前端技术论坛 PPT 集合已放出,马上获取
关注「Alibaba F2E」
回复 「PPT」一键获取大会完整PPT