Write Once, Run AnyWhere.
越来越多的业务需求都有统一的业务诉求,按照传统的方式,在开发、测试、维护上的成本都是乘以N的,体验也很难做到一致性,特别是复杂的业务,实现成本高,导致功能不能很快的上线,各端侧对齐存在成本,综合来看,这样或者类似的业务基于研发效率等考虑,选择用跨平台的实现方式是非常有必要的。
这里以电商的消息为例,
多角色的运营,多场景的触发,多重能力的支持,导致我们的消息系统十分的复杂,历史发展下的多通道消息系统底层由于历史发展衍生出3个子系统分别是:- AMP消息 - 淘宝天猫的各类运营基于此,衍生出来的主要业务包括商家群、淘友、达人业务等,
- IMBA消息 - 主要是号方面相关的消息,包括BC通知类的消息、达人运营,品牌号运营等
分别在不同的BU维护,而这些在多平台运营下都是需要的,比如多角色的融合,多载体运行的诉求,每个载体又有多种平台。
基于以上背景,研发一个高可用、高复用、可定制的跨终端消息模块是非常有必要的。
关于语言的选择
选择编译型语言还是解释型语言,我觉得是没有绝对而言的,具体选择哪个要根据面对业务形态,适配的终端类型等多方面抉择而言,这里说下我们选择C++作为跨平台开发的首选语言一些背景。
我们团队本身是客户端的业务型团队,当前需要跨平台的业务主要是消息以及消息衍生业务的开发、维护和创新。
主要的终端包括移动IOS、移动安卓、MAC、WINDOWS。
主要载体包括淘宝app、天猫app、千牛app、淘特app、1688app、ICBU app等等。
C++作为天然的跨平台语言,可以高效无缝的调用系统能力,有丰富的技术生态,综合组内的技术栈,是比较契合我们的。
其实也跟集团里其他几个有类似需求的团队聊过,目前选择比较多的也是C++技术栈,本文也将以C++作为跨平台选型语言为主要来讲述。
即使是对于客户端而言,可选择跨平台语言也是众多的,通常跟随着跨平台技术方案一起来看会比较好一些,没有绝对的对和错,只有是否适合你。
关于基础库的选择
既然选择了C++作为我们跨平台开发的基础语言,那么面临的第一个问题是需要有一个功能较为完备,且符合当前诉求的基础库。我们调研了市面上几个主流的基础库
再结合集团内当时的现状,特别考虑移动端的现状:
包大小问题
集团中多种中间件(mtop、db、accs等)要不然已然跨终端,要不然在各端上都有较为优秀的独立sdk提供,基于第一点考虑,这一部分不需要在基础库里二次建设。
综合以上因素,集团内没有合适的足够小的且满足需求的c++基础库,因此我们当时决定自研一个基于C++ STD的符合移动端现状的轻量级跨终端基础库。
这里是跨终端轻量级基础库(LITE)的一个功能集合图,在对这部分进行设计的时候,主要考虑的几个设计原则:
基础能力要圈覆盖,
综合考虑包大小,性能 以及耗电量等
除了提供默认实现外,当系统层面有比较好的能力、或更优解时时,复用端上的能力,通过统一的方式嫁接。举个例子,集团中间件mtop,accs等,亦或者诸如Crypt模块,我们期望用比较简单的方式去实现,调用了Openssl的加解密接口,熟悉Openssl的同学都知道他的库大小在1M左右,同时IOS系统直接就提供Openssl能力,所以在做这一部分的时候我们提供了默认实现,同时开放统一接口接入系统能力,从而实现包大小最优。
总结一下设计原则9个字:最小够用可扩展原则。
平台 | 包大小(kb, 基础功能) | 说明 |
ANDROID | 43kb | 动态库,v8a |
IOS | 116 | 静态库,实际占app大小 |
Windows | 47kb | x86 |
包大小情况如图(未包含淘宝天猫基础客户端能力,包含后略增加60k左右)。
目前Lite库已经作为淘系C++基础库集成在集团的近100个App里,除了包大小优势外,稳定性也极佳,百万分之1左右的崩溃率(淘宝双端统计),同时与淘宝天猫架构组合作,无缝提供淘宝天猫客户端基础能力(log、埋点、键值存储等)。
关于跨平台的架构选择
通常技术还是为业务服务,还是贴近业务来讲述,能更有体感一些,这样也更直观的把我们遇到的问题和背后思考透漏给大家,作为参考,还是以消息为例:
我们面临的问题和诉求:
屏蔽通道,以统一的数据模型方式对外提供:底层需要对接3个服务端(多通道),同时每个server端提供的能力、功能又高度相似,需要抽象后统一屏蔽提供给上层,此外相关数据需要跨通道或者屏蔽通道存储。
调用方线程不统一:业务部分需要处理来自不同线程的高并发(于客户端而言)请求。
多语言:接入层需要适配不同平台,ANDROID-JAVA,IOS、MACOS-Object C,WIN-C++。
多维度支持定制:
业务定制:业务上需要面对不同的APP可以有不同的功能定制,这里还包括支持通道的可配置,业务能力的可定制等,举个例子:在淘特里无群聊服务,但是在淘宝里这一部分又是需要的,在千牛里需要有商家群,但不需要有淘友关系群,等等基于业务、以及包大小考虑的定制。
线程调度定制:基于耗电量、系统资源、平台特性等考虑,需要线程调度方案的可定制。在移动端对耗电量比较敏感且系统资源有限,但是PC端上对性能要求会更高一些,如何在统一的方案里支持线程调度的平台级、甚至是业务级别定制。
网络流量管理定制:对网络能力一方面要有统一的监控,方便判断MTOP的各种API调用情况分析,另一方面需要有统一的切面能力,在MTOP的调用上做二次业务级别的网络流量接口优化定制。
DB管理定制:基于FD、平台特定等考虑,需要DB管理可以根据不同的平台有不同的定制策略,在移动端采用单用户一库多表的形式,在pc端性能最大化采用多库多表甚至需要支持不同app的不同db管理方案可定制。
基础能力复用:对于集团通用的基础组件,需要有统一的能力从外部获取从而方便c++层使用。
稳定性保障:是否方便做单元测试,设计上如何避免单元测试对内部代码的入侵。
基于以上,我们设计了MessageSDK的架构大致如下:
来整体看下上面的设计是如何解决这些问题的。
总体分5部分,Service层,业务层,数据层,数据通道层以及基础服务层。从上到下看整个架构设计
Service层:也就是我们通常说的Wrapper层,其目的是为了翻译业务层C++的接口,提供三种方式的直接接入,Java,Oc,C++,需要说明的是这一层不要做除语言转换外的任何代码,一来方便问题排查,wrapper不做额外的处理和问题排查,二来业界也有不少自动化实现语言翻译的工具,有关这部分我们后面部分详细聊。
业务层:业务逻辑的一个组合,以消息为例,按模块分为三个子模块第一个消息模块,第二个profile模块,第三个群模块,模块与模块之间相对独立,做强制隔离,代码无耦合,功能可定制化
数据层:分为两部分本地存储以及网络存储,其目的是屏蔽数据来源以统一的方式提供给业务层,让业务不感知通道来源,同时在这一层还吃掉所有的耗时操作,IO以及网络请求,其中Adpter主要是屏蔽通道之间的差异,这一部分支持整体可替换,目的提高扩展性,可定制接入三方通道
数据通道层:这一部分就是数据来源的通道,主要有SYNC,WXNet,DB等
基础服务层:是一个跨平台的c++基础库。
其实客户端按层分割的设计理念,无论是在PC时代还是现在的移动互联,都不是什么显而易见的事儿,主要关注下这里面的差异点也可以说是这种框架设计的优点。
便捷:service我们只做语言层面的胶水,缩短后期排查问题的整个链路,方便后期排查和定位问题。同时自动化的实现也再次降低了维护成本。
开放:上图右侧是Openpoint能力,主要是通过一些标准化的开放点去开放一些能力,让接入方做更多的业务属性,简单来说【每个业务有特定的开放点,用统一的形式开放出去】
定制:
业务定制:模块与模块之间相互完全独立,代码无耦合,强制隔离,实现功能的可定制化,让业务方可以自由的进行业务形态组合的同时做到包大小最优,
线程调度定制:统一线程调度处理方案,抽象线程配置策略,多app可自由配置,上层可选择平台级线程调度最优方案。
网络流量管理定制:网络调用处统一切面处理,一方面对瞬时相同请求做拦截合并,大幅度降低服务端压力,另一方面针对业务级别做原子调用的合并定制,减少服务端查询量,此外切面处进行统一打点处理,方便客户端做调用流量监控。
DB管理定制:同线程调度定制,统一DB管理分配方案,抽象库分配策略,多app可自由配置,上层可选择平台级线程调度的最优方案。
扩展:对外提供整体能力,又可以动态替换,目的是提供一种能力让接入方可以放入自己的数据通道,从而实现一个业务的扩展,此方法也极大的方便了后期做统一的单元测试。
于CPP程序员而言,线程模型是非常重要的,好的设计可以从架构上就避免掉后期一系列隐秘的bug,这里包括一系列问题,比如:
在移动端上,通常调用者不关心调用线程,但是在数据层又需要做统一的数据修改和聚合能力
业务层频繁调用,过多的锁除了可能会出现死锁外,也不利于性能最大化。
部分接口如果IO、NET时间过长可能导致整个流程中断,或异常。
来看下跨终端SDK架构设计的线程模型:
接入层:不限制调用线程,降低接入成本
业务层 : 按模块做隔离,单独运行在自己的业务线程里,在biz层做业务无锁话的同时,做到单业务无锁化,多业务并行化
Model层 : 主要是做IO、网络操作,目的是为了业务线程IO、网络无阻塞
整个线程模型的特点:纯异步,可定制,单业务无锁化,多业务并行化,IO网络无阻塞。
C++工程脚手架及C++工程标准化
C++作为天然的跨平台语言,可以高效无缝的调用系统能力,有丰富的技术生态。但是实际面向Android、iOS、Windows、Mac等多平台的开发过程并不顺滑,主要存在以下问题:
平台特性差异、开发语言、开发工具链、编译打包等
多平台下项目配置维护成本高
胶水(语言转换层)代码人工接入成本高、重复性工作大、门槛高(主要是对于C++开发者)
业务及技术框架选型困难
构建发布方案规范化较差、集团研发平台对跨平台构建发布的设计不足
我们在想,SDK开发流程是否可以是一个可被SOP的过程?如果有统一的跨平台SDK开发SOP流程,特别是对于新入此行的同学,当然是非常友好的。想想coder如果可以上来就可以写业务代码,不用关心框架,工程模型,打包方案等等,是不是美滋滋。
为了解决上述共性问题,结合之前我们在消息SDK上的相关经验,我们启动了Eyas(雏鹰)项目。
Eyas旨在进一步结合集团技术能力,降低跨终端开发成本,为其提供标准化的作业流程,包括跨平台基础库组件化、业务框架通用化、配置一体化、语言转换层代码工具化,以及发布能力统一化等一整套解决方案,让开发者们可以在轻量级认识跨终端的同时,更专注于业务本身。
Eyas的中文是雏鹰,我们希望通过Eyas,可以一起在跨终端的蓝海中探索无限可能,同时可以孵化出更多跨平台的模块,让开发者低成本开发跨平台SDK,保证多平台业务一致性,提升业务开发效率的同时降低测试成本。
为了让开发者可以更快的上手跨平台的开发,我们拟定了一套跨平台SDK开发的作业流程,同时在每一步都提供了对应的解决方案,所有的解决方案均提供工具化的方式来高效、低成本的辅助开发者们进行跨平台SDK的开发。下图是MTL4上C++跨平台研发工作流:
其中 IDL(接口描述)的编写 以及 功能代码编写,需要开发者根据实际业务进行开发。我们从项目创建、代码生成、编译选项和构建四个维度分析。
▐ 创建项目
跨终端团队在以往的桌面端开发中积累了很多SDK开发经验,也有一套成熟的SDK框架代码,但是我们在孵化新SDK时候,还是要花一定时间在SDK的通用代码的拷贝上,无法快速的复制出一个新的SDK项目。
开发者都是懒惰的,为了尽可能的复用代码,提升开发效率,开发者发明了宏,也发明了模板编程。为了支持复用SDK框架通用代码和项目配置,我们提供了基于项目模板创建项目的工具,快速实现项目工程从0到1的过程。
其实对于刚涉足到跨终端开发的开发者,创建了可以编译不同平台产物的项目就已经开启了跨平台的大门。
▐ 代码生成
代码生成即语言胶水层代码工具化,由于平台开发的语言差异,需要有一层胶水代码来进行语言的翻译,胶水代码本身是没有业务逻辑的,并且耗费开发成本,人工编写代码也会带来出错的风险。我们使用胶水代码自动化来解决该问题。
胶水代码自动化的原理:
根据接口的idl描述生成接口文件和胶水代码
JNI: C++ <-> Java交互的胶水代码
ObjCPP: C++ <-> ObjC交互的胶水代码
我们基于djinni(地址:https://github.com/dropbox/djinni)进行了二次开发,提升安全性的同时增加了多项feature,更容易对复杂项目做模块定制化能力。
▐ 编译配置+依赖管理