《美团:2022年美团技术年货——前端系列(198页).pdf》由会员分享,可在线阅读,更多相关《美团:2022年美团技术年货——前端系列(198页).pdf(198页珍藏版)》请在三个皮匠报告上搜索。
1、前端1知识图谱可视化技术在美团的实践与探索1终端新玩法:技术栈无关的剧本式引导33自动化测试在美团外卖的实践与落地57深入理解函数式编程(上)86深入理解函数式编程(下)115Android 对 so 体积优化的探索与实践142从 0 到 1:美团端侧 CDN 容灾解决方案163美团高性能终端实时日志系统建设实践182目录知识图谱可视化技术在美团的实践与探索作者:巍耀1.知识图谱可视化基本概念1.1知识图谱技术的简介知识图谱(KnowledgeGraph)是人工智能的重要分支,它是一种揭示实体之间关系的语义网络,可以对现实世界的事物及其相互关系进行形式化地描述。举个例子,“孙悟空的师傅是唐僧”
2、就是一条知识。在这条知识里,有“孙悟空”和“唐僧”两个实体,“师傅”是描述这两个实体之间的关系,上述内容在知识图谱中就组成了一个 SPO 三元组(Subject-Predicate-Object)。所以,对于现实世界中实体之间的关联关系,用知识图谱进行描述的话,就显得非常合适。正是由于知识图谱的这种优势,这项技术得到迅速普及,目前在搜索、推荐、广告、问答等多个领域都有相应的解决方案。1.2知识图谱可视化的简介可视化,简单来说就是将数据以一种更直观的形式表现出来。其实,我们现在常用的折线图、柱状图、饼状图(下称折柱饼),甚至 Excel 表格,都属于数据可视化的一种。以往,我们存储数据主要是以数
3、据表的方式,但这种方式很难结构化地存储好知识类型的数据。对于关系类型的数据,如果用前文的例子为基础并补充一些相关信息,经过可视化后就能展示成这样:前端22022年美团技术年货西游记中人、物关系这种信息就很难用“折柱饼”或者表格呈现出来,而用知识图谱可视化的方式呈现,就非常的清晰。2.场景分析与架构设计2.1场景需求分析我们梳理后发现,在美团的各个业务场景中知识图谱可视化需求主要包含以下几类:图查询应用:以图数据库为主的图谱可视化工具,提供图数据的编辑、子图探索、顶点/边信息查询等交互操作。图分析应用:对业务场景中的关系类数据进行可视化展示,帮助业务同学快速了解链路故障、组件依赖等问题。技术品牌
4、建设:通过知识图谱向大家普及人工智能技术是什么,以及它能做什么,让 AI 也具备可解释性。2.2技术选型与架构设计在图关系可视化上,国内外有很多图可视化的框架,由于美团的业务场景中有很多个性化的需求和交互方式,所以选择了 D3.js 作为基础框架,虽然它的上手成本更高一些,但是灵活度也比较高,且功能拓展非常方便。D3.js 提供了力导向图位置计算的基础算法,同时也有很方便的布局干预手段。于是,我们基于 D3.js 封装了自己的知前端2022年美团技术年货布局策略-基础布局提取数据特征优化布局D3.js 提供的力导向图模块(d3-force)实现了一个 velocityVerlet 数值积分器,
5、用于模拟粒子的物理运动。在不做过多干预的情况下,会根据节点与边的关系模拟物理粒子的随机运动。D3.js 的力导向图提供的力学调参项主要包括 Centering(向心力)、Collision(碰撞检测)、Links(弹簧力)、Many-Body(电荷力)、Positioning(定位力)。如何针对不同的节点进行合适的力学干预,是让布局更符合预期的关键。一般来讲,同一业务场景的图谱结构都具有一定的相似性,我们考虑针对业务特定的数据结构特征来做定制化的力学调优。这里举一个简单的场景进行说明,我们抽象出了在树中才有的层级和叶子节点的概念,虽然部分节点会互相成环,不满足树的定义,但是大部分数据是类似于树
6、的结构,这样调试后,展示的关联关系就会比随机布局更加清晰,用户在寻找自己需要的数据时也会更快。前端2022年美团技术年货布局策略-层级布局-1布局策略-层级布局-2以聚簇层布局为例,我们简单介绍一下实现过程:首先处理图谱数据,将中心节点关联的子节点按关联关系归类,生成聚簇边和聚簇边节点,同时将子节点分层。还需要自定义一种聚簇力,聚簇力包含三个参数ClusterCenter、Strength、Radius,对应聚簇中心、力的强度、聚簇半径。在前端2022年美团技术年货图数据库可视化-布局样式参数调整布局策略-图数据库服务链路可视化-平铺层布局参数调整布局策略-服务链路3.2视觉降噪在用户使用可视
7、化应用时,文字/节点/边等元素内容混杂在一起,如果没有做好信前端2022年美团技术年货视觉降噪-文字-对比但这种解法的时间复杂度会随着节点的增多逐渐变得不可控。假如我们有 100 个节点,最多需要 O(n!)的时间复杂度才能计算完毕。我们这里采用栅格划分的方式来做优化,先对画布进行栅格划分,然后确定节点所在的一个或多个栅格,在进行碰撞检测的时候,只需要和自己同栅格的节点做对比即可,因为不同栅格内的节点一定不会出现碰撞的情况。视觉降噪-文字-栅格划分前端2022年美团技术年货边处理-散列排布多类型可调节边我们还实现了多种类型的边,并支持通过参数配置的方式来调整边的样式,比如:贝塞尔曲线控制点、弧
8、度、自旋角度等参数,以满足各种复杂图谱的可视化场景。边处理-多类型边通过多边散列排布,改变边线类型,并调整样式参数,下面是我们将图谱中凌乱无序的边线优化后的效果:前端2022年美团技术年货交互功能-路径锁定聚焦展现对于当前不关注的图谱区域,默认布局可以密集一些来节省画布空间,关注某个区域后,会对当前关注的一小块区域重新布局,让节点排布分散一些,方便查看文字的内容。交互功能-聚焦展现其实,无论可视化的节点与边的数量有多庞大,当深入到业务细节中的时候,使用者关注的节点数量其实不多,重点是把使用者关心的数据从大量数据中筛选出来,并且前端2022年美团技术年货code-sass非等比缩放:在长宽都需要
9、考虑的缩放场景中,使用基于视口百分比的单位vh、vw 很合适,设计稿尺寸为 1920*1080,可以通过转换函数自动计算出对应的 vh、vw 值。其中为了保证字体大小在不同尺寸的屏幕上更符合预期,会用设计稿里的高为基础单位做 rem 的指导参数。个性化适配:在超宽的大屏尺寸下,按照比例自动缩放,在某些元素上视觉效果并不是特别完美,上面的 mixin 可以很方便地在 CSS 中对特定尺寸的屏幕做个性化适配。像素级还原:针对不同尺寸的设计稿校准时,有些元素会带有阴影效果或者是不规则图形,但是设计师只能按照矩形切图,导致 Sketch 自动标注的数据不准确。这时可以把浏览器的尺寸设置成与设计稿一致,
10、再蒙上一层半透明的设计稿图片,逐个元素做对齐,就可以快速对不同尺寸屏幕的设计稿做像素级还原。这套大屏适配技术方案支撑了美团大脑历次的版本迭代。此前在参展亚洲美食节时,由于会场搭建情况比较复杂,屏幕分辨率经历了多次变更,只花费了 0.5 人日就做到了各种不同分辨率的定制、开发和适配工作。前端2022年美团技术年货静态效果对比动态效果此外,美团大脑在展出过程中部分时间是无人值守的,而有了动态可视化后,还需要自动播放循环动画,因此就有了动画脚本自动化的需求:在无人操作时,按照配置好的动画脚本循环执行。用户与应用交互时,能够自动将动画停止。有便捷的方式重新运行动画或进行任意场景的转跳。美团大脑的动画效
11、果具有以下几个特点:动画类型多样化,包含 3D 类型、DOMAnimation、SVGAnimation、第三方 Canvas 组件、Vue 组件切换。多个动画模块之间有衔接依赖,动画执行可以暂停和开始。不同模块的动画之间需要相互通信。我们将每个动画都封装成一个函数,初期使用了 setTimeout 和 asyncfunction 的方案:setTimeout:可以管理简单的动画执行,但是只要前面的动画有时间上的变动,后续所有动画 setTimeout 的 delay 参数都需要改,非常麻烦。asyncfunction:将动画都封装成返回 Promise 的函数,可以解决多个动画模块依赖的问题
12、,这个方案对不同动画模块开发者的协作效率有很大的提升,但是依然无法暂停和取消动画。前端2022年美团技术年货定制开发了支持异步事件的 EventEmitter,emit 函数会返回一个 Promise,resolve 时就会得知 emit 的动画已经执行完毕,使 Vue 跨组件的动画调度更容易。Vue 组件卸载时会自动 off 监听的事件,同时也能自动停止当前组件内的动画调度器。监听 DOM 的 transitionend 和 animationend 事件,自动获取 CSS 动画执行结束的时机。通过上述方案,我们让开发动画模块的同学像写异步函数一样写动画模块,极大地提高了动画模块的开发效率,
13、让每个同学的精力都放在动画细节调试上,下面是最终的实现效果:美团大脑-总体效果前端2022年美团技术年货经过团队的头脑风暴,我们认为核心原因是 AI 技术高深复杂,难以具象化,需要对真实场景进行还原。刚好,知识图谱相对于其他的技术而言其可解释性更强,于是我们决定进行可视化叙事的研发。数据可视化叙事(VisualDataStoryTelling)是通过隐喻对数据进行可视化,并以可视化为手段,向受众讲述数据背后的故事。下面举个例子,来对比一下纯文字与可视化叙事的不同:可视化叙事可以看到,可视化叙事的形式要比文字更直观,能更清晰地让观看者了解数据背后的故事,还可以通过动效将重点信息呈现,引导用户按照
14、顺序了解故事内容。下面我们会介绍几个在可视化叙事中开发动效的思路。扫光效果扫光效果对视觉观感的提升和视觉重点的强调非常有效,我们在做扫光效果的轮廓元素上,需要设计师提供两个文件,一个是轮廓的背景图片,一个是带有轮廓 path 的svg。经过技术调研,我们发现可以通过 svg 渐变或者蒙版来进行实现。前端2022年美团技术年货渐变方案用在弯曲角度较小的轮廓元素或图谱的边上没有问题,不过渐变只能线性的从一侧到另一侧,如果应用到弯曲角度较大的边上,渐变效果会不连续。扫光-渐变-缺点综合分析一下两种方案,蒙版方案更加灵活,渐变性能更好。由于我们的场景可以规避弧度过大的边,因此我们选择了性能更好的渐变方
15、案。动效节奏调试一个动效是否有节奏,对于观看者的体验影响非常大,但是节奏感是一个非常难掌握的东西,这里推荐两个辅助工具:animejs 和贝塞尔调节。这两个工具能够给大家带来很多灵感,同时可以让设计师自己利用工具调试出或者找到期望的动效,降低动画开发的协作成本,这里展示一个使用贝塞尔函数实现的动效:前端2022年美团技术年货3.63D 可视化场景的探索上面介绍的都是在 2D 场景下知识图谱可视化的开发经验,为了实现更好的视觉效果,我们还探索了 3D 场景的技术方案。我们选择了 vasturiano 的 3d-force-graph,主要原因如下:知识图谱布局库为 d3-force-3d,是基于
16、 d3-force 开发的,延续了团队之前在 D3.js 方向的积累,使用起来也会更熟悉。它是基于 three.js 做 3D 对象的渲染,并在渲染层屏蔽了大量的细节,又暴露出了 three.js 的原始对象,便于对 3D 场景的二次开发。在产品与设计层面,因为我们团队在 3D 可视化上的经验比较少,就学习调研了很多优秀的作品,这里主要从 Earth2050 项目获取了一些灵感。下面介绍我们在二次开发过程中主要的优化点。节点样式优化3d-force-graph 中默认节点就是基础的 SphereGeometry3D 对象,视觉观感一般,需要更有光泽的节点,可以通过下面的方案实现。用 shade
17、r 实现一个透明发光遮罩的材质。用类似高尔夫的纹理让节点更有质感。操作虽然比较简单,但是将关键节点“点亮”后,整体的视觉观感会好很多。前端2022年美团技术年货粒子飞散在飞散的时候,我们创建随机不可见的粒子,控制粒子数量缓慢出现,利用 reques-tAnimationFrame 向各自方向飞散。3D-粒子飞散产品效果与场景思考最终在 CES 会场效果如下:3D-CES 现场前端2022年美团技术年货落地场景-2落地场景-3前端2022年美团技术年货参考资料https:/d3js.org/https:/ NLP 部/平台前端团队是一个创新、开放、对技术有热情的前端的团队,团队主要负责搜索平台、
18、NLP 平台、知识图谱可视化、跨端框架、低代码工具等方向,长期诚聘实习、校招、社招,坐标北京/上海,欢迎感兴趣的同学发送简历至:,也欢迎同行进行技术交流。前端2022年美团技术年货常见的功能引导App功能引导是用户心智建设的“敲门砖”,只有让用户熟悉平台操作、了解产品特色作为前提,才能进一步借助情感化、场景识别、运营技巧等手段来做用户心智建设。随着App功能的不断迭代,在用户中逐渐出现了“用不明白”的现象,这个现象在美团外卖商家客户端尤为突出。作为商家生产运营的主要工具,客户端承载的业务功能复杂多样,设置项更是品类繁杂,如果商家用不明白,就会对整个运营体系造成非常不利的影响。为了让商户“用得明
19、白”,2021年第一季度,美团外卖商家端在功能引导类需求层面耗费了大量人力,平台产品侧重点对商家进行了扶持,并试点了“情感化引导”等项目,虽然业务效果取得了正向收益,但由于后续的研发估时较大,空有想法却难以落地。类似的营销、广告、商品、订单等业务也由于快速迭代,也需要配套生产一系列产品功能的引导需求,也因为人力问题而一直处于积压状态。部分引导类需求前端2022年美团技术年货力。我们提供标准化的框架,并通过一些参数与类型的调配来应对不同的需求场景,在大框架中提供有限的定制能力。更高的应用效果,相比于传统的功能引导,剧本式引导可以更加生动,能够融合更多元素(不僵硬的语音、恰逢时机的动效、和蔼的 I
20、P 形象),从而带来沉浸式的体验,增强用户感知。更加关注与用户的交互/互动,操作后的反馈最好是真实页面的变化,加深用户的理解。时机更加可控,在满足规则后自动触发,后台可筛选特定特征的用户(比如用不明白的用户)定向下发剧本引导。面临的挑战1.目前,Flutter/ReactNative/小程序/PWA 等终端技术栈各有各的适用场景,App大多数为几种技术栈的组合,如何抹平差异,做到技术栈无关?(即容器无关性Containerless)。2.剧本执行的成功率与健壮性如何保证?(MVP版Demo的成功率仅达到50%,稳定版目标要达到99%以上)。3.怎样落实“零代码”的剧本生产方案,以支持产运独立发
21、布?(之前类似单任务需要研发2050 人日)。整体设计展示形式选择项目主体应该选择基于什么样的形式?我们的思路是先确定“好的效果”,再去尝试在此形式下做到“更低的成本”。“好的效果”自然是期望体现在产品指标上,但是前期,在数据对比上不同的场景落地指标跨度较大,对于不同的形式也难以拉齐标准横向比较。所以我们从“学的越多才能会的越多”的角度推演,通过平台传递的信息能否更多的被用户接受,来衡量最终产品效果。前端2022年美团技术年货高。目前,简易的试点已经获得了不错的提升效果,所以产研同学有信心在引入更多客户端能力与调优后,整体效果还有更大的提升空间。方案描述ASG剧本式引导项目的目标受众是产品运营
22、同学,我们尝试从他们的角度思考了:怎样才算是一个便捷且高效的“剧本式引导生产与投放工具”?产品运营视角如上图所示,我们提供给产品运营同学的交互仅有:录制、编辑、预览、发布等四个步骤,当产品运营同学需要在业务模块上线引导时,只需拟定一个剧本,然后四步即可完成这个“需求”,整个流程几乎不需要研发和设计同学的参与。在具体的执行方案中,我们对剧本引导进行了模板化的设计编排,将每个引导动作抽象成一个事件,多个事件组合形成一个剧本。同时为保证不同终端的兼容性,我们设计了一套标准且易扩展的协议描述剧本元素,运行时PC管理后台和App可自动将剧本解析成可执行的事件(如坐标点击、页面导航、语音播放等)。核心的功
23、能模块在剧本的执行侧。为了保证更高的应用效果,我们要求引导过程与用户的交互,均操作在真实的业务页面,播放展示的元素也要求是实时计算与绘制的,这对系统性能与准确性提出了更高的要求。系统的全景图如下图所示,由终端侧、管理后台与云服务三个部分组成:前端2022年美团技术年货剧本预览区域:支持通过二维码扫描,实现便捷、无差别地效果预览,保证与最终呈现给用户的引导效果一致。管理后台云服务:依赖美团的底层云服务平台,在剧本编辑完成后,需要资源托管服务、CDN等进行资源的管理及分发,完成剧本的下发及更新。业务中台在端侧SDK和后台策略配置的共同作用下,提供了更细粒度的下发配置,更丰富的触达时机,满足业务侧按
24、时间、城市、账号与门店、业务标签等维度配置的诉求。部分技术方案剖析基于视觉智能的区域定位方案在引导过程中,需要对关键路径上目标区域设置高亮效果。在技术栈无关的前提下,基本思路是线下截取目标区域,线上运行时全屏截图,通过图像匹配算法,查找目标区域在全屏截图中的位置,从而获得该区域坐标,如下图所示:前端2022年美团技术年货图像匹配流程概要图像匹配算法由信息提取、匹配准则两部分组成。根据信息载体的二维结构特征是否保留,匹配算法可分为基于区域的信息匹配与基于特征的信息匹配,如下图所示:图像匹配流程概要基于区域的图像匹配方法,采用原始图片或域变化后的图片作为载体,选取最小信息差异区域作为匹配结果,该方
25、法对于图像形变、噪声敏感等处理不佳。而基于特征的图像匹配方法,丢弃了图像二维结构信息,提取图片的纹理、形状、颜色等特征及位置信息描述,进而得到匹配结果。基于特征的算法鲁棒性好、信息匹配步骤速度快、适应性强,应用也更加广泛。基于传统 CV 特征的图像匹配其实该项目的应用场景,属于典型的ROI(RegionOfInteresting)区域检测、定位,传统CV算法针对不同的使用场景已经有很多比较成熟的算法,比如轮廓特征、连通区域、基于颜色特征、角点检测等。角点特征是基于中心像素与周围像素亮度差异变化剧烈,且基本不受旋转、缩放、明暗等变化影响的特征点,经典的角点检测有前端2022年美团技术年货要想判断
26、一个像素点p是不是FAST特征点,只需要判断其周围7x7邻域内的16个像素点中是否有连续N个点的灰度值与p的差的绝对值超出阈值。此外,FAST之所以快,是因为首先根据上、下、左、右4个点的结果做判断,如果不满足角点条件则直接剔除,如果满足再计算其余12个点,由于图像中绝大多数像素点都不是特征点,所以这样做的结果,用深度学习”炼丹师“的话来说,就是“基本不掉点”,且计算时间大大减少。对于相邻的特征点存在重复的问题,可以采用极大值抑制来去除。邻域 16 个点的位置(左);上、下、左、右 4 个点(右)改进后的OFAST会针对每个特征点计算一个方向向量。研究表明,通过从亮度中心至几何中心连接的向量作
27、为特征点的方向,会比直方图算法和MAX算法有更好的效果。OFAST 方向向量的计算ORB算法的第二步是计算特征描述符。这一步采用的是rBRIEF算法,每个特征描述符是仅包含1和0的长度为128-512位的向量。得到特征点和特征描述符之后,前端2022年美团技术年货速定位其在全局特征中对应的位置。该问题看似可以使用目标检测的相关算法进行求解,但是一般目标检测算法需要目标的类别/语义信息,而我们这里需要匹配的是目标区域的表观特征。针对该问题,我们采用了基于目标检测的图像跟踪算法,即将目标区域视为算法需要跟踪的目标,在全屏截图中找到我们要跟踪的目标。在具体实现过程中,我们使用类似于GlobalTra
28、ck7的算法,首先会提取目标区域对应的特征,并使用目标区域的特征来对全屏截图的特征进行调制,并根据调制之后的特征来对目标区域进行定位。并根据移动端计算量受限的特性,我们在GlobalTrack的基础上设计了一个单阶段的目标检测器来对该过程进行加速。GlobalTrack 示意图由于我们直接使用目标区域的特征来引导目标检测的过程,所以其能够处理更为复杂的目标区域,比如纯文本、纯图像或图标、文字图像混编等,凡是能在UI上出现的元素都可能是目标区域,如下图所示的一些示例。目标区域示例与包含不同尺寸类别组合的训练数据结合业务场景,要求针对移动设备上AppUI画面的任何局部区域做到精确定位。如上述的分析
29、,该问题既可以看作是一个目标检测和匹配的问题,又可以看作是一个目标跟踪问题。同时算法需要能够适配不同内容的ROI区域、不同的屏幕分辨率、不前端2022年美团技术年货首先,比较优雅的“黑盒”方案是使用图像相似度对比技术,此能力模型在视觉智能中比较基础,在通过跳转来到目标页面后,会截图与目标特征进行比较,进行快速容错。根据线下的大量测试数据,去除一些极端情况,我们发现在不同的阈值下是有规律的:相似度80%以上的区间,基本可以确定目标页面准确,受一些角标或图片区块加载的影响没达到更高。相似度60%80%的区间,是在一些列表样式或背景图、Banner 图有些许差异导致的,可以模糊判定命中(上报数据但不
30、用上报异常)。相似度40%60%的区间,大概率遇到了对应模块的UI界面改版,或者有局部弹窗,这时就需要进行一些重试策略适时上报异常。相似度40%以下,基本确定跳转的是错误页面,可以直接终止引导流程,并上报异常。图片相似度部分 Case 实测效果同时,我们在端侧也有一些判定规则来辅助图像对比的决策,比如容器路由URL比对,当图像对比不匹配但容器路由URL准确时,会有一些策略调整并进行重试逻辑。在确认页面准确后,才会进行高亮区域寻找以及后续的绘制逻辑。最后兜底可以通过超时失败的方式自然验证,一个剧本关键帧的完整判定流程,我们设置了5秒的超时策略。前端2022年美团技术年货对目标区域造成遮挡,影响整
31、个引导流程,需要对各类弹窗进行过滤和拦截。针对Native技术栈,我们通过对统一弹窗组件进行拦截,判断执行过程中禁止弹窗弹出,引导过程中业务认为非常重要的弹窗则通过加白处理。在Flutter上则采用全局拦截NavigatorObserver中的 didPush过程,拦截及过滤Flutter的各类Widget、Dialog及Alert弹窗。关于Web上的处理,由于Web弹窗业务方比较多,没有特别统一的弹窗规范,特征比较难取;目前是在Web容器中注入一段JavaScript代码,给部分有弹窗特征和指定类型的组件设置隐藏,考虑到拓展性,JavaScript代码设置成可动态更新。对于部分页面元素复杂导
32、致加载时间稍长的场景,剧本播放时也会基于录制侧提供的delayInfo字段,进行一些延迟判定策略。基于前面的努力,剧本的执行链路成功率(如下图所示)基本可以达到98%以上,部分成功率较低的剧本可以根据维度下钻,查询具体的异常原因。部分链路指标监控零代码完成剧本创作与编辑把一个剧本的生命周期划分为“生产”和“消费”两个阶段,“生产”阶段对应的是前端2022年美团技术年货标准协议设计标准协议作为“零代码”的基石串联了录制到编辑的整个过程。当前App中,操作类引导场景有数十种,我们通过传输模型和视图模型的结合,将核心字段提取,冗余字段剥离。在保证标准化与兼容性的前提下,将数十种场景抽象为四种通用事件
33、类型,为关键帧的编排及业务场景的覆盖提供了便利。对心智类剧本而言,会随着用户的交互操作不断产生新的分支,最终成为一个复杂且冗余的二叉树结构。我们在设计此类协议时,将二叉树节点进行拍平,存储为一个HashMap,两个关键帧的衔接可以以id为标识。用户在使用App时,在某些需求指引下,会产生心智类和操作类剧本引导交替出现的情况。例如,商家(用户)打开推广页面后,出现一个心智类剧本小袋动画伴随着语音:“老板好,小袋发现您开店3个月了,还没有使用过门店推广功能呢,请问您是不会操作还是担心推广效果不明显呢?”屏幕中会伴随两个按钮选择(1)不会操作;(2)担心推广效果。此时,如果用户点击了 1,会转向“操
34、作类”剧本,所以我们在设计协议时,要尤为关注两种剧本的衔接。在这里,我们将协议进行了细化,将基础能力协议与展示类协议进行拆分。两种剧本共用一套基础能力协议,防止出现兼容性的问题。部分协议节点设计管理后台的编辑器引擎解析剧本协议后,完成内置逻辑的初始化,以及引导剧本中事件关键帧的渲染。编辑器引擎内部基于事件机制实现了可订阅的能力,当关键帧触发插入、编辑、调整顺序等事件时,所有其他的关键帧都可以订阅以上核心事件,实现完整的联动效果。编辑加工后的剧本协议,通过接入美团统一的动态下发平台,实现前端2022年美团技术年货场景以拟人化的方式给用户传递平台的制度与规范,让用户更容易接受平台的理念进而遵守经营
35、的规范;比如可以在商家阅读差评时,执行一个情感化剧本(大概内容为差评是普遍现象,每xx条订单就容易产生一条差评,所以不用过于担心,平台也有公正的差评防护与差评申诉规则);如果商家出现违规经营,也可以执行一个概念强化的严肃类剧本(大概内容为平台非常公正且有多重检查措施,不要试图在申诉中上传不实材料侥幸过关)。值得一提的是,在这个过程中产出的图像特征定位、去 Alpha 通道的视频动画等能力也完成了技术储备,可以提供给其他场景使用。前文核心技术内容也申请了两项国家发明专利。部分业务线上效果新店成长计划,是剧本式引导应用的首个大需求。支撑新店成长计划项目顺利上线,目前的结果非常正向。ASG支撑了整个
36、项目78.1%的引导播放量,单个剧本开发成本0.5d。综合观测指标“商家任务完成度”同比观测周期内,从18%提升至35.7%,其他过程指标也有不同程度的提升。超值换购,提供了心智类指引,是指导商家用最优的方式创建换购活动,结合过程数据预估访购率从4%提升至5.5%,活动商家订单渗透率从2.95%提升至4%,均有35%左右的涨幅。配送信息任务引导,优化配送信息任务整体流程的行动点引导,避免引起商家行为阻塞,降低了用户操作成本与理解成本,提升商家在开门营业阶段满意度,同时提升商家对配送服务的认知度水平。自2021年11月上线以来,ASG已经支撑了新店、活动、营销、广告等多个业务,在美团超过20个业
37、务场景中进行了落地。总体看来,ASG剧本式引导相比于传统引导方案,粗略估计的话,可以用约先前1/10的成本来提升约20%的效果。带入之前的结果测算公式,提效倍数(x=(1/(1-90%))*(1+20%)),就是 12 倍。从最终的结果来看,成本的降低,远比效果提升更加明显,所以本文对前者的论述篇前端2022年美团技术年货结合目前的阶段性成果,我们验证了之前方向的正确性,下一步我们会继续从“更低的生产成本”与“更高的应用效果”两个角度进行深耕(例如组合元素剧本的易用性、剧本更新成本优化、引导时机结合规则引擎与意图猜测、折叠与再次唤醒逻辑等),以支撑更多类似场景的需求。并且,我们欣喜地看到终端的
38、“容器无关性”收益杠杆明显,接下来还有很大的发挥空间。欢迎大家跟我们一起探讨交流。作者简介松涛、尚先、成浩、张雪、庆斌等,来自美团到家研发平台/外卖技术部;筱斌、民钦、德榜等,来自美团基础研发平台/视觉智能部。参考文献1AppAnnie.2022年移动市场报告2HBR.WhenLow-Code/No-CodeDevelopmentWorksandWhenItDoesn t3Google.CompressionTechniques4Apple.Quartz2DProgrammingGuide5E.Karami,S.Prasad,M.Shehata“ImageMatchingUsingSIFT,S
39、URF,BRIEFandORB:PerformanceComparisonforDistortedImages”NewfoundlandElectricalandComputerEngineeringConference,St.johns,Canada,October,20176JiayiMa,JiZhao,JunjunJiang,HuabingZhou,andXiaojieGuo.“LocalityPreservingMatching”,InternationalJournalofComputerVision,127(5),pp.512-531,May2019.7Huang,Lianghua
40、,etal.Globaltrack:Asimpleandstrongbaselineforlong-termtrackingC/ProceedingsoftheAAAIConferenceonArtificialIntelligence.2020,34(07):11037-11044.前端2022年美团技术年货图 2外卖迭代模型另一方面,相比于 2018 年,2022 年的测试用例数量增长近 3 倍,已经超过 1 万 2千条(如下图所示)。同时,外卖的业务是“三端复用”,除了外卖 App,还需要集成到美团 App 和大众点评 App 上,这样一来,测试工作量就翻了 3 倍,业务测试压力之大可想
41、而知。如果按照当前的增长趋势持续下去,要保障外卖业务的稳定,就必须持续不断地投入大量的人力成本,所以引入能够支持外卖“多业务场景”、“多 App复用”、“多技术栈”特点的自动化测试工具来提升人效和质量,势在必行。图 3近几年用例增长变化前端2022年美团技术年货AppiumAirtestProjectSoloPi脚本语言支持 Python,Java,JavaS-cript,PHP,C#,Ruby,OC 等Python/数据记录(网络/本地)不支持不支持不支持环境模拟不支持不支持不支持上手难度高,需要各种环境支持和语言学习一般,不熟悉编程语言,也可以一定程度使用低,用例即操作,不展示问题溯源成本
42、高高高维护成本高高高视图检索基于 UI 控件的检索,支持 10 多种 UI 控件查找方式基于图像识别和基于UI 控件检索两种方式基于图像识别和基于UI 控件检索两种方式源码集成无需可选无需WebView 支持支持支持支持用例编辑支持支持支持平台支持iOS、Android、WindowsiOS、Android、Win-dows、游戏测试AndroidAppium 是一个开源工具,用于自动化测试 iOS 手机、Android 手机和Windows 桌面平台上的原生、移动Web 和混合应用。它使用了各系统自带的自动化框架,无需 SDK 集成,Appium 把这些系统本身提供的框架包装进一套 APIW
43、ebDriverAPI 中,可以使用任何语言编写 Client 脚本向服务器发送适当的 HTTP 请求。这让不同技术栈的人员都能快速上手编写测试用例,可以选择自己最为熟悉的语言,但是对于没有语言开发基础的人来说,还是有一定学习成本,而且这种方式在多人协作时并没有太大作用,为了保证自动化用例的可维护性,团队内部应该需要统一脚本语言。值得一提的是:Appium 在 iOS、Android 和Windows测试套件之间可做的一定程度的复用代码。但是由于不同端界面及元素定位的差异,这往往是不现实的,更无法保证测试的准确性,所以这种所谓的“跨端”就变得毫无意义。前端2022年美团技术年货4.实践和探索一
44、个自动化测试工具/平台能不能用起来,取决于他的上手成本和稳定性,即使工具的测试稳定性做的再好,使用的门槛高也会让人望而生却,反之亦然。所以AlphaTest 平台为了上手简单,降低使用成本,采用了基于录制回放的方式进行设计,并且弥补了常规录制回放无法编辑的痛点,同时在手势操作的基础上增加了数据录制。整合美团系 App 的特性增加了环境模拟、跨 App 支持、混合技术栈的支持等能力,在使用简单的同时,也保障了用例的可维护性、测试的准确性等。我们先通过视频简单的了解一下:用例录制:用例回放:前端2022年美团技术年货图 4指令编辑器图 5 手机录制演示前端2022年美团技术年货图 7前置条件与其它
45、测试框架不同的是,AlphaTest 采用了 SDK 集成,但对业务无侵入的方式,因此可以通过编写白盒代码来实现前置条件的自动配置,只需要在平台添加需要的指令,下发到 SDK 后,即可根据相关指令完成前置条件的自动配置,不再需要重复进行相关的操作。并且这些前置条件支持复用,也不需要每次进行用例准备时的重复配置。AlphaTest 的前置条件,不仅有着基于美团内部服务及底层 Hook 的默认实现,也提供了 API 支持业务方自定义实现,比如实现不同的账号体系。4.3用例录制与回放的数据一致性影响用例执行的不仅是代码,还有数据。很多时候,自动化用例无法正常执行完成,可能是因为 App 回放时的本地
46、数据及网络数据与录制时的不一致,从而导致用例执行流程的阻塞或 App 界面展示的不同。前端2022年美团技术年货工具/平台的可用性。目标控件定位准确性:操作行为是否一致首先需要确认操作目标是否一致。与一般测试工具/平台不同的是AlphaTest 采用了 ViewPath+图像+坐标的多重定位方案。得益于 SDK 集成的方式,我们的 ViewPath 可以记录更多的元素视图特征和执行不同的匹配策略。定位过程中会优先使用 ViewPath 进行目标控件检索,当目标控件查找异常时,会结合图像匹配和坐标匹配的方式进行兜底查找,来确保界面变化程度不大时,也能准确的查找到目标控件。图 9图像识别示意图前端
47、2022年美团技术年货图 10手势识别示意图4.5 可溯源的自动化测试测试全流程记录,问题溯源一键即达。测试的目的是保证 App 运行的稳定,测试过程中出现 Bug 导致测试未通过时,需要溯源问题原因,发生的场景,乃至具体的执行步骤。这也是大多自动化测试工具/平台所欠缺的,即使发现了问题,排查工作也很困难;这个问题在手工测试的时候,更为严重,往往因为很多缺陷无法复现而难以定位。AlphaTest 的自动化用例最小执行单元是操作指令,我们将测试过程的每一条指令的执行状况和过程中的界面快照进行了记录,并在指令执行失败时,对异常原因进行了初步分析。然后将整个用例的执行组合成了一份完整的测试报告,可快
48、速溯源问题步骤。除此之外,我们还增加大量的日志上报,并将整个用例测试过程进行了视频录制,来进一步帮助疑难问题的排查。真正做到了用例回放测试可溯源。前端2022年美团技术年货4.6用例的维护自动化用例需要持续地投入人力来维护么?架构升级,页面重构,用例需要全部重新录制么?因自动化工具/平台众多,阻碍长期落地使用的一大问题是用例维护成本高,很多工具/平台让我们即便是使用上了自动化,但还需要持续投入人力维护用例的更新,最终的提效收益微乎其微。对于用例更新维护,我们可以梳理划分成三个场景:需求发生重大变更,整体的业务执行流程及相关的校验点都需要进行大量的调前端2022年美团技术年货图 15自修复能力4
49、.7跨 App 回放用例同一份代码运行在不同的 App 上,是否需要重新编写多份用例?美团系的一些业务可能会复用在多个 App 上。比如外卖有独立 App,但同时也要复用到美团和点评 App 上,这些功能,几乎共用一份代码,而测试人员却不得不对每个 App 上的业务功能都进行测试,维护多份用例。由于业务本身实现是一致的,那我们可以通过适配不同 App 之间的差异,来让一个业务 Case 可以横跨多个 App 回放,这便可以将成本缩减好几倍,这些差异主要体现在:前置条件和初始页面:业务的初始页面进入路径不同,例如外卖 App 打开App 就进入到了外卖上海品茶,但是在美团 App 中就需要从美团上海品茶
50、跳转到外卖频道。同时由于不同 App 的样式风格、设计规范、业务特性等因素,也会造成上海品茶代码逻辑和视图层级的差异。前端2022年美团技术年货图 17埋点上报数据控制台打印埋点时机校验:针对时机校验,程序并不支持埋点曝光的”1px 曝光”,”下拉刷新曝光”,”页面切换曝光”,”切前后台曝光”这些规则,主要的原因是每一个业务方在对埋点曝光的规则都是不一致的,而且该规则的实现会极大耦合业务代码。在针对时机校验我们目前只支持:1点击埋点上报时机校验,程序通过事件监听和埋点类型信息来判断点击埋点上报的时机是否是在点击的操作下产生的,如果不是则报错。2埋点重复上报校验,针对一般情况下用户一次操作不会产生两
51、个相同的埋点上报,所以程序会校验某个事件下发生的所有埋点日志进行一一校验,检测是否具有 2 个或多个埋点日志完全一致,如有发生则会上报错误。结果校验:回放完成后,我们会对比录制和回放时的埋点数据,根据配置好的字段规则校验埋点上报是否符合预期。前端2022年美团技术年货化用例并将结果报告推送给相关负责人。图 19建设自动化测试流程闭环录制用例:1首先在 AlphaTest 平台选择要录制的测试用例,打开待测试 App 进行扫码即可进入用例待录制状态,此时可以设置用例需要的前置条件(账号信息、Mock 数据、定位信息等),之后点击开始按钮后,手机便会自动重启,开始录制。2用户按照测试用例步骤,正常
52、操作手机,AlphaTest 会将用户的操作行为全部记录下来,并自动生成语义化的描述语言显示在 AlphaTest 平台上,与此同时产生的网络数据、埋点数据等校验信息也会一并存储下来。3在录制的过程中可以快捷的打开断言模式,将页面上想要校验的元素进行文本提取/截图等操作记录下来,用于后续回放过程中对相同元素进行校验。4测试步骤全都执行完毕后,点击保存按钮即可生成本条自动化用例。前端2022年美团技术年货5.2回放集群调度整个测试过程真正的解放双手,才能算的上是自动化。因此,我们着手搭建了自己的自动化机器集群,可以24 小时不间断的执行测试任务。为了保证任务回放能够顺利完成,我们在不同阶段增加了
53、相应的保活策略。在极大程度上提高了任务执行完毕的成功率。执行流程:回放任务通过用户在平台手动触发或者二轮自动触发。新增的回放任务经过任务拆分系统拆分成 n 个子任务,加入到不同设备的回放任务队列中。每个子任务经过占用设备-安装待测 App-应用授权-打开scheme-上报结果等步骤完成回放操作。节点保活机制:针对回放流程中每一个节点,失败后进行 N(默认为 3)次重试操作。减少因网络波动,接口偶现异常导致的回放失败数量。子任务保活机制:每个回放流程,失败后进行 N(默认为 3)次断点重试。减少因设备异常,SDK 心跳上报异常导致的回放失败数量。父任务保活机制:一个父任务会被拆分成 N 个子任务
54、,当其中的一个子任务S1 在节点保活机制和子任务保活机制下仍然执行失败之后,父任务保活机制会尝试将子任务 S1 中未执行完毕的用例转移到其他活跃状态的子任务中。减少因设备异常,设备掉线等问题导致的回放失败数量。图 21机器集群前端2022年美团技术年货图像服务:图像对比模型采用基于度量学习的对比算法,将图像对的一致性判别转换为图像语义的相似度量问题。度量学习(MetricLearning),也称距离度量学习(DistanceMetricLearning,DML)属于机器学习的一种。其本质就是相似度的学习,也可以认为距离学习。因为在一定条件下,相似度和距离可以相互转换。比如在空间坐标的两条向量,
55、既可以用余弦相似度的大小,也可以使用欧式距离的远近来衡量相似程度。度量学习的网络采用经典的 Siamese 结构,使用基于 resnext50 的主干网络提取图像的高级语义特征,后接 spplayer 完成多尺度特征融合,融合后的特征输出作为表达图像语义的特征向量,使用 ContrastiveLoss 进行度量学习。图 22训练过程1预训练过程:resnext50 网络是使用 ImageNet 的预训练模型。2数据增强:为增加数据的丰富性、提高网络的泛化性能,数据增强的方式主要包括:图像右下部分的随机剪切和添加黑色蒙层(相应改变图像对的标签)。这种数据增强符合控键截图实际情况,不会造成数据分布
56、的改变。3对比损失:对比损失函数采用 ContrastiveLoss,它是一种在欧式空间的 pairbasedloss,其作用是减少一致图像对距离,保证不一致图像对的距离大于 margin,其中 margin=2。图 23训练过程4相似度量:相似度量也是采用计算图像对特征向量的欧式距离的方法,并归一化前端2022年美团技术年货给测试人员用来进行二轮核心流程回归的时间为三天,为此 C 端测试团队投入了许多人力资源,但即便如此,仍难以覆盖全部流程;而 AlphaTest 的设计初衷也正是为解决此问题UI 测试流程全覆盖及自动化验证。6.1业务共建用例的转化与维护1AlphaTest在外卖 C 端测
57、试团队的落地初期,我们采用了共建的模式,也就是业务研发人员与对应测试人员共同来进行用例录制与维护的工作;推荐这种工作模式的核心原因是,在 C 端功能迭代流程中的二轮周期的原有工作模式为,研发人员进行二轮冒烟测试,完成测试之后提交二轮包交由测试人员进行二轮回归测试,所以这本来就是一个双方都需要参与的环节;而二轮测试作为版本上线前的最重要一个测试流程,保证核心流程的正常也是测试人员与研发人员所关心重点。2经过多轮的使用与磨合之后,这种模式被证明是行之有效的,在整个 C 端二轮用例的转化过程中,测试人员主要负责了用例的录制与迭代流程,研发人员则主要负责版本回放数据的统计及问题用例的发现与解决。外卖二
58、轮落地情况1目前,AlphaTest 已经在外卖多个业务落地,支持了大于 15 个版本的二轮回归测试,用例覆盖率达到 70%。2覆盖了 Native、Mach、ReactNative、美团小程序、H5技术栈的测试工作,能力上可进行支持:UI 自动化测试、埋点自动化测试、动态化加载成功率自动化测试、无障碍适配率自动化测试。未来,我们会朝着“智能化”和“精准化”两个方向探索,覆盖更多测试场景的同时,更进一步提升测试人效。前端2022年美团技术年货深入理解函数式编程(上)作者:俊杰前言本文分为上下两篇,上篇讲述函数式编程的基础概念和特性,下篇讲述函数式编程的进阶概念、应用及优缺点。函数式编程既不是简
59、单的堆砌函数,也不是语言范式的终极之道。我们将深入浅出地讨论它的特性,以期在日常工作中能在对应场景中进行灵活应用。1.先览:代码组合和复用在前端代码中,我们现有一些可行的模块复用方式,比如:图 1前端2022年美团技术年货函数式编程是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,演算是该语言最重要的基础。而且 演算的函数可以接受函数作为输入的参数和输出的返回值。我们可以直接读出以下信息:1.避免状态变更2.函数作为输入输出3.和 演算有关那什么是 演算呢?2.2函数式编程起源:演算 演算(读作 lambda 演算)由数学家阿隆佐邱奇在 20 世纪 30 年代首
60、次发表,它从数理逻辑(Mathematicallogic)中发展而来,使用变量绑定(binding)和代换规则(substitution)来研究函数如何抽象化定义(define)、函数如何被应用(apply)以及递归(recursion)的形式系统。演算和图灵机等价(图灵完备,作为一种研究语言又很方便)。通常用这个定义形式来表示一个 演算。图3所以 演算式就三个要点:1.绑定关系。变量任意性,x、y 和 z 都行,它仅仅是具体数据的代称。2.递归定义。项递归定义,M 可以是一个 项。前端2022年美团技术年货图 5上面的演算式表示有一个函数 f 和一个参数 x。令 0 为 x,1 为 fx,2
61、 为 ffx什么意思呢?这是不是很像我们数学中的幂:ax(a 的 x 次幂表示 a 对自身乘 x次)。相应的,我们理解上面的演算式就是数字 n 就是 f 对 x 作用的次数。有了这个数字的定义之后,我们就可以在这个基础上定义运算。图 6其中 SUCC 表示后继函数(+1 操作),PLUS 表示加法。现在我们来推导这个正确性。前端2022年美团技术年货图 8第一次函数调用传入 m=5,返回一个新函数,这个新函数接收一个参数 n,并返回 m+n 的结果。像这种情况产生的上下文,就是 Closure(闭包,函数式编程常用的状态保存和引用手段),我们称变量 m 是被绑定(binding)到了第二个函数
62、的上下文。除了绑定的变量,演算也支持自由的变量,比如下面这个 y:图 9这里的 y 是一个没有绑定到参数位置的变量,被称为一个自由变量。绑定变量和自由变量是函数的两种状态来源,一个可以被代换,另一个不能。实际程序中,通常把绑定变量实现为局部变量或者参数,自由变量实现为全局变量或者环境变量。前端2022年美团技术年货图 112.3JavaScript 中的 表达式:箭头函数ECMAScript2015 规范引入了箭头函数,它没有 this,没有 arguments。只能作为一个表达式(expression)而不能作为一个声明式(statement),表达式产生一个箭头函数引用,该箭头函数引用仍然
63、有 name 和 length 属性,分别表示箭头函数的名字、形参(parameters)长度。一个箭头函数就是一个单纯的运算式,箭头函数我们也可以称为 lambda 函数,它在书写形式上就像是一个 演算式。图 12可以利用箭头函数做一些简单的运算,下例比较了四种箭头函数的使用方式:前端y=x(y)可以有两种理解,一种是 x=y 函数传入 X=x(y),另一种是 x 传入 y=x(y)。add_x 类型表明,一个运算式可以有很多不同的路径来实现。上面的 add_1/add_2/add_3 我们用到了 JavaScript 的立即运算表达式 IIFE。演算是一种抽象的数学表达方式,我们不关心真实
64、的运算情况,我们只关心这种运算形式。因此上一节的演算可以用 JavaScript 来模拟。下面我们来实现 演算的JavaScript 表示。962022年美团技术年货图 15我们把 演算中的 f 和 x 分别取为 countTime 和 x,代入运算就得到了我们的自然数。这也说明了不管你使用符号系统还是 JavaScript 语言,你想要表达的自然数是等价的。这也侧面说明 演算是一种形式上的抽象(和具体语言表述无关的抽象表达)。2.4函数式编程基础:函数的元、柯里化和 Point-Free回到 JavaScript 本身,我们要探究函数本身能不能带给我们更多的东西?我们在JavaScript
65、中有很多创建函数的方式:前端2022年美团技术年货2.4.1函数的元:完全调用和不完全调用不论使用何种方式去构造一个函数,这个函数都有两个固定的信息可以获取:name表示当前标识符指向的函数的名字。length表示当前标识符指向的函数定义时的参数列表长度。在数学上,我们定义 f(x)=x 是一个一元函数,而 f(x,y)=x+y 是一个二元函数。在 JavaScript 中我们可以使用函数定义时的 length 来定义它的元。图 17定义函数的元的意义在于,我们可以对函数进行归类,并且可以明确一个函数需要的确切参数个数。函数的元在编译期(类型检查、重载)和运行时(异常处理、动态生成代码)都有重
66、要作用。如果我给你一个二元函数,你就知道需要传递两个参数。比如+就可以看成是一个二元函数,它的左边接受一个参数,右边接受一个参数,返回它们的和(或字符串连接)。在一些其他语言中,+确实也是由抽象类来定义实现的,比如 Rust 语言的 traitAdd。但我们上面看到的 演算,每个函数都只有一个元。为什么呢?只有一个元的函数方便我们进行代数运算。演算的参数列表使用 x.y.z 的格前端a-a),得到另一个函数add1(类型为 a-a)。图 19第三个,上一个例子的 JavaScript 版本。1002022年美团技术年货图 20“不完全调用”在 JavaScript 中也成立。实际上它就是 Ja
67、vaScript 中闭包(Clo-sure,上面我们已经提到过)产生的原因,一个函数还没有被销毁(调用没有完全结束),你可以在子环境内使用父环境的变量。注意,上面我们使用到的是一元函数,如果使用三元函数来表示 addThree,那么函数一次性就调用完毕了,不会产生不完全调用。函数的元还有一个著名的例子(面试题):图 21造成上述结果的原因就是,Number 是一元的,接受 map 第一个参数就转换得到返回值;而 parseInt 是二元的,第二个参数拿到进制为 1(map 函数为三元函数,第二个参数返回元素索引),无法输出正确的转换,只能返回 NaN。这个例子里面涉及到了一元、二元、三元函数,
68、多一个元,函数体就多一个状态。如果世界上只有一元函数就好了!我们可以全通过一元函数和不完全调用来生成新的函数处理新的问题。前端2022年美团技术年货函数调用变成延迟的偏函数(不完全调用函数)调用。这在函数组合、复用等场景非常有用。比如:图 24虽然你可以用其他闭包方式来实现函数的延迟调用,但 Curry 函数绝对是其中形式最美的几种方式之一。2.4.3Point-Free 无参风格:函数的高阶组合函数式编程中有一种 Point-Free 风格,中文语境大概可以把 point 认为是参数点,对应 演算中的函数应用(FunctionApply),或者 JavaScript 中的函数调用(Funct
69、ionCall),所以可以理解 Point-Free 就指的是无参调用。来看一个日常的例子,把二进制数据转换为八进制数据:前端b)在每个节点的形态(关心数据的流向)。有没有一种方式,可以让我们只关心入参和出参,不关心数据流动过程呢?图 26上面的方法假设我们已经有了一些基础函数 toBinary(语义化,消除魔法数字 2)和toStringOx(语义化,消除魔法数字 8),并且有一种方式(pipe)把基础函数组合(Composition)起来。如果用 Typescript 表示我们的数据流动就是:1042022年美团技术年货图 27用 Haskell 表示更简洁,后面都用 Haskell 类型
70、表示方式,作为我们的符号语言。图 28值得注意的是,这里的 x-int-y 我们不用关心,因为 pipe(.)函数帮我们处理了它们。pipe 就像一个盒子。图 29BOX 内容不需要我们理解。而为了达成这个目的,我们需要做这些事:utils一些特定的工具函数。前端2022年美团技术年货TypeInference类型推导:如果无法确定数据的类型,那函数怎么去组合?(常见,但非必需)LazyEvaluation惰性求值:函数天然就是一个执行环境,惰性求值是很自然的选择。SideEffectIO:一种类型,用于处理副作用。一个不能执行打印文字、修改文件等操作的程序,是没有意义的,总要有位置处理副作用
71、。(边缘)数学上,我们定义函数为集合 A 到集合 B 的映射。在函数式编程中,我们也是这么认为的。函数就是把数据从某种形态映射到另一种形态。注意理解“映射”,后面我们还会讲到。图 30前端2022年美团技术年货图 32这个 add 函数是不纯的,但我们把副作用都放到它里面了。任意使用这个 add 函数的位置,都将变成不纯的(就像是 async/await 的传递性一样)。需要说明的是抛开实际应用来谈论函数的纯粹性是毫无意义的,我们的程序需要和终端、网络等设备进行交互,不然一个计算的运行结果将毫无意义。但对于函数的元来说,这种纯粹性就很有意义,比如:图 33当函数的元像上面那样是一个引用值,如果
72、一个函数的调用不去控制自己的纯粹性,对别人来说,可能会造成毁灭性打击。因此我们需要对引用值特别小心。前端2022年美团技术年货2.5.4不可变性:把简单留给自己一个函数不应该去改变原有的引用值,避免对运算的其他部分造成影响。图 36一个充满变化的世界是混沌的,在函数式编程世界,我们需要强调参数和值的不可变性,甚至在很多时候我们可以为了不改变原来的引用值,牺牲性能以产生新的对象来进行运算。牺牲一部分性能来保证我们程序的每个部分都是可预测的,任意一个对象从创建到消失,它的值应该是固定的。一个元如果是引用值,请使用一个副本(克隆、复制、替代等方式)来得到状态变更。2.5.5高阶:函数抽象和组合JS
73、中用的最多的就是 Array 相关的高阶函数。实际上 Array 是一种 Monad(后面讲解)。图 37前端2022年美团技术年货图 39map(addOne)并不会真实执行+1,只有真实调用 exec 才执行。当然这个只是一个简单的例子,强大的惰性计算在函数式编程语言中还有很多其他例子。比如:图 40“无穷”只是一个字面定义,我们知道计算机是无法定义无穷的数据的,因此数据在take 的时候才真实产生。惰性计算让我们可以无限使用函数组合,在写这些函数组合的过程中并不产生调用。但这种形式,可能会有一个严重的问题,那就是产生一个非常长的调用栈,而虚拟机或者解释器的函数调用栈一般都是有上限的,比如
74、 2000 个,超过这个限制,函数调用就会栈溢出。虽然函数调用栈过长会引起这个严重的问题,但这个问题其实不是函数式语言设计的逻辑问题,因为调用栈溢出的问题在任何设计不良的程序中都有可能出现,惰性计算只是利用了函数调用栈的特性,而不是一种缺陷。记住,任何时候我们都可以重构代码,通过良好的设计来解决栈溢出的问题。前端2022年美团技术年货3.小结函数式有很多基础的特性,熟练地使用这些特性,并加以巧妙地组合,就形成了我们的“函数式编程范式”。这些基础特性让我们对待一个 function,更多地看作函数,而不是一个方法。在许多场景下,使用这种范式进行编程,就像是在做数学推导(或者说是类型推导),它让我
75、们像学习数学一样,把一个个复杂的问题简单化,再进行累加/累积,从而得到结果。同时,函数式编程还有一块大的领域需要进入,即副作用处理。不处理副作用的程序是毫无意义的(仅在内存中进行计算),下篇我们将深入函数式编程的应用,为我们的工程应用发掘出更多的特性。4.作者简介俊杰,美团到家研发平台/医药技术部前端工程师。前端2022年美团技术年货2.本文简介本文通过深入函数式编程的副作用处理及实际应用场景,提供一个学习和使用函数式编程的视角给读者。一方面,这种副作用管理方式是一种高级的抽象形式,不易理解;另一方面,我们在学习和使用函数式编程的过程中,几乎都会遇到类似的副作用问题需要解决,能否解决这个问题也
76、决定了一门函数式编程语言最终是否能走上成功。本文主要分为三个部分:副作用处理方式函数式编程的应用函数式编程的优缺点比较3.副作用处理:单子 Monad,一种不可避免的抽象上面说的,都是最基础的 JavaScript 概念+函数式编程概念。但我们还留了一个“坑”。如何去处理 IO 操作?我们的代码经常在和副作用打交道,如果要满足纯函数的要求,几乎连一个需求都完成不了。不用急,我们来看一下 ReactHooks。ReactHooks 的设计是很巧妙的,以 useEffect 为例:图 43前端2022年美团技术年货3.1什么是 Monad?先思考一个问题,下面两个定义有什么区别?图 45num1
77、是数字类型,而 num2 是对象类型,这是一个直观的区别。不过,不仅仅如此。利用类型,我们可以做更多的事。因为作为数字的 num1 是支持加减乘除运算的,而 num2 却不行,必须要把它视为一个对象 val:2,并通过属性访问符 num2.val 才能进行计算 num2.val+2。但我们知道,函数式编程是不能改变状态的,现在为了计算 num2.val 被改变了,这不是我们期望的,并且我们使用属性操作符去读数据,更像是在操作对象,而不是操作函数,这与我们的初衷有所背离。现在我们把 num2 当作一个独立的数据,并假设存在一个方法 fmap 可以操作这个数据,可能是这样的。前端119图 46得到
78、的还是对象,但操作通过一个纯函数 addOne 去实现了。上面这个例子里面的 Num,实际上就是一个最简单的 Monad,而 fmap 是属于Functor(函子)的概念。我们说函数就是从一个数据到另一个数据的映射,这里的fmap 就是一个映射函数,在范畴论里面叫做态射(后面讲解)。由于有一个包裹的过程,很多人会把 Monad 看作是一个盒子类型。但 Monad 不仅是一个盒子的概念,它还需要满足一些特定的运算规律(后面涉及)。但是我们直接使用数字的加减乘除不行吗?为什么一定要 Monad 类型?首先,fmap 的目的是把数据从一个类型映射到另一个类型,而 JavaScript 里面的map
79、函数实际上就是这个功能。图 47我 们 可 以 认 为 Array 就 是 一 个 Monad 实 现,map 把 Array 类 型 映 射到 Array 类型,操作仍然在数组范畴,数组的值被映射为新的值。如果用TypeScript 来表示,会不会更清晰一点?1202022年美团技术年货图 48看起来 Monad 只是一个实现了 fmap 的对象(Functor 类型,mappable 接口)而已。但 Monad 类型不仅是一个 Functor,它还有很多其他的工具函数,比如:bind 函数flatMap 函数liftM 函数这些概念在学习 Haskell 时可以遇到,本文不作过多提及。这些
80、额外的函数可以帮助我们操作被封装起来的值。3.2范畴、群、幺半群范畴论是一种研究抽象数学形式的科学,它把我们的数学世界抽象为两个概念:对象态射为什么说这是一种形式上的抽象呢?因为很多数学的概念都可以被这种形式所描述,前端2022年美团技术年货是范畴对象,而一个三角形的表示变换到另一个形式,就是范畴的态射。而我们说这些三角形表示方式的集合为一个群。群论主要是研究变换关系,群又可以分为很多种类,也有很多规律特性,这不在本文研究范围之内,读者可以自行学习相关内容。科学解释一个 Monad 为自函子范畴上的幺半群。如果没有学习群论和范畴论的话,我们是很难理解这个解释的。图 50简单来说先固定一个正方形
81、 abcd,它和它的几何变换方式(旋转/逆时针旋转/对称/中心对称等)形成的其他正方形一起构成一个群。从这个角度来说,群研究的事物是同一类,只是性质稍有不一样(态射后)。前端2022年美团技术年货3.3Monad 范畴:定律、折叠和链我们要在一个更大的空间上讨论这个范畴对象(Monad)。就像 Number 封装了数字类型,Monad 也封装了一些类型。图 53Monad 需要满足一些定律:结合律:比如 a b c=a (b c)。幺元:比如 a e=e a=a。一旦定义了 Monad 为一类对象,fmap 为针对这种对象的操作,那么定律我们可以很容易证明:前端2022年美团技术年货图 55f
82、old(折叠,对应能力我们称为 foldable)的意义在于你可以将数据从一个特定范畴映射到你的常用范畴,比如面向对象语言的 toString 方法,就是把数据从对象域转换到字符串域。JavaScript 中的 Array.prototype.reduce 其实就是一个 fold 函数,它把数据从Array 范畴映射到其他范畴。一旦数据类型被我们锁定在了 Monad 空间(范畴),那我们就可以在这个范畴内连续调用 fmap(或者其他这个空间的函数)来进行值操作,这样我们就可以链式处理我们的数据。前端2022年美团技术年货图 58这种情况下我们需要去判断 x 和 y 是否为空。在 Monad 空
83、间中,这种情况就很好表示:图 59我们在 Monad 空间中消除了烦人的!=null 判断,甚至消除了三元运算符。一切都只有函数。实际使用中一个 Maybe 要么是 Just 要么是 Nothing。因此,这里用Maybe(.)构造可能让我们难以理解。如果非要理解的话,可以理解 Maybe 为 Nothing 和 Just 的抽象类,Just 和前端2022年美团技术年货图 61这样,我们就可以使用“函数”来替代分支了。这里的 Either 实现比较粗糙,因为Either 类型应该只在 Monad 空间。这里加入了布尔常量的判断,目的是好理解一些。其他的编程语言特性,在函数式编程中也能找到对应
84、的影子,比如循环结构,我们往往使用函数递归来实现。3.5IO 的处理方式终于到 IO 了,如果不能处理好 IO,我们的程序是不健全的。到目前为止,我们的Monad 都是针对数据的。这句话对也不对,因为函数也是一种数据(函数是第一公民)。我们先让 MonadJust 能存储函数。前端2022年美团技术年货这个抽象类我们称为“应用函子”,它可以保存一个函数作为内部值,并且使用apply 方法可以把这个函数作用到另一个 Monad 上。到这里,我们完全可以把Monad 之间的各种操作(接口,比如 fmap 和 apply)视为契约,也就是数学上的态射。现在,如果我们有一个单子叫 IO,并且它有如下表
85、现:图 64我们把这种类型的 Monad 称为 IO,我们在 IO 中处理打印(副作用)。你可以把之前我们学习到的类型合并一下,得到一个示例:前端2022年美团技术年货4.1设计一个请求模块图 66用这种方式构建的模块,组合和复用性很强,你也可以利用 lodash 的其他库对 req做一个其他改造。我们调用业务代码的时候只管传递 params,分支校验和错误检查就教给 validate.js 里面的高阶函数就好了。前端2022年美团技术年货4.3超长文本省略:Ramdajs 为例图 68这个也是常见的前端场景,当文本长度大于 X 时,显示省略号,这个实现使用Ramdajs。这个过程中你就像是在
86、搭积木,很容易就把业务给“搭建”完成了。5.函数式编程库、语言函数式编程的库可以学习:Ramda.js:函数式编程库lodash.js:函数工具immutable.js:数据不可变rx.js:响应式编程partial.lenses:函数工具monio.js:函数式编程工具库/IO 库你可以结合起来使用。下面是 Ramda.js 示例:前端2022年美团技术年货6.1优点除了上面提到的风格和特性之外,函数式编程相对其他编程范式,有很多优点:函数纯净程序有更少的状态量,编码心智负担更小。随着状态量的增加,某些编程范式构建的软件库代码复杂度可能呈几何增长,而函数式编程的状态量都收敛了,对软件复杂度带
87、来的影响更小。引用透明性可以让你在不影响其他功能的前提下,升级某一个特定功能(一个对象的引用需要改动的话,可能牵一发而动全身)。高度可组合函数之间复用方便(需要关注的状态量更少),函数的功能升级改造也更容易(高阶组件)。副作用隔离所有的状态量被收敛到一个盒子(函数)里面处理,关注点更加集中。代码简洁/流程更清晰通常函数式编程风格的程序,代码量比其他编程风格的少很多,这得益于函数的高度可组合性以及大量的完善的基础函数,简洁性也使得代码更容易维护。语义化一个个小的函数分别完成一种小的功能,当你需要组合上层能力的时候,基本可以按照函数语义来进行快速组合。惰性计算被组合的函数只会生成一个更高阶的函数,
88、最后调用时数据才会在函数之间流动。跨语言统一性不同的语言,似乎都遵从类似的函数式编程范式,比如 Java8的 lambda 表达式,Rust 的 collection、匿名函数;而面向对象的实现,不同语言可能千差万别,函数式编程的统一性让你可以舒服地跨语言开发。关键领域应用因为函数式编程状态少、代码简洁等特点,使得它在交互复杂、安全性要求高的领域有重要的应用,像 Lisp 和 Haskell 就是因上一波人工智能热而火起来的,后来也在一些特殊的领域(银行、水利、航空航天等)得到了较大规模的应用。前端2022年美团技术年货7.FAQQ:你觉得 Promise 是不是一种 MonadIO 模型?A
89、:我认为是的。纯函数是没有异步概念的,Promise 用了一种很棒的方式把异步和IO 转化为了.then 函数。你仍然可以在.then 函数中写纯粹的函数,也可以在.then函数中调用其他的 Promise,这就和 IO Monad 的行为非常像。Q:你愿意在生产中使用 Haskell/Lisp/Clojure 等纯函数式语言吗?A:不论是否愿意使用,现在很多语言都开始引入函数式编程语法了。并不是说函数式编程一定是优秀的,但它至少没有那么恐怖。有一点可以肯定的是,学习函数式编程可以扩展我们的思维,增加我们看问题的角度。Q:有没有一些可以预见的好处?A:有的。比如强制你写代码的时候去关注状态量(
90、多少、是否引用值、是否变更等),这或多或少可以帮助你写代码的时候减少状态量的使用,也慢慢地能复合一些状态量,写出更简洁的代码。Q:函数式编程能给业务带来什么好处?A:业务拆分的时候,函数式的思维是单向的,我们会通过实现,想到它对应需要的基础组件,并递归地思考下去,功能实现从最小粒度开始,上层逐步通过函数组合来实现。相比于面向对象,这种方式在组合上更方便简洁,更容易把复杂度降低,比如面向对象中可能对象之间的相互引用和调用是没有限制的,这种模式带来的是思考逻辑的时候思维会发散。这种对比在业务复杂的情况下更加明显,面向对象必须要优秀的设计模式来实现控制代码复杂度增长不那么快,而函数式编程大多数情况下
91、都是单向数据流+基础工具库就减少了大量的复杂度,而且产生的代码更简洁。前端2022年美团技术年货Android 对 so 体积优化的探索与实践作者:洪凯常强1.背景应用安装包的体积影响着用户的下载时长、安装时长、磁盘占用空间等诸多方面,因此减小安装包的体积对于提升用户体验和下载转化率都大有益处。Android应用安装包其实是一个zip文件,主要由dex、assets、resource、so等各类型文件压缩而成。目前业内常见的包体积优化方案大体分为以下几类:针对dex的优化,例如Proguard、dex的DebugItem删除、字节码优化等;针对resource的优化,例如AndResGuard
92、、webp优化等;针对assets的优化,例如压缩、动态下发等;针对so的优化,同assets,另外还有移除调试符号等。随着动态化、端智能等技术的广泛应用,在采用上述优化手段后,so在安装包体积中的比重依然很高,我们开始思索这部分体积是否能进一步优化。经过一段时间的调研、分析和验证,我们逐渐摸索出一套可以将应用安装包中so体积进一步减小30%60%的方案。该方案包含一系列纯技术优化手段,对业务侵入性低,通过简单的配置,可以快速部署生效,目前美团App已在线上部署使用。为让大家能知其然,也能知其所以然,本文将先从so文件格式讲起,结合文件格式分析哪些内容可以优化。2.so 文件格式分析so即动态
93、库,本质上是ELF(ExecutableandLinkableFormat)文件。可以从两个维度查看so文件的内部结构:链接视图(LinkingView)和执行视图(ExecutionView)。链接视图将so主体看作多个section的组合,该视图体现的是so是如何组前端2022年美团技术年货在进行优化之前,我们需要对这些section以及它们之间的关系有一个清晰的认识,下图较直观地展示了so中各个section之间的关系(这里只绘制了本文涉及的section):图 1so 文件结构示意图结合上图,我们从另一个角度来理解so文件的结构:想象一下,我们把所有的函数实现体都放到.text中,.t
94、ext中的指令会去读取.rodata中的数据,读取或修改.data和.bss中的数据。看上去so中有这些内容也足够了。但是这些函数怎样执行呢?也就是说,只把这些函数和数据加载进内存是不够的,这些函数只有真正去执行,才能发挥作用。我们知道想要执行一个函数,只要跳转到它的地址就行了。那外界调用者(该so之外的模块)怎样知道它想要调用函数的地址呢?这里就涉及一个函数ID的问题:外部调用者给出需要调用的函数的ID,而动态链接器(Linker)根据该ID查找目标函数的地址并告知外部调用者。所以so文件还需要一个结构去存储“ID-地址”的映射关系,这个结构就是动态符号表的所有导出符号。具体到动态符号表的实
95、现,ID的类型是“字符串”,可以说动态符号表的所有导出符前端2022年美团技术年货用栈的每个栈帧的相应指令在so中的位置,不一定能获取到符号。但是排查崩溃问题时,我们希望得知so崩溃在源码的哪个位置。带调试信息和符号表的so可以将崩溃调用栈的每个栈帧还原成其对应的源码文件名、文件行号、函数名等,大大方便了崩溃问题的排查。所以说,虽然带调试信息和符号表的so不会打包到最终的apk中,但它对排查问题来说非常重要。AGP通过开启strip优化,可以大幅缩减so的体积,甚至可以达到十倍以上。以一个测试so为例,其最终so大小为 14KB,但是对应的带调试信息和符号表的so大小为136KB。不过在使用中
96、,我们需要注意的是,如果AGP找不到对应的strip命令,就会把带调试信息和符号表的so直接打包到apk或aar中,并不会打包失败。例如缺少armeabi架构对应的strip命令时提示信息如下:Unable to strip library XXX.so due to missing strip tool for ABI ARMEABI.Packaging it as is.除了上述Android构建工具默认为so体积做的优化,我们还能做哪些优化呢?首先明确我们优化的原则:对于必须保留的内容考虑进行缩减,减小体积占用;对于无需保留的内容直接删除。基于以上原则,可以从以下三个方面对so继续进行深
97、入优化:精简动态符号表:上文已经提到,动态符号表是so与外部进行连接的“桥梁”,其中的导出表相当于是so对外暴露的接口。哪些接口是必须对外暴露的呢?在Android中,大部分so是用来实现Java的native方法的,对于这种so,只要让应用运行时能够获取到Javanative方法对应的函数地址即可。要实现这个目标,有两种方法:一种是使用RegisterNatives动态注册Javanative方法,一种是按照JNI规范定义java_*样式的函数并导出其符号。RegisterNatives方式可以提前检测到方法签名不匹配的问题,并且可以减少导出符号的数量,这也是Google推荐的做法。所以在最
98、优情况下只需导出前端2022年美团技术年货图 2so 可优化部分在确定了so中可以优化的内容后,我们还需要考虑优化时机的问题:是直接修改so文件,还是控制其生成过程?考虑到直接修改so文件的风险与难度较大,控制so的生成过程显然更稳妥。为了控制so的生成过程,我们先简要介绍一下so的生成过程:图 3so 文件的生成过程如上图所示,so的生成过程可以分为四个阶段:预处理:将include头文件处扩展为实际文件内容并进行宏定义替换。编译:将预处理后的文件编译成汇编代码。前端2022年美团技术年货如下:_attribute_(visibility(“hidden”)int hiddenInt=3;其
99、常用值也是default和hidden,与visibility方式意义类似,这里不再赘述。attribute方式指定的符号可见性的优先级,高于visibility方式指定的可见性,相当于visibility是全局符号可见性开关,attribute方式是针对单个符号的可见性开关。这两种方式结合就能控制源码中每个符号的可见性。需要注意的是上面这两种方式,只能控制变量或函数是否存在于动态符号表中(即是否删除其动态符号表项),而不会删除其实现体。使用static关键字控制符号可见性在 C/C+语言中,static关键字在不同场景下有不同意义,当使用static表示“该函数或变量仅在本文件可见”时,那么
100、这个函数或变量就不会出现在动态符号表中,但只会删除其动态符号表项,而不会删除其实现体。static关键字相当于是增强的hidden(因为static声明的函数或变量编译时只对当前文件可见,而hidden声明的函数或变量只是在动态符号表中不存在,在编译期间对其他文件还是可见的)。在项目开发中,使用static关键字声明一个函数或变量“仅在本文件可见”是很好的习惯,但是不建议使用static关键字控制符号可见性:无法使用static关键字控制一个多文件可见的函数或变量的符号可见性。使用excludelibs移除静态库中的符号上述visibility方式、attribute方式和static关键字,
101、都是控制项目源码中符号的可见性,而无法控制依赖的静态库中的符号在最终so中是否存在。excludelibs就是用来控制依赖的静态库中的符号是否可见,它是传递给链接器的参数,可以使依赖的静态库的符号在动态符号表中不存在。同样,也是只能删除符号表项,实现体仍然会存在于产生的so文件中。前端2022年美团技术年货看上去,versionscript是明确地指定需要保留的符号,如果通过visibility结合attribute的方式控制每个符号是否导出,也能达到versionscript的效果,但是versionscript方式有一些额外的好处:1.versionscript方式可以控制编译进so的静态
102、库的符号是否导出,visibility和attribute方式都无法做到这一点。2.visibility结合attribute方式需要在源码中标明每个需要导出的符号,对于导出符号较多的项目来说是很繁杂的。versionscript把需要导出的符号统一地放到了一起,能够直观方便地查看和修改,对导出符号较多的项目也非常友好。3.versionscript支持通配符,*代表 0 个或者多个字符,?代表单个字符。比如my*;就代表所有以my开头的符号。有了通配符的支持,配置versionscript会更加方便。4.还有非常特殊的一点,versionscript方式可以删除_bss_start这样的一些
103、符号(这是链接器默认加上的符号)。综上所述,versionscript方式优于visibility结合attribute的方式。同时,使用了versionscript方式,就不需要使用excludelibs方式控制依赖的静态库中的符号是否导出了。4.2移除无用代码开启LTOLTO是LinkTimeOptimization的缩写,即链接期优化。LTO能够在链接目标文件时检测出DeadCode并删除它们,从而减小编译产物的体积。DeadCode举例:某个if条件永远为假,那么if为真下的代码块就可以移除。进一步地,被移除代码块所调用的函数也可能因此而变为DeadCode,它们又可以被移除。能够在链
104、接期做优化的原因是,在编译期很多信息还不能确定,只有局部信息,无法执行一些优化。但是链接时大部分信息都确定了,相当于获取了全局信息,所以可以进行一些优化。GCC和Clang均支持LTO。LTO方式编译的目标文件中存储的不再是具体机器的前端2022年美团技术年货section中,一些可读写变量会放到.data样式的section中,等等。链接器会把所有输入的目标文件的同类型的section进行合并,组装出最终的so文件。GCsections参数通知链接器:仅保留动态符号(及.init_array等)直接或者间接引用到的section,移除其他无用section。这样就能减小最终so的体积。但开启
105、GCsections还需要考虑一个问题:编译器默认会把所有函数放到同一个section中,把所有相同特点的数据放到同一个section中,如果同一个section中既有需要删除的部分又有需要保留的部分,会使得整个section都要保留。所以我们需要减小目标文件section的粒度,这需要借助另外两个编译参数-fdata-sections和-ffunction-sections,这两个参数通知编译器,将每个变量和函数分别放到各自独立的section中,这样就不会出现上述问题了。实际上Android编译目标文件时会自动带上-fdata-sections和-ffunction-sections参数,
106、这里一并列出来,是为了突出它们的作用。CMake项目的配置方式:set(CMAKE_C_FLAGS“$CMAKE_C_FLAGS-fdata-sections-ffunction-sections”)set(CMAKE_CXX_FLAGS“$CMAKE_CXX_FLAGS-fdata-sections-ffunction-sections”)set(CMAKE_SHARED_LINKER_FLAGS“$CMAKE_SHARED_LINKER_FLAGS-Wl,-gc-sections”)ndk-build项目的配置方式:LOCAL_CFLAGS+=-fdata-sections-ffuncti
107、on-sectionsLOCAL_LDFLAGS+=-Wl,-gc-sections4.3优化指令长度使用Oz/Os优化级别编译器根据输入的-Ox参数决定编译的优化级别,其中O0表示不开启优化(这种情况主要是为了便于调试以及更快的编译速度),从O1到O3,优化程度越来越强。Clang和GCC均提供了Os的优化级别,其与O2比较接近,但是优化了生成产物前端2022年美团技术年货CMake项目的配置方式:set(CMAKE_CXX_FLAGS“$CMAKE_CXX_FLAGS-fno-rtti”)ndk-build默认会禁用C+的RTTI机制,因此无需特意禁用(如果现有项目开启了C+的RTTI机制
108、,说明确有需要,需仔细确认后才能禁用)。合并so以上都是针对单个so的优化方案,对单个so进行优化后,还可以考虑对so进行合并,能够进一步减小so的体积。具体来讲,当安装包内某些so仅被另外一个so动态依赖时,可以将这些so合并为一个so。例如liba.so和libb.so仅被libx.so动态依赖,可以将这三个so合并为一个新的libx.so。合并so有以下好处:1.可以删除部分动态符号表项,减小so总体积。具体来讲,就是可以删除liba.so和libb.so的动态符号表中的所有导出符号,以及libx.so的动态符号表中从liba.so和libb.so中导入的符号。2.可以删除部分PLT表项
109、和GOT表项,减小so总体积。具体来讲,就是可以删除libx.so中与liba.so、libb.so相关的PLT表项和GOT表项。3.可以减轻优化的工作量。如果没有合并so,对liba.so和libb.so做体积优化时需要确定libx.so依赖了它们的哪些符号,才能对它们进行优化,做了so合并后就不需要了。链接器会自动分析引用关系,保留使用到的所有符号的对应内容。4.由于链接器对原liba.so和libb.so的导出符号拥有了更全的上下文信息,LTO优化也能取得更好的效果。可以在不修改项目源码的情况下,在编译层面实现so的合并。提取多so共同依赖库上面“合并so”是减小so总个数,而这里是增加
110、so总个数。当多个so以静态方式依赖了某个相同的库时,可以考虑将此库提取成一个单独的so,原来的几个so改前端2022年美团技术年货有通用性。至此,我们总结出一套可行的so体积优化方案。但在工程实践中,还有一些问题要解决。5.工程实践支持多种构建工具美团有众多业务使用了so,所使用的构建工具也不尽相同,除了上述常见的CMake和ndk-build,也有项目在使用Make、Automake、Ninja、GYP和GN等各种构建工具。不同构建工具应用so优化方案的方式也不相同,尤其对大型工程而言,配置复杂性较高。基于以上原因,每个业务自行配置so优化方案会消耗较多的人力成本,并且有配置无效的可能。为
111、了降低配置成本、加快优化方案的推进速度、保证配置的有效性和正确性,我们在构建平台上统一支持了so的优化(支持使用任意构建工具的项目)。业务只需进行简单的配置即可开启so的体积优化。配置导出符号的注意事项注意事项有以下两点:1.如果一个so的某些符号,被其他so通过dlsym方式使用,那么这些符号也应该保留在该so的导出符号中(否则会导致运行时异常)。2.编写version_script.txt时需要注意C+等语言对符号的修饰,不能直接把函数名填写进去。符号修饰就是把一个函数的命名空间(如果有)、类名(如果有)、参数类型等都添加到最终的符号中,这也是C+语言实现重载的基础。有两种方式可以把C+的
112、函数添加到导出符号中:第一种是查看未优化so的导出符号表,找到目标函数被修饰后的符号,然后填写到version_script.txt中。例如有一个MyClass类:前端2022年美团技术年货化效果是否符合预期。在Mac和Linux下均可使用下述命令查看so保留了哪些导出符号:nm-D-defined-only xxx.so例如:图 5nm 命令查看 so 文件的导出符号可以看出,libexample.so的导出符号有两个:JNI_OnLoad和Java_com_exam-ple_MainActivity_stringFromJNI。解析崩溃堆栈本文的优化方案会移除非必要导出的动态符号,那so如
113、果发生崩溃的话是不是就无法解析崩溃堆栈了呢?答案是完全不会影响崩溃堆栈的解析结果。“so可优化内容分析”一节已经提过,使用带调试信息和符号表的so解析线上崩溃,是分析so崩溃的标准方式(这也是Google解析so崩溃的方式)。本文的优化方案并未修改调试信息和符号表,所以可以使用带调试信息和符号表的so对崩溃堆栈进行完整的还原,解析出崩溃堆栈每个栈帧对应的源码文件、行号和函数名等信息。业务编译出release版的so后将相应的带调试信息和符号表的so上传到crash平台即可。6.方案收益优化so对安装包体积和安装后占用的本地存储空间有直接收益,收益大小取决于原so冗余代码数量和导出符号数量等具体
114、情况,下面是部分so优化前后占用安装包体积的对比:前端2022年美团技术年货8.参考资料https:/www.cs.cmu.edu/afs/cs/academic/class/15213-f00/docs/elf.pdfhttps:/llvm.org/docs/LinkTimeOptimization.htmlhttps:/gcc.gnu.org/onlinedocs/gccint/LTO-Overview.htmlhttps:/sourceware.org/binutils/docs/ld/VERSION.htmlhttps:/clang.llvm.org/docshttps:/gcc.gn
115、u.org/onlinedocs/gcc9.本文作者洪凯、常强,来自美团平台/App 技术部。前端2022年美团技术年货时效性:当CDN出现问题时,SRE会手动进行CDN切换,因为需要人为操作,响应时长就很难保证。另外,切换后故障恢复时间也无法准确保障。有效性:切换至备份CDN后,备份CDN的可用性无法验证,另外因为LocalDNS缓存,无法解决域名劫持和跨网访问等问题。精准性:CDN的切换都是大范围的变更,无法针对某一区域或者某一项目单独进行。风险性:切换至备份CDN之后可能会导致回源,流量剧增拖垮源站,从而引发更大的风险。当前,美团外卖业务每天服务上亿人次,即使再小的问题在巨大的流量面前,
116、也会被放大成大问题。外卖的动态化架构,70%的业务资源都依赖于CDN,所以CDN的可用性严重影响着外卖业务。如何更有效的进行CDN容灾,降低CDN异常对业务的影响,是我们不断思考的问题。既然以上问题SRE侧无法完美地解决,端侧是不是可以进行一些尝试呢?比如将CDN容灾前置到终端侧。不死鸟(Phoenix)就是在这样的设想下,通过前端能力建设,不断实践和完善的一套端侧CDN容灾方案。该方案不仅能够有效降低CDN异常对业务的影响,还能提高CDN资源加载成功率,现已服务整个美团多个业务和App。3.目标与场景3.1核心目标为降低CDN异常对业务的影响,提高业务可用性,同时降低SRE同学在CDN运维方
117、面的压力,在方案设计之初,我们确定了以下目标:端侧CDN域名自动切换:在CDN异常时,端侧第一时间感知并自动切换CDN域名进行加载重试,减少对人为操作的依赖。CDN域名隔离:CDN域名与服务厂商在区域维度实现服务隔离且服务等效,前端2022年美团技术年货4.1总体设计图 1Phoenix端侧CDN容灾方案主要由五部分组成:端侧容灾SDK:负责端侧资源加载感知,CDN切换重试,监控上报。动态计算服务:根据端侧SDK上报数据,对多组等效域名按照城市、项目、时段等维度定时轮询计算域名可用性,动态调整流量至最优CDN。同时也是对CDN可用性的日常巡检。容灾监控平台:从项目维度和大盘维度提供CDN可用性
118、监控和告警,为问题排查提供详细信息。CDN服务:提供完善的CDN链路服务,在架构上实现域名隔离,并为业务方提供等效域名服务,保证端侧容灾的有效性。等效域名,就是能够通过相同路径访问到同一资源的域名,比如: XHR 来实现。通过 Webpack 在工程构建阶段把同步资源进行抽离,然后通过 Phoenix-Loader 来加载资源。这样就能通过网络请求返回的状态码,来感知资源加载结果。在方案的实现上,我们将SDK设计成了WebpackPlugin,主要基于以下四点考虑:1.通用性:美团前端技术栈相对较多,要保证容灾SDK能够覆盖大部分的技术框架。2.易用性:过高的接入成本会增加开发人员的工作量,不
119、能做到对业务的有效覆盖,方案价值也就无从谈起。3.稳定性:方案要保持稳定可靠,不受CDN可用性干扰。4.侵入性:不能侵入到正常业务,要做到即插即用,保证业务的稳定性。通过调研发现,前端有70%的工程构建都离不开Webpack,而WebpackPlugin独立配置,即插即用的特性,是实现方案的最好选择。整体方案设计如下:前端2022年美团技术年货图 4CSS资源的处理与JS有所差别,但原理相似,只需要重写mini-css-extract-plugin的异步加载实现即可。Web端方案资源加载示意:图 5前端2022年美团技术年货败后,Native容灾基建代理处理失败返回,业务方仍处于等待结果状态,
120、重请新求结束后向业务方返回最终结果。整个过程中从业务方角度来看仍只发出一次请求,收到一次结果,从而达到业务方不感知的目的。为将重新请求效率提升至最佳,必须尽可能的保证重新请求次数趋向于最小。调研业务的关注点和技术层面使用的网络框架,结合Phoenix容灾方案的基本流程,在方案设计方面,我们主要考虑以下几点:便捷性:接入的便捷性是SDK设计时首先考虑的内容,即业务方可以用最简单的方式接入,实现资源容灾,同时也可以简单无残留拆除SDK。兼容性:Android侧的特殊性在于多样的网络框架,集团内包括Retrofit框架,okHttp框架,okHttp3框架及已经很少使用的URLConnection框
121、架。提供的SDK应当与各种网络框架兼容,同时业务方在即使变更网络框架也能够以最小的成本实现容灾功能。而iOS侧则考虑复用一个NSURLProtocol去实现对请求的拦截,降低代码的冗余度,同时实现对初始化项进行统一适配。扩展性:需要在基础功能之上提供可选的高级配置来满足特殊需求,包括监控方面也要提供特殊的监控数据上报能力。基于以上设计要点,我们将Phoenix划分为以下结构图,图中将整体的容灾SDK拆分为两部分Phoenix-Adaptor部分与Phoenix-Base部分。前端2022年美团技术年货监控器:分发容灾过程的详细数据,内置数据大盘的上报,若有外部自定义的监控器,也会向自定义监控器
122、分发数据。Phoenix-AdaptorPhoenix-Adaptor是Phoenix容灾的扩展适配部分,用于兼容各种网络框架。绑定器:生成适合各个网络框架的拦截器并绑定至原始请求执行者。解析器:将网络框架的Request转换为Phoenix内部执行器的Request,并将Phoenix内部执行器的Response解析为外部网络框架Response,以此达到适配目的。容灾效果业务成功率以外卖图片业务为例,Android业务成功率对比(同版本7512,2021.01.17未开启Phoenix容灾,2021.01.19晚开启Phoenix容灾)。图 9iOS业务成功率对比(同版本7511,2021
123、.01.17未开启Phoenix容灾,2021.01.19晚开启Phoenix容灾)。前端2022年美团技术年货如果域名A大范围异常,端侧依然会首先进行域名A的重试加载,这样就导致不必要的重试成本。如何让资源的首次加载更加稳定有效,如何为不同业务和地区动态提供最优的CDN域名列表,这就是动态计算服务的要解决的问题。计算原理动态计算服务通过域名池和项目的Appkey进行关联,按照不同省份、不同地级市、不同项目、不同资源等维度进行策略管理。通过获取5分钟内对应项目上报的资源加载结果进行定时轮询计算,对域名池中的域名按照地区(城市&省份)的可用性监控。计算服务会根据域名可用性动态调整域名顺序并对结果
124、进行输出。下图是一次完整的计算过程:图 13假设有A、B、C三个域名,成功率分别是99%、98%、97.8%,流量占比分别是90%、6%、4%。基于转移基准,进行流量转移,比如,A和B成功率差值是1,B需要把自己1/2的流量转移给A,同时A和C的成功率差值大于1,C也需要把自己1/2的流量转移给A,同时B和C的差值是0.2,所以C还需要把自己1/4的流量转移给B。最终,经过计算,A的流量占比是95%,B是3.5%,C是1.5%。最前端2022年美团技术年货图 154.3.3容灾监控在监控层面,SRE团队往往只关注域名、大区域、运营商等复合维度的监控指标,监控流量巨大,对于小流量业务或者小范围区
125、域的CDN波动,可能就无法被监控分析识别,进而也就无法感知CDN边缘节点异常。容灾监控建设,主要是为了解决SRE团队的CDN监控告警滞后和监控粒度问题。监控整体设计如下:图 16前端2022年美团技术年货图 184.3.4CDN服务端侧域名切换的有效性,离不开CDN服务的支持。在CDN服务方面,在原有SRE侧容灾的基础上,对CDN服务整体做了升级,实现域名隔离,解决了单域名对应多CDN和多域名对应单CDN重试无效的弊端。图 19前端2022年美团技术年货美团高性能终端实时日志系统建设实践作者:洪坤徐博陈成少星1.背景1.1Logan简介Logan是美团面向终端的统一日志服务,已支持移动端 Ap
126、p、Web、小程序、IoT等多端环境,具备日志采集、存储、上传、查询与分析等能力,帮助用户定位研发问题,提升故障排查效率。同时,Logan也是业内开源较早的大前端日志系统,具有写入性能高、安全性高、日志防丢失等优点。1.2Logan工作流程为了方便读者更好地理解Logan系统是如何工作的,下图是简化后的Logan系统工作流程图。主要分为以下几个部分:主动上报日志:终端设备在需要上报日志时,可以通过HTTPS接口主动上传日志到Logan接收服务,接收服务再把原始日志文件转存到对象存储平台。日志解密与解析:当研发人员想要查看主动上报的日志时会触发日志下载与解析流程,原始加密日志从对象存储平台下载成
127、功后进行解密、解析等操作,然后再投递到日志存储系统。日志查询与检索:日志平台支持对单设备所有日志进行日志类型、标签、进程、关键字、时间等维度的筛选,同时也支持对一些特定类型的日志进行可视化展示。前端2022年美团技术年货针对以上痛点问题,我们提出了建设Logan实时日志,旨在提供统一的、高性能的实时日志服务,以解决美团现阶段不同业务系统想要日志上云的需求。1.4Logan实时日志是什么?Logan实时日志是服务于移动端App、Web、小程序、IoT等终端场景下的实时日志解决方案,旨在提供高扩展性、高性能、高可靠性的实时日志服务,包括日志采集、上传、加工、消费、投递、查询与分析等能力。图 2Lo
128、gan 实时日志产品功能图2.设计实现2.1整体架构图 3Logan 实时日志整体架构图如上图所示,整体架构主要分为五个部分,它们分别是:前端2022年美团技术年货重点模块介绍:配置管理:采集端初始化完成后,首先启动配置管理模块,拉取和刷新配置信息,包括上报限流配置、指标采样率、功能开关等,支持对关键配置进行灰度发布。加密:所有记录的日志都使用ECDH+AES方案加密,确保日志信息不泄漏。Web版加密模块使用浏览器原生加密API进行适配,可实现高性能异步加密,其他平台则使用纯JS实现。存储管理:线上数据表明,由于页面关闭导致的日志丢失占比高达1%,因此我们设计了日志落盘功能,当日志上传失败后会
129、先缓存在本地磁盘,等到页面下一次启动时再重新恢复上传。队列管理:需要发送的日志首先进行分组聚合,如果等待分组过多则需要丢弃一部分请求,防止在弱网环境或者日志堆积太多时造成内存持续上涨而影响用户。落盘缓存+上报恢复,防止日志丢失为了方便读者更好地理解端上日志采集过程,下面将具体介绍下采集端流程设计。当采集端初始化API开始调用时,先创建Logger、Encryptor、Storage等实例对象,并异步拉取环境配置文件。初始化完成之后,先检查是否有成功落盘,但是上报失败的日志,如果有的话立即开始恢复上传流程。当正常调用写日志API时,原始日志被加密后加入当前上报组,等到有上报事件(时间、条数、导航
130、等)触发时,当前上报组内的所有日志被加入上报队列并开始上传。采集端详细流程图如下:前端2022年美团技术年货2.4数据处理层在数据处理框架的技术选型上,我们先后考虑了三种方案,分别是传统架构(Java应用)、Storm架构、Flink架构,这三种方案在不同维度的对比数据如下:方案成熟度稳定性扩展性容错性延迟吞吐量开发成本运维成本传统架构高高低低高低高高Storm架构高中高高中中中中Flink架构中中高高低高中中表 1技术选型对比表综合来看,虽然传统架构有比较好的成熟度与灵活性,但是在扩展性、容错性以及性能上均不能满足系统要求,而Flink架构与Storm架构都有比较优秀的扩展性与容错性,但是F
131、link架构在延迟与吞吐量上表现要更好,因此我们最终选择了使用Flink架构作为数据处理框架。Flink:业内领先的流式计算引擎,具有高吞吐、低延迟、高可靠和精确计算等优点,对事件窗口有很好的支持,被业内很多公司作为首选的流式计算引擎。在日志处理流程设计上,日志数据通过接入层处理后被投递到汇总topic里面,然后再通过Flink作业的逻辑处理后分发到下游。处理流程如下图所示:图 7日志处理层流程图前端2022年美团技术年货图 8数据消费层设计图数据消费层的一些典型的应用场景:1.网络全链路追踪:现阶段前后端的日志可能分布在不同的系统中,前端日志系统记录的主要是代码级日志、端到端日志等,后端日志
132、系统记录的是链路关系、服务耗时等信息。通过Logan实时日志开放的数据消费能力,用户可以根据自己的需求来串联多端日志,从而实现网络全链路追踪。2.指标聚合统计&告警:实时日志也是一种实时的数据流,可以作为指标数据上报的载体,如果把日志数据对接到数据统计平台就能实现指标监控和告警了。3.离线数据分析:如果在一些需求场景下需要对数据进行长期化保存或者离线分析,就可以将数据导入到Hive中来实现。前端1912.6日志平台日志平台的核心功能是为用户提供日志检索支持,日志平台提供了用户标识、自定义标签、关键字等多种检索过滤方式。在日志底层存储架构的选择上,目前业界广泛使用的是Elasticsearch,
133、考虑到计费与运维成本的关系,美团内部已经有一套统一的框架可以使用,所以我们也选用了Elasticsearch架构。同时,我们也支持通过单独的接口层适配其他存储引擎,日志查询流程如下:图 9日志查询流程设计图Elasticsearch:是一个分布式的开源搜索和分析引擎,具有接入成本低、扩展性高和近实时性等优点,比较适合用来做大数据量的全文检索服务,例如日志查询等。3.稳定性保障3.1核心监控为了衡量终端实时日志系统的可用性,我们制定了以下核心SLA指标:指标名称指标定义目标端侧上报成功率端侧日志上报请求成功次数/上报请求总次数99.5%服务可用性服务周期内系统可用时长/服务周期总时长99.9%日
134、志平均延迟日志从产生到可以被消费的平均延迟时长2022年美团技术年货除了核心指标监控之外,我们还建设了全流程监控大盘,覆盖了分端上报成功率、域名可用性、域名QPS、作业吞吐量、平均聚合条数等重要观测指标,并且针对上报成功率、域名QPS、作业吞吐量等配置了兜底告警,当线上有异常时可以第一时间发现并进行处理。3.2蓝绿发布实时日志依赖于实时作业的处理计算能力,但是目前实时作业的发布和部署不能无缝衔接,中间可能存在真空的情况。当重启作业时,需要先停止原作业,再启动新的作业。如果遇到代码故障或系统资源不足等情况时则会导致作业启动失败,从而直接面临消息积压严重和数据延时升高的问题,这对于实时日志系统来说
135、是不能忍受的。蓝绿发布(BlueGreenDeployment)是一种平滑过渡的发布模式。在蓝绿发布模式中,首先要将应用划分为对等的蓝绿两个分组,蓝组发布新产品代码并引入少许线上流量,绿组继续运行老产品代码。当新产品代码经线上运行观察没有问题后,开始逐步引入更多线上流量,直至所有流量都访问蓝组新产品。因此,蓝绿发布可以保证整体系统的稳定,在产品发布前期就可以发现并解决问题,以保证其影响面可控。目前美团已有的实时作业蓝绿部署方案各不相同,由于Logan实时日志接入业务系统较多,且数据量较大,经过综合考量后,我们决定自己实现一套适合当前系统的蓝绿部署方案。为了保证系统的稳定性,在作业运行过程中,启
136、动另外一个相同的作业,当新作业运行没有问题后,再完成新老作业切换。蓝绿发布流程图如下:前端2022年美团技术年货1.核心链路还原:到家某C端小程序使用Logan实时日志记录程序核心链路中的关键日志与异常日志,当线上有客诉问题发生时,可以第一时间查看实时日志并定位问题。项目上线后,平均客诉定位时间从之前的10分钟减少到3分钟以内,排障效率有明显提升。2.内测阶段排障:企业平台某前端项目由于2.0改版改动较大,于是使用Logan实时日志在内测阶段添加更多的调试日志,方便定位线上问题。项目上线后,每次排查问题除了节省用户上报日志时间10-15分钟,还杜绝了因为存储空间不足而拿不到用户日志的情况。3.
137、日志数据分析:美团到店某团队使用Logan实时日志分析前后端交互过程中的请求头、请求参数、响应体等数据是否符合标准化规范。经过一个多月时间的试运行,一期版本上线后共覆盖300+前端页面和500+前端接口,共计发现1000+规范问题。5.未来规划Logan实时日志经过半年的建设与推广,已经完成了系统基础能力的建设,能满足用户对于实时日志的基本诉求。但是对于日志数据深加工与清洗、日志统计与告警等高阶需求还不支持,因此为了更好地发挥日志价值,提升终端故障排查效率,Logan实时日志有以下几个方面的规划:完善功能:支持更多终端类型,建设日志加工与清洗、日志统计与告警、全链路追踪等功能。提升性能:支持百万并发 QPS、日志上报成功率提升至99.9%等。提升稳定性:建设限流熔断机制、应急响应与故障处理预案等。6.本文作者洪坤、徐博、陈成、少星等,均来自美团-基础技术部-前端技术中心。前端1957.招聘信息美团基础技术部-前端技术中心,诚招高级、资深技术专家,Base 上海、北京。我们致力于为美团海量业务建设高性能、高可用、高体验的前端基础技术服务,涵盖终端通信、终端安全、资源托管、可观测性、研发协同、设计工具、低代码、Node基建等技术领域,欢迎有兴趣的同学投送简历至:。