闲鱼在数据聚合上的探索与实践

nywm2493 6年前
   <h3>概述</h3>    <p>随着业务的不断扩张,各种运营活动越来越多,原有的前端渲染-后端提供业务接口的开发方式对于一个生命周期可能只有几天的活动来说成本巨大。闲鱼在降低开发成本,提高整体效率上做了一些尝试和实践。本文介绍闲鱼从数据聚合方面进行了一些探索和尝试,以及Graphql的引入给闲鱼带了研发效率的提升。</p>    <h3>背景</h3>    <p>长期以来,前端和后端开发中面临一个矛盾:前端希望页面只获取结构化数据,能够直接渲染出页面组件;后端则希望只提供业务领域API服务能力,数据组装和处理由前端完成。mock数据,联调等低价值的工作会耗费很多的成本,原有的开发模式已跟不上业务快速发展的节奏。 因此我们希望前端可以直接获取数据,后端又能从重复的、低价值的消费型开发中解放出来。</p>    <p><img src="https://simg.open-open.com/show/2b56f0da284487dc085afef24dde7b33.jpg"></p>    <p>数据聚合是我们解决的一个思路。</p>    <h2>1. 数据聚合的解决方案</h2>    <p>数据聚合是将多个服务请求一起打包给服务端,服务端一次性返回相应请求的结果,这种方式可以降低网络耗时,在数据处理上也会更方便。在入参语法上也有扩展的可能性,比如依赖调用等,是一种比RESTFul更加灵活和高效的查询方式。</p>    <p><img src="https://simg.open-open.com/show/4b22990105a51dab3de925e6c713577a.jpg"></p>    <p>在数据聚合的调用下,由于服务端的业务领域接口已经存在,这些接口认为是可靠的,联调成本将会大大降低,在一些测试环境发生异常的情形,前端甚至可以直接在线上测试。</p>    <p>设计原则</p>    <ul>     <li> <p>服务端暴露通用场景的数据服务,即标准业务API,包括数据查询和写入;</p> </li>     <li> <p>尽可能少与前端交互,一次调用获取所有所需数据</p> </li>     <li> <p>并发/异步调用降低耗时</p> </li>    </ul>    <h2>2. 数据聚合1.0</h2>    <p>闲鱼服务端开发了第一个数据聚合服务。 通过将底层服务暴露出来,从请求总入口进行并发调用具体的服务接口,页面多个服务查询可以一次性将所有的数据返回给前端。 调用过程如下:</p>    <p><img src="https://simg.open-open.com/show/7a837ac6ddd68cb559a65af3b25b6094.jpg"></p>    <p>这个框架有如下几个特点:</p>    <ul>     <li> <p>非常轻量,核心代码1000行左右</p> </li>     <li> <p>去中心化直接部署在应用系统上,不依赖其他二方包和服务系统,</p> </li>     <li> <p>无代码入侵,无需对现有系统服务和代码做改造适配,仅需在注册中心注册服务即可</p> </li>     <li> <p>全并发调用,调用的多个服务API均采用并发方式调用,耗时低 此外我们对其语法结构和功能上进行了扩展:支持字段选取,依赖调用,循环依赖检查,别名等功能:</p> </li>    </ul>    <p><img src="https://simg.open-open.com/show/ae64df3f492dc1e450c623c124e241ee.jpg"></p>    <h3>2.1 上线效果</h3>    <p>上线半年内,数据聚合服务支撑了30+的页面上线,占同类需求的80%以上,降低了两端的开发成本超过50%。</p>    <h3>2.2 闲鱼聚合服务上线后存在以下问题:</h3>    <ul>     <li> <p>数据响应结构对调用方不够友好,虽然支持依赖调用,但是返回的数据是平级展现形式,对于一些批量接口来说,返回的结构往往是Map结构,这需要调用方进一步处理,增加了复杂度;</p> </li>     <li> <p>安全性问题。multiquery的查询串没有经过加密,一些非法的请求可能会修改查询语句带来系统风险;而且对于一些敏感数据需要加密或脱敏处理,multiquery语法结构上缺乏数据处理的扩展点</p> </li>     <li> <p>研发体系不完善:缺乏对服务的meta信息透出,导致调用方不清楚要用哪个服务,入参是什么出参是什么,双方存在一定的沟通成本。没有ide支撑,书写起来比较困难。</p> </li>    </ul>    <h2>3. GraphQL-像写sql一样拼装数据</h2>    <h3>3.1 什么是Graphql</h3>    <p>Graphql (https://graphql.org ) 是 非死book 推出的一种数据查询语言,其设计的目的是要将不稳定的数据组装部分从稳定的业务数据逻辑中剥离,使数据控制逻辑前移,开发模式由“下发数据”转变成“取数据”的过程。</p>    <p><img src="https://simg.open-open.com/show/feb874a536e24201420e1e6b2b497493.jpg"></p>    <p>Graphql的优势:</p>    <ul>     <li> <p>结构化清晰:所见即所得,输入和输出结构一致,前端需要什么数据字段,就在ql上填写什么字段,同时支持多层级结构,也可以平级展现,由调用方根据业务决定合适的输出形式。</p> </li>     <li> <p>精细化场景控制:即便是类似的场景,需要的数据也可能不完全相同,graphql中没有一个数据是多余的</p> </li>     <li> <p>数据处理可扩展性强:graphql提供了很多Directives满足日常的开发需求,甚至支持js代码, 开发者也可以自定义一套工具库来扩展</p> </li>    </ul>    <h3>3.2 GraphQL接入应用改造</h3>    <p>闲鱼选择了TQL作为Graphql的服务端实现。Tql是淘宝提供的对GraphQL的java实现,并解决了开源版graphql的很多局限性和痛点,提供了很多特性,使graphql能够低成本部署在应用上。</p>    <ul>     <li> <p>接入简单,代码侵入性低:去中心化的设计,不依赖二方服务,应用系统直接引入可用</p> </li>     <li> <p>研发体系支撑:提供了ql编写的在线ide,可展示各个服务的meta信息,提高了开发效率,书写提示,执行耗时日志,调用场景监控等功能便于服务性能优化</p> </li>     <li> <p>多执行策略:提供了并发执行策略和异步执行策略,在多服务调用和层级调用场景上保证了ql的高效运行;</p> </li>     <li> <p>合并调用:执行前合并所依赖的上游数据集中的元素,这样我们就可以充分利用批量接口查询,而不是单个多次调用,性能显著提高</p> </li>     <li> <p>安全性提升: graphql语句从前端请求到服务端,用签名的方式来避免查询串被篡改</p> </li>    </ul>    <p>3.2.1服务端改造</p>    <ul>     <li> <p>统一graphql查询接口</p> </li>     <li> <p>原有的一个业务领域服务再包装一个GraphQL的Function</p> </li>    </ul>    <p>Function入参改造: 后台的服务参数对于前端视角可能不同(参数过多,影响数据的参数等) </p>    <p>非批量Function出参:一般无需改造,同上 </p>    <p>支持批量的Function改造,返回结构必须是有序List </p>    <p>非标准DO参数:由于输出是JSON,存在循环依赖的java对象使用fastjson输出时会造成栈溢出</p>    <p>开发GraphQL API</p>    <p>下面的示例可以提供一个Graphql的API。@GraphQLDataFetcher 注解表示该方法可以作为Graphql的数据源,supportBatch = true表示支持合并调用,执行前会将依赖的数据结果合并成一个List作为入参;</p>    <p>支持合并调用情况下要求我们保证两点:</p>    <ul>     <li> <p>入参userids的长度和返回的List长度一致,否则无法回填到相应的字段上;</p> </li>     <li> <p>返回的数据顺序要和入参的顺序对应,否则会造成数据错误。</p> </li>    </ul>    <p>统一graphql Gateway API</p>    <p>执行时会自动引入当前请求的全局信息,如登录用户,设备id,设备机型等,如UserAgent可获取用户的机型,系统等信息,不需要前端和后端额外处理,可以直接使用。</p>    <p>3.2.2前端调用改造</p>    <ul>     <li> <p>调用接口改为后端提供的统一graphql接口</p> </li>     <li> <p>根据业务需求使用graphql语法表达所需数据 </p> </li>    </ul>    <p>调用graphql gateway API:</p>    <p>编写GraphQL 查询语句,获取结构化数据:</p>    <p><img src="https://simg.open-open.com/show/cae072ab8a81e28d4ac75d2a88f33d29.jpg"></p>    <p>随着前端团队增多和人员流动,我们对Graphql的学习成本,ql复用以及管理上提出了更高的要求。</p>    <h2>4. GraphqlQL管理平台</h2>    <p>管理平台给开发者提供了很多便利。在线编编辑ql和保存,在线调试,即时发布,多人可见,有示例参考,降低了graphql的学习成本。</p>    <p><img src="https://simg.open-open.com/show/09a550e967f50764d6728ab7528ef3c1.jpg"></p>    <p>前端在页面请求时只需要传入对应的Id,不再需要graphql查询语句</p>    <h3>GraphQL给闲鱼带来的变化</h3>    <p>研发成本降低</p>    <p>引入Graphql后两端的研发成本显著降低,运营类场景整体上线时间明显缩短,大部分情况下,服务端的成本为0。而前端在编写graphql语句+自测的时间可能只有10分钟。</p>    <p><img src="https://simg.open-open.com/show/c045cbcece99002c131ea0e3a4b42e9c.jpg"></p>    <p>GraphqlQL可以与前端页面搭建平台完美结合,如TMS,已经有很多页面组件是基于GraphqlQL来完成的.借助graphql可以很方便地构建出各种各样的页面组件,对研发无人化的方向上也有积极作用。</p>    <p>耗时</p>    <p>通过分析graphql的执行日志,主要耗时在实际调用的接口耗时,graphql自身的耗时一般在20ms以下,某些情况下耗时较长。graphql耗时点包括:</p>    <ul>     <li> <p>ql复杂度</p> </li>     <li> <p>@js指令 后端执行js脚本会引起较多耗时增加</p> </li>     <li> <p>合并调用时的入参数据处理与回填也有一定影响</p> </li>    </ul>    <h2>5. 总结</h2>    <p>本文介绍了闲鱼在数据聚合上的一些探索和尝试,并介绍了Graphql的引入和应用改造。从自研服务到Graphql的引入,研发效率不断提升,并取得了很好的效果。 目前,graphql还只在weex/h5的场景上使用,将来我们会在native上使用并逐步扩大。</p>    <p>GraphqlQL的引入使前端/客户端和服务端的编程模式发生了很大的改变。</p>    <ul>     <li> <p>服务端从此只需专注于建设稳定的业务领域模型,不再维护不稳定的、容易变化的VO层,也不需要与前端反复沟通结构定义。</p> </li>     <li> <p>前端/客户端 不再依赖服务端特定的接口,而是通过 graphql 来自由组合服务端提供各种数据服务,也可以更方便的进行页面搭建,服务端基本不需要参与。</p> </li>     <li> <p>前端/客户端 对业务模型也会有更深入的理解。</p> </li>    </ul>    <p> </p>    <p>来自:https://mp.weixin.qq.com/s/9P_16cNEF0puHg75fVL1xA</p>    <p> </p>