网站负责人 法人,金融网站建设网,wordpress相册展示插件,百度推广方案怎么写前言 美团外卖2013年11月开始起步#xff0c;随后高速发展#xff0c;不断刷新多项行业记录。截止至2018年5月19日#xff0c;日订单量峰值已超过2000万#xff0c;是全球规模最大的外卖平台。业务的快速发展对技术支撑提出了更高的要求。为线上用户提供高稳定的服务体验随后高速发展不断刷新多项行业记录。截止至2018年5月19日日订单量峰值已超过2000万是全球规模最大的外卖平台。业务的快速发展对技术支撑提出了更高的要求。为线上用户提供高稳定的服务体验保障全链路业务和系统高可用运行的同时要提升多入口业务的研发速度推进App系统架构的合理演化进一步提升跨部门跨地域团队之间的协作效率。而另一方面随着用户数与订单数的高速增长美团外卖逐渐有了流量平台的特征兄弟业务纷纷尝试接入美团外卖进行推广和发布期望提供统一标准化服务平台。因此基础能力标准化推进多端复用同时输出成熟稳定的技术服务平台一直是我们技术团队追求的核心目标。 多端复用的端 这里的“端”有两层意思 其一是相同业务的多入口美团外卖在iOS下的业务入口有三个『美团外卖』App、『美团』App的外卖频道、『大众点评』App的外卖频道。值得一提的是由于用户画像与产品策略差异『大众点评』外卖频道与『美团』外卖频道和『美团外卖』虽经历技术栈融合但业务形态区别较大暂不考虑上层业务的复用故这篇文章主要介绍美团系两大入口的复用。在2015年外卖C端合并之前美团系的两大入口由两个不同的团队研发虽然用户感知的交互界面几乎相同但功能实现层面的代码风格和技术栈都存在较大差异同一需求需要在两端重复开发显然不合理。所以我们的目标是相同功能只需要写一次代码做一次估时其他端只需做少量的适配工作。其二是指平台上各个业务线外卖不同兄弟业务线都依赖外卖基础业务包括但不限于地图定位、登录绑定、网络通道、异常处理、工具UI等。考虑到标准化的范畴这些基础能力也是需要多端复用的。关于组件化 提到多端复用不免与组件化产生联系可以说组件化是多端复用的必要条件之一。大多数公司口中的“组件化”仅仅做到代码分库使用Cocoapods的Podfile来管理再在主工程把各个子库的版本号聚合起来。但是能设计一套合理的分层架构理清依赖关系并有一整套工具链支撑组件发版与集成的相对较少。否则组件化只会导致包体积增大开发效率变慢依赖关系复杂等副作用。 整体思路 A. 多端复用概念图 多端复用的目标形态其实很好理解就是将原有主工程中的代码抽出独立组件Pods然后各自工程使用Podfile依赖所需的独立组件独立组件再通过podspec间接依赖其他独立组件。 B. 准备工作 确认多端所依赖的基层库是一致的这里的基层库包括开源库与公司内的技术栈。 iOS中常用开源库网络、图片、布局每个功能基本都有一个库业界垄断这一点是iOS相对于Android的优势。公司内也存在一些对开源库二次开发或自行研发的基础库即技术栈。不同的大组之间技术栈可能存在一定差异。如需要复用的端之间存在差异则需要重构使得技术栈统一。这里建议重构不建议适配因为如果做的不够彻底后续很大可能需要填坑。 就美团而言美团平台与点评平台作为公司两大App历史积淀厚重。自2015年底合并以来为了共建和沉淀公共服务减少重复造轮子提升研发效率对上层业务方提供统一标准的高稳定基础能力两大平台的底层技术栈也在不断融合。而美团外卖作为较早实践独立App同时也是依托于两大平台App的大业务方在外卖C端合并后的1年内我们也做了大量底层技术栈统一的必要工作。 C. 方案选型 在演进式设计与计划式设计中的抉择。 演进式设计指随着系统的开发而做设计变更而计划式设计是指在开发之前完全指定系统架构的设计。演进的设计同样需要遵循架构设计的基本准则它与计划的设计唯一的区别是设计的目标。演进的设计提倡满足客户现有的需求而计划的设计则需要考虑未来的功能扩展。演进的设计推崇尽快地实现追求快速确定解决方案快速编码以及快速实现而计划的设计则需要考虑计划的周密性架构的完整性并保证开发过程的有条不紊。 美团外卖iOS客户端在多端复用的立项初期面临着多个关键点频道入口与独立应用的复用外卖平台的搭建兄弟业务的接入点评外卖的协作以及架构迁移不影响现有业务的开发等等因此权衡后我们使用“演进式架构为主计划式架构为辅”的设计方案。不强求历史代码一下达到终极完美架构而是循序渐进一步一个脚印满足现有需求的同时并保留一定的扩展性。 演进式架构推动复用 术语解释 Waimai特指『美团外卖』App泛指那些独立App形式的业务入口一般为project。Channel特指『美团』App中的外卖频道泛指那些以频道或者Tab形式集成在主App内的业务入口一般为Pods。Special指将Waimai中的业务代码与原有工程分离出来让业务代码成为一个Pods的形态。下沉即下沉到下层这里的“下层”指架构的基层一般为平台层或通用层。“下沉”指将不同上层库中的代码统一并移动到下层的基层库中。在这里先贴出动态的架构演进过程让大家有一个宏观的概念后续再对不同节点的经历做进一步描述。 原始复用架构 如图4所示在过去一两年因为技术栈等原因我们只能采用比较保守的代码复用方案。将独立业务或工具类代码沉淀为一个个“Kit”也就是粒度较小的组件。此时分层的概念还比较模糊并且以往的工程因历史包袱导致耦合严重、逻辑复杂在将UGC业务剥离后发现其他的业务代码无法轻易的抽出。此时的代码复用率只有2.4%。 鉴于之前的准备工作已经完成多端基础库已经一致于是我们不再采取保守策略丰富了一些组件化通信、解耦与过渡的手段在分层架构上开始发力。 业务复用探索 在技术栈已统一基础层已对齐的背景下我们挑选外卖核心业务之一的Store即商家容器开始了在业务复用上的探索。如图5所示大致可以理解为“二合一一分三”的思路我们从代码风格和开发思路上对两边的Store业务进行对齐在此过程中顺势将业务类与技术功能类的代码分离一些通用Domain也随之分离。随着一个个组件的拆分我们的整体复用度有明显提升但开发效率却意外的受到了影响。多库开发在版本的发布与集成中增加了很多人工操作依赖冲突、lock文件冲突等问题都阻碍了我们的开发效率进一步提升而这就是之前“关于组件化”中提到的副作用。 于是我们将自动发版与自动集成提上了日程。自动集成是将“组件开发完毕到功能合入工程主体打出测试包”之间的一系列操作自动化完成。在这之前必须完成一些前期铺垫工作——壳工程分离。 壳工程分离 如图6所示壳工程顾名思义就是将原来的project中的代码全部拆出去得到一个空壳仅仅保留一些工程配置选项和依赖库管理文件。 为什么说壳工程是自动集成的必要条件之一 因为自动集成涉及版本号自增需要机器修改工程配置类文件。如果在创建二进制的过程中有新业务PR合入会造成commit树分叉大概率产生冲突导致集成失败。抽出壳工程之后我们的壳只关心配置选项修改很少与依赖版本号的变化。业务代码的正常PR流程转移到了各自的业务组件git中以此来杜绝人工与机器的冲突。 壳工程分离的意义主要有如下几点 让职能更加明确之前的综合层身兼数职过于繁重。为自动集成铺路避免业务PR与机器冲突。提升效率后续Pods往Pods移动代码比proj往Pods移动代码更快。『美团外卖』向『美团』开发环境靠齐降低适配成本。 图7的第一张图到第二张图就是上文提到的壳工程分离将“Waimai”所有的业务代码打包抽出移动到过渡仓库Special让原先的“Waimai”成为壳。 第二张图到第三张图是Pods库的内部消化。 前一阶段相当于简单粗暴的物理代码移动后一阶段是对Pods内整块代码的梳理与分库。 内部消化对齐 在前文“多端复用概念图”的部分我们提到过所谓的复用是让多端的project以Pods的方式接入统一的代码。我们兼容考虑保留一端代码完整性降低回接成本决定分Subpods使用阶段性合入达到平滑迁移。 图8描述了多端相同模块内的代码具体是如何统一的。此时因为已经完成了壳工程分离所以业务代码都在“Special”这样的过渡仓库中。 “Special”和“Channel”两端的模块统一大致可分为三步平移 → 下沉 → 回接。前提是此模块的业务上已经确定是完全一致。 平移阶段是保留其中一端“Special”代码的完整性以自上而下的平移方式将代码文件拷贝到另一端“Channel”中。此时前者不受任何影响后者的代码因为新文件拷贝和原有代码存在重复。此时将旧文件重命名并深度优先遍历新文件的依赖关系补齐文件最终使得编译通过。然后将旧文件中的部分差异代码加到新文件中做好一定的差异化管理最后删除旧文件。 下沉阶段是将“Channel”处理后的代码解耦并独立出来移动到下层的Pods或下层的SubPods。此时这里的代码是既支持“Special”也支持“Channel”的。 回接阶段是让“Special”以Pods依赖的形式引用之前下沉的模块引用后删除平移前的代码文件。如果是在版本的间隙完成固然最好否则需要考虑平移前的代码文件在这段时间的diff。 实际操作中很难在有限时间内处理完一个完整的模块例如订单模块下沉到Pods再回接。于是选择将大模块分成一个个子模块这些子模块平滑的下沉到SubPods然后“Special”也只引用这个统一后的SubPods待一个模块完全下沉完毕再拆出独立的Pods。 再总结下大量代码下沉时如何保证风险可控 联合PM先进行业务梳理特殊差异要标注出来。使用OClint的提前扫描依赖做到心中有数精准估时。以“Special”的代码风格为基准“Channel”在对齐时仅做加法不做减法。“Channel”对齐工作不影响“Special”并且回接时工作量很小。分迭代包QA资源提前协调。中间件层级压平 经过前面的“内部消化”Channel和Special中的过渡代码逐渐被分发到合适的组件如图9所示Special只剩下AppOnlyChannel也只剩下ChannelOnly。于是Special消亡Channel变成打包工程。 AppOnly和ChannelOnly 与其他业务组件层级压平。上层只留下两个打包工程。 平台层建设 如图10所示下层是外卖基础库WaimaiKit包含众多细分后的平台能力Domain为通用模型XunfeiKit为对智能语音二次开发CTKit为对CoreText渲染框架的二次开发。 针对平台适配层而言在差异化收敛与依赖关系梳理方面发挥重要角色这两点在下问的“衍生问题解决中”会有详细解释。 外卖基础库加上平台适配层整体构成了我们的外卖平台层这是逻辑结构不是物理结构提供了60余项通用能力支持无差异调用。 多端通用架构 此时我们把基层组件与开源组件梳理并补充上达到多端通用架构到这里可以说真正达到了多端复用的目标。 由上层不同的打包工程来控制实际需要的组件。除去两个打包工程和两个Only组件下面的组件都已达到多端复用。对比下“Waimai”与“Channel”的业务架构图中两个黑色圆圈的部分。 衍生问题解决 差异问题 A.需求本身的差异 三种解决策略 对于文案、数值、等一两行代码的差异我们使用 运行时宏动态获取proj-identifier或预编译宏custome define直接在方法中进行if else判断。对于方法实现的不同 使用Glue胶水层protocol提供相同的方法声明用来给外部调用在不同的载体中写不同的方法实现。对于较大差异例如两边WebView容器不一样我们建多个文件采用文件级预编译可预编译常规.m文件或者Category。例如WMWebViewManeger_wm.mWMWebViewManeger_mt.m、UITableViewWMEstimated.mUITableViewMTEstimated.m进一步优化策略 用上述三种策略虽然完成差异化管理但差异代码散落在不同组件内难以收敛不便于管理。有了平台适配层之后我们将差异化判断收敛到适配层内部对上层提供无差异调用。组件开发者在开发中不用考虑宿主差异直接调用用通用接口。差异的判断或者后续优化在接口内部处理外部不感知。 图14给出了一个平台适配层提供通用接口修改后的例子。 B.多端节奏差异 实际场景中除了需求的差异还有可能出现多端进版节奏的差异这类差异问题我们使用分支管理模型解决。 前提条件既然要多端复用了那需求的大方向还是会希望多端统一。一般较多的场景是多端中A端功能最少B端功能基本算是是A端的超集。没有绝对的超集A端也会有较少的差异点。在外卖的业务中“Channel”就是这个功能较少的一端“Waimai”基本是“Channel”的超集。 两端的差异大致分为了这5大类9小类 需求两端相同1.1、提测上线时间基本相同1.2、“Waimai”比“Channel”早3天提测 1.3、“Waimai”比“Channel”晚3天提测。需求“Waimai”先进版“Channel”下一版进 2.1、频道下一版就上2.2、频道下两版本后再上。需求“Waimai”先进版“Channel”不需要。需求“Channel”先进版“Waimai”下一版进4.1、需要改动通用部分4.2、只改动“ChannelOnly”的部分。需求“Channel”先进版“Waimai”不需要只改动“ChannelOnly”的部分。 也不用过多纠结图15是最复杂的场景实际场合中很难遇到目前的我们的业务只遇到1和2两个大类最多2条线。 编译问题 以往的开发方式初次全量编译5分钟左右之后就是差量编译很快。但是抽成组件后随着部分子库版本的切换间接的增加了pod install的次数此时高频率的3分钟、5分钟会让人难以接受。 于是在这个节点我们采用了全二进制依赖的方式目标是在日常开发中直接引用编译后的产物减少编译时间。 如图所示三个.a就是三个subPods分了三种Configuration debug/ 下是 deubg 设置编译的 x64 armv7 arm64。release/ 下是 release 设置编译的 armv7 arm64。dailybuild/ 下是 release TEST1编译的 armv7 arm64。默认在文件夹外的.a是 debug x64 release armv7 release arm64。这里有一个问题需要解决即引用二进制带来的弊端显而易见的就是将编译期的问题带到了运行期。某个宏修改了但是编译完的二进制代码不感知这种改动并且依赖版本不匹配的话原本的方法缺失编译错误就会带到运行期发生崩溃。解决此类问题的方法也很简单就是在所有的打包工程中都配置了打包自动切换源码。二进制仅仅用来在开发中获得更高的效率一旦打提测包或者发布包都会使用全源码重新编译一遍。关于切源码与切二进制是由环境变量控制拉取不同的podspec源。 并且在开发中我们支持源码与二进制的混合开发模式我们给某个binary_pod修饰的依赖库加上标签或者使用.patch文件控制特定的库拉源码。一般情况下开发者将与自己当前需求相关联的库拉源码便于Debug不关联的库拉二进制跳过编译。 依赖问题 如图17所示外卖有多个业务组件公司也有很多基础Kit不同业务组件或多或少会依赖几个Kit所以极易形成网状依赖的局面。而且依赖的版本号可能不一致易出现依赖冲突一旦遇到依赖冲突需要对某一组件进行修改再重新发版来解决很影响效率。解决方式是使用平台适配层来统一维护一套依赖库版本号上层业务组件仅仅关心平台适配层的版本。 当然为了避免引入平台适配层而增加过多无用依赖的问题我们将一些依赖较多且使用频度不高的Kit抽出subPods支持可选的方式引入例如IM组件。 再者就是pod install 时依赖分析慢的问题。对于壳工程而言这是所有依赖库汇聚的地方依赖关系写法若不科学极易在analyzing dependency中耗费大量时间。Cocoapods的依赖分析用的是Molinillo算法链接中介绍了这个算法的实现方式是一个具有前向检察的回溯算法。这个算法本身是没有问题的依赖层级深只要依赖写的合理也可以达到秒开。但是如果对依赖树叶子节点的版本号控制不够严密或中间出现了循环依赖的情况会导致回溯算法重复执行了很多压栈和出栈操作耗费时间。美团针对此类问题的做法是维护一套“去依赖的podspec源”这个源中的dependency节点被清空了下图中间。实际的所需依赖的全集在壳工程Podfile里平铺统一维护。这么做的好处是将之前的树状依赖下图左压平成一层下图右。 效率问题 前面我们提到了自动集成这里展示下具体的使用方式。美团发布工程组自行研发了一套HyperLoop发版集成平台。当某个组件在创建二进制之前可自行选择集成的目标如果多端复用了那只需要在发版创建二进制的同时勾选多个集成的目标。发版后会自行进行一系列检查与测试最终将代码合入主工程修改对应壳工程的依赖版本号。 以上是“Waimai”的commit对比图。第一张图是以往的开发方式能看出工程配置的commit与业务的commit交错堆砌。第二张图是进行壳工程分离后的commit能看出每条message都是改了某个依赖库的版本号。第三张图是使用自动集成后的commit能看出每条message都是画风统一且机器串行提交的。 这里又衍生出另一个问题当我们用壳工程引Pods的方式替代了project集中式开发之后我们的代码修改散落到了不同的组件库内。想看下主工程6.5.0版本和6.4.0版本的diff时只能看到所有依赖库版本号的diff想看commit和code diff时必须挨个去组件库查看在三轮提测期间这样类似的操作每天都会重复多次很不效率。 于是我们开发了atomic diff的工具主要原理是调git stash的接口得到版本号diff再通过版本号和对应的仓库地址深度遍历commit再深度遍历commit对应的文件最后汇总得到整体的代码diff。 整套工具链对多端复用的支撑 上文中已经提到了一些自动化工具这里整理下我们工具链的全景图。 在准备阶段我们会用OClint工具对compile_command.json文件进行处理对将要修改的组件提前扫描依赖。在依赖库拉取时我们有binary_pod.rb脚本里通过对源的控制达到二进制与去依赖的效果美团发布工程组维护了一套ios-re-sankuai.com的源用于存储remove dependency的podspec.json文件。在依赖同步时会通过sync_podfile定时同步主工程最新Podfile文件来对依赖库全集的版本号进行维护。在开发阶段我们使用Podfile.patch工具一键对二进制/源码、远端/本地代码进行切换。在引用本地代码开发时子库的版本号我们不太关心只关心主工程的版本号我们使用beforePod和AfterPod脚本进行依赖过滤以防止依赖冲突。在代码提交时我们使用git squash对多条相同message的commit进行挤压。在创建PR时以往需要一些网页端手动操作填写大量Reviewers现在我们使用MTPR工具一键完成或者根据个人喜好使用Chrome插件。在功能合入master之前会有一些jenkins的job进行检测。在发版阶段使用Hyperloop系统一键发版操作简便。在发版之后可选择自动集成和联合集成的方式来打包打包产物会自动上传到美团的“抢鲜”内测平台。在问题跟踪时如果需要查看主工程各个版本号间的commit message和code diff我们有atomic diff工具深度遍历各个仓库并汇总结果。总结 多端复用之后对PM-RD-QA都有较大的变化我们代码复用率由最初的2.4%达到了84.1%让更多的PM投入到了新需求的吞吐中但研发效率提升增大了QA的工作量。一个大的尝试需要RD不断与PM和QA保持沟通选择三方都能接受的最优方案。分清主次关系技术架构等最终是为了支撑业务如果一个架构设计的美如画天衣无缝但是落实到自己的业务中确不能发挥理想效果或引来抱怨一片那这就是个失败的设计。并且在实际开发中技术类代码修改尽量选择版本间隙合入如果与业务开发的同学产生冲突时都要给业务同学让路不能影响原本的版本迭代速度。时刻对 “不合理” 和 “重复劳动”保持敏感。新增一个埋点常量要去改一下平台再发个版是否成本太大一处订单状态的需求为什么要修改首页的Kit实际开发中遇到别扭的地方多增加一些思考而不是硬着头皮过去并且手动重复两次以上的操作就要思考有没有自动化的替代方案。一旦决定要做在一些关键节点决不能手软。例如某个节点为了不Block别人加班不可避免。在大量代码改动时也不用过于紧张有提前预估有Case自测还有QA的三轮回归来保障保持专注放手去做就好。作者简介 尚先美团资深工程师。2015年加入美团目前作为美团外卖iOS端平台化虚拟小组组长主要负责业务架构、持续集成和工程化相关工作致力于提升研发效率与协作效率。招聘信息 美团外卖长期招聘iOS、Android、FE高级/资深工程师和技术专家Base北京、上海、成都欢迎有兴趣的同学投递简历到chenhang03#meituan.com。