精华内容
下载资源
问答
  • Serverless是这几年兴起的一个概念,Serverless可以帮助开发者减轻甚至摆脱传统后端应用开发所需要的服务器设备的设置和运维工作,并以服务接口的方式为开发者提供所需要的功能。它希望开发者更加专注于应用逻辑本身...

    Serverless是这几年兴起的一个概念,Serverless可以帮助开发者减轻甚至摆脱传统后端应用开发所需要的服务器设备的设置和运维工作,并以服务接口的方式为开发者提供所需要的功能。它希望开发者更加专注于应用逻辑本身,而不是被琐碎的基础设施细节所”绑架“。

    而FaaS是Serverless的一种比较好的实践方式。自从亚马逊的AWS在14年推出Lambada之后,FaaS这种后端发开方式迅速被大家接受并应用。它拥有更加轻量、事件驱动的特点。

    闲鱼选择使用Flutter + FaaS体系来实现云端一体化的开发模式也正是看中了Flutter和FaaS技术本身都是轻量的、面向应用的技术。与一体化本身希望开发者尽可能关注整体的业务逻辑非常契合。

    Flutter + FaaS的云端一体化开发模式已经在闲鱼中被使用了一段时间。同事们之前也有过一些文章来介绍一体化开发在闲鱼演进和落地的过程。在这些文章中,都提到了Logic_engineNexus_Framework等字眼。它们一直默默得在业务开发同学的身后,支撑着一体化的落地和发展。

    今天,我们就来介绍一下这个一体化的幕后推手--- Nexus协议,以及基于它衍生出来的框架和库。

    Nexus协议的由来

    一开始说要做Flutter + FaaS一体化开发的时候,我们对”一体化“这三个字的认知相对比较模糊,只是知道端侧的同学可以用Dart这门语言来写FaaS函数,这样的语言上的一体化。对于FaaS所能做的事,也仅仅停留在前端实施已久的BFF层面。那个阶段,对于要做些什么,还是比较迷茫的。

    阿里的同学经常说:

    你不知道能做些什么,是因为想得还不够清楚

    本着这样的想法,一体化小组经常聚在一起讨(liao)论(tian),不管Flutter + FaaS有没有一体化,反正我们小组先”一体化“了再说。

    整个一体化的概念在讨论中慢慢变得清晰,首现我们对于一体化进行了定义,它应该是这样的一个形态:

    1. 语言一体化
    2. 开发模式与架构一体化

    最终达到开发Flutter页面和开发FaaS无明显gap,像在开发一整个应用的体验。

    语言一体化

    由于Flutter本身是以Dart作为开发语言,那么我们自然也选择它作为FaaS的开发语言。闲鱼在之前已经实践过了Dart Server这种开发方式,在Dart runtime、相关开发工具方面有非常深厚的沉淀。组内的同学将这个runtime经过修改之后移植到了集团的FaaS平台Gaia上。

    开发同学不仅可以在端上使用hotreload进行页面快速调试,同样可以使用这项功能在FaaS平台上快速部署与调试,极大得提升了部署和调试的体验。

    开发模式与架构一体化

    在语言一体化的基础上,我们同样希望开发者在开发Flutter页面和FaaS函数的时候,有着相同的心智。

    在传统的前后端分离开发模式中,端侧的开发与后端开发有着比较明显的不同,端侧通过和后端约定数据结构的方式获取用于页面渲染和处理用户输入的数据。这种模式下,双方仅对数据进行了依赖,各自属于不同的系统。

    在一体化的模式下,我们希望开发者能把端侧页面和FaaS函数当成同一个系统来看待。它们应该是一个有机的整体,共同完成一个页面的功能。在职责上,端侧代码主要处理UI的渲染,FaaS函数主要处理逻辑与副作用。

    开发者应该可以像在一个系统内一样进行相互的调用,就好像你在本地调用一个对象的函数那样自然。

    但显然,端与FaaS现实中还是属于两个系统的,如何能够做到像调用函数一样自然呢?它们之间又以什么样方式进行触发呢?

    事件驱动

    在常见的客户端页面开发过程中,端侧逻辑总是围绕着三个操作在进行,不管代码多少,写成什么样,这些逻辑代码最终都会产生:

    1. 发起一个网络请求(remote req)
    2. 调用一个公共函数(native api)
    3. 修改页面数据并渲染(state change)

    这样的三个效果。

    比如页面的初始化过程,就是典型的 remote req=>state change=>render的过程。

    当然这是精简之后的流程,由于一个http请求回来后的数据并不能直接作用于页面state,通常还需要先对数据进行一下处理。

    这些动作都会由一个明显的事件来触发,通常来说是用户进行的交互事件,不论是请求、页面渲染,或者弹出一个Dialog,进行一次页面间的跳转,它们都不会自发得进行(否则看上去有些诡异)。

    而一个端上的事件,也可能会传导到FaaS上,来驱动FaaS上的逻辑函数对这个事件进行处理。当我们把端和FaaS看成一个整体的时候,这个事件就是在一个系统中流转。

    于是我们总结出了第一张图:

    逻辑归一与互相调用

    在传统的开发模型下,页面逻辑、状态、展示三者之间的流转是在端侧进行的,后端负责了一部分的逻辑处理(通常这部分逻辑是需要对于各种领域接口进行调用)。

    而还有一部分领域数据到UI state的一些转换逻辑,则是端、后端都会做一部分。这两部分逻辑分散在两端,通过某种弱的协议进行连接。

    引入了FaaS之后,自然可以把逻辑放到FaaS上实现,那么请求回来的数据理论上可以直接作用于页面渲染。

    如果我们再进一步,不如直接让FaaS来指挥端上的UI怎么做好了。就好像FaaS是一个导演,而端侧UI是一个提线木偶,FaaS怎么说,UI怎么变。这样把业务相关的逻辑都搬上FaaS去,端侧专注于如何将state渲染到UI上,两个部分组合成为一个页面整体,岂不是更加一体化?

    于是我们有了第二张图:

    逻辑归一到FaaS之后,FaaS已经可以跳过传统的弱协议,直面端侧页面了。对于后端来说,一个请求可以映射到一个具体的处理函数。我们可以不太严谨得说,一直以来,客户端是有调用后端函数的能力的。那么既然我们现在想让FaaS来指挥端上的UI的变化,势必也要让FaaS具有调用端侧函数的能力。

    我们把一次调用抽象为一个Action,每一个Action的背后都有一个特定的函数为它提供真实的逻辑,也就是说,一个特定的Action,可以用来描述一个特定的函数与函数背后的逻辑代码,Action本身就是一个函数签名。

    那么端侧需要提供多少函数给FaaS呢?当逻辑归一到FaaS上之后,我们会发现端侧的大部分实现都围绕着两部分进行:

    1. UI展现
    2. 副作用处理

    UI的展现是”纯“的,它基本上都可以由一个页面的state数据来描述,也就是说,大部分情况下,一个state就描述当前UI的状态。那么对于端侧来说,只需要提供一个state到UI的映射函数,理论上就可以让FaaS具有更新端侧UI的能力。也就是说,假设FaaS函数想要更新页面,只需要下发一个state changeAction,带上页面所需要的所有state数据,就可以达到效果。现实场景中,某些页面的state数据可能巨大无比,不好直接传输。我们做了一个JsonPatch库来解决这种场景下的问题,如果FaaS只修改了state中的一部分数据,则可以通过下发patch的方式由端来合成一个新的、完整的state。

    对于副作用处理的部分,大部分的副作用都来自于比如Dialog,页面跳转等。这类操作是通用的,有共性的,我们同样使用一类叫做native apiAction来描述,这些Action与它们背后的处理函数,将面向所有使用了Nexus协议的页面提供这样的能力。

    这两类函数的抽象,已经可以cover 80%的页面需求了,而剩下的20%复杂交互的页面,我们提供custom类型的Action来让开发者进行自定义。

    总结

    通过对于一体化的定义,以及拆分了需要的功能之后,Nexus协议就破土而出了。它是一个

    基于Action的,提供Client/FaaS系统间调用的协议

    Action的调度者-LogicEngine

    我们有了可以用于两个系统间进行相互调用的协议,相当于我们有了一门语言,这门语言只有我们自己认识,所以还需要一个解释器来执行它。

    LogiceEngine就是这样的一个执行器。

    如果我们给它下一个定义,LogicEngine就是一个:

    基于Nexus的Action协议的,提供Client、FaaS之间相互调用能力的库

    Engine本身不提供任何具体的逻辑能力,所有的逻辑能力都需要通过函数的形式注册到Engine中,并绑定到一个具体类型的Action上去。

    所以Engine的设计相对明确:

    1. 对外,它提供函数注册和基于消息(action)执行函数调用功能
    2. 对内,进行消息解析、函数匹配和执行上下文管理

    开发者通过post函数来发出一个Action,相当于通过Engine调用了一个函数,这个函数可能在本地,也可能在FaaS上。这并不是开发者需要关心的内容。甚至于,这个调用会产生什么影响,也不是当前调用者所需要关心的。

    因为调用的发起者实际只是发出了自己的一个意图,比如在实践中,我们会在用户按下"下单"按钮的时候提交一个意图(Action)。

    这个意图最终会产生什么样的UI变化,FaaS会通过一个state change或者native api形式的Action直接调用到具体的实现函数去。

    而端侧注册在Engine的函数不会很多,前面有提到过,大部分UI编程中的逻辑,都可以被归纳为三类。所以我们大多数时候只需要注册三种固定类型的处理函数就可以了。

    展望

    有了Nexus协议、三类通用处理函数的抽象和LogicEngine,意图=>作用中间过程就可以变得透明。

    但这些远远不够,

    后续我们还希望对协议进行升级,从现有的json提升到一个更加类型安全的协议上。
    我们也希望有一个IDL工具,可以自动得将面向Action调用转换成面向接口调用,让开发者有更好的调用体验。
    我们还希望改变现有单向的请求=>应答模型,让FaaS可以自由得调用端侧函数,再次突破两个系统之间的gap,变得更加一体化。


    原文链接
    本文为阿里云原创内容,未经允许不得转载。

    展开全文
  • 像阿里其他技术团队以及业界的做法一样,闲鱼的大多数后端应用都是全部使用java来实现的。java易用、丰富的库、结构容易设计的特性决定了它是进行业务开发的最好语言之一。后端应用中数据的存储、访问、转换、输出...

    背景

    像阿里其他技术团队以及业界的做法一样,闲鱼的大多数后端应用都是全部使用java来实现的。java易用、丰富的库、结构容易设计的特性决定了它是进行业务开发的最好语言之一。后端应用中数据的存储、访问、转换、输出虽然都属于后端的范畴,但是其中变更的频率是不同的。通常领域对象确定之后,它的变化是很少的,但是客户端展示的变化很多,导致接口层(或者叫粘连前台和后台的胶水层)的变化非常快。大多数web应用采用统一的技术栈来实现后端,胶水层跟领域层使用统一技术,这样的做法仍然有可以优化的地方:

    • 在预发环境中验证调试比较困难:一方面,每次提交代码、构建、部署、验证的总时间相对较长;另一方面,多人共用一个部署环境,相互干扰(代码冲突和部署冲突),增加了成本。后端开发人员都渴望有一个独立、高效的开发环境,就像开发一个前端页面那样
    • 前台(java、object-c,javascript)和后台(java)的技术不同,导致前台同学很难开发后端程序,闲鱼技术团队为了追求更高的开发效率,希望能够跨越服务端开发与客户端、前端的界限,让前台开发人员也能够写后端代码
    • 胶水层通常依赖很多后端服务,计算比较简单,是IO密集型的任务。我们理想中的编程框架是能够像写同步代码一样简单,但是享受异步的好处。目前的方案还无法完全做到这一点。

    为什么选择dart

    闲鱼技术团队选择使用dart作为胶水层的实现语言。

    • dart是一种静态类型语言,在编译器就能完全确定变量的类型。它是支持泛型的面向对象语言,任何变量都是对象,不存在java中的原始类型。跟javascript类似,它是一种单线程语言,对异步的支持非常好(async/await)。dart的语法与主流开发语言(java,python,c/c++,javascript)很类似, 在主流的语言语法基础上,dart增加了很多语法结构,getter/setter、方法级联、函数式编程、闭包,这些语法让允许开发人员更加容易地写出简洁的代码;全面易用的类库也是dart能够作为flutter开发语言的重要原因。
    • flutter证明了dart在客户端开发上的成功,闲鱼不仅走在flutter开发的前列,也正在尝试使用dart开发后端应用;语法跟javascript,java相近,有人形容这门语言是傻瓜式的简单(stupid-simple to learn),无论是java后端开发人员,还是客户端开发同学,亦或是前端开发同学,都能够快速上手写出生产级的代码。所有技术同学都能够开发后端接口在闲鱼是可以做到的。
    • dart对异步化的良好支持对业务开发是强大助力。后端应用胶水层代码大多数IO密集型的任务,使用异步化技术可以把多个IO请求的总RT,从所有请求RT之和,降低为所有请求中最高RT。dart对异步有良好的支持,开发同学使用dart可以以近乎同步的代码风格取得异步的性能。我们以闲鱼宝贝详情页的代码举例,对比不同的编码方式。
    // java同步代码
    ItemDetailDO queryItemDetail(Long itemId) {
        ItemDetailDO data = new ItemDetailDO();
        data.setBrowseCount(IdleItemBrowseService.count(itemId));// 多少人看过
        data.setFavorCount(IdleItemFavorService.count(itemId));// 多少人点赞
        return data;
    }
    
    // dart异步代码
    ItemDetailDO queryItemDetail(int itemId) async {
        var data = new ItemDetailDO();
        await Future.wait([
            IdleItemBrowseService.count(itemId).then((count) => data.browseCount = count)
                          .catchError((exp, stackTrace) => logError('$exp $stackTrace')),
              IdleItemFavorService.count(itemId).then((count) => data.favorCount = count)
                          .catchError((exp, stackTrace) => logError('$exp $stackTrace'))
        ]);
        return data;
    }
    
    // rxjava异步代码
    ItemDetailDO queryItemDetail(Long itemId) {
          ItemDetailDO data = new ItemDetailDO();
        Flowable<Long> browseCountFlow = Flowable.fromCallable(
          () => IdleItemBrowseService.count(itemId)
        ).onErrorReturn(t -> 0).subscribeOn(Schedulers.io());
        Flowable<Long> favorCountFlow = Flowable.fromCallable(
          () => IdleItemFavorService.count(itemId)
        ).onErrorReturn(t -> 0).subscribeOn(Schedulers.io());
        Flowable.zip(browseCountFlow, favorCountFlow, (browseCount, favorCount) -> {
            data.setBrowseCount(browseCount);
            data.setFavorCount(favorCount);
        }).blockingFirst();
    }

    在java中我们也广泛使用RxJava这种强大的响应式扩展实施异步操作:RxJava作为java的响应式编程扩展,功能非常强大全面,它使用流的概念封装所有的异步操作。需要注意的是这里的两个服务调用都被放到一个IO线程池中运行, 这个线程池是无界的,容易消耗线程这种系统稀缺的资源。这意味着当流量非常大的时候,系统的线程池很容易被打满,需要设置合理的背压策略。
    从上面的代码中可以看到“数据获取”,“数据组装”的逻辑非常清晰,不像同步代码分散在各处;相比于同步操作,dart的异步操作允许我们同时等待多个IO事件,降低总的响应时间。dart的异步代码拥有同步代码的简洁容易理解的优点,又具有异步编程的性能优势。
    dart异步的原理也是容易理解的。作为单线程语言,dart依靠事件循环运行代码。dart从main函数开始执行,我们在main函数里面创建Future,相当于在一个dart内部维护的事件队列(event queue)中添加计划任务(添加的任务并不会立即执行)。main中的代码执行完之后,dart事件循环开始从事件队列中依次获取任务执行。async/await是dart的语法糖,它允许开发人员能够以书写同步代码的方式来实施异步编程(在C#、javascript中也有类似实现)。被async修饰的方法返回一个Future,调用这样的方法,相当于创建一个Future。await一个Future,相当于把await之后的代码打包放在Future.then()的代码块里,这样就保证之后的代码在Future之后执行。由于任务存储于事件队列,dart在流量大的时候,内存消耗较大,也需要我们前期合理评估需求和分配系统资源。

    dart后端开发实战

    为了提高开发效率,我们利用dart的特性构建了一套高效的隔离开发环境。在业务开发实践中,我们总结出基本的开发架构和代码模式。在这些技术基础上,开发了闲鱼宝贝详情页的主干业务。下面逐一介绍。

    高效的隔离开发环境

    我们以往的开发场景是:提交代码 -> 代码冲突(多人共用一个部署环境) -> 构建/部署 -> 通过接口验证 -> 提交fix -> 构建/部署 -> 验证 的迭代。在这个过程中,开发人员有可能需要亲自解决代码冲突,或者依赖别人解决代码冲突,需要等待构建/部署的时间(少则5分钟,多则十几分钟)。而且这个过程可能需要迭代多次,时间成本很高,如果因为其他开发人员的代码分支的问题导致部署失败,那么等待验证的时间成倍增加。这样的开发效率显然不是特别理想。

    在闲鱼的dart应用中,这种问题会得到缓解。每个开发人员使用自己独立的开发环境,开发环境使用每个人的工号唯一识别。在不需要提交代码的情况下,开发人员把代码部署到远程预发环境中,并在本地调用预发服务,查看服务的输出,做到本地验证调试的效果,极大地提高了开发效率。因为只会有开发自己单一分支的代码部署,不会牵扯到代码冲突。整个过程,部署、服务调用过程十分快速,可以在10秒内完成。验证和调试的效率非常高。

    每个开发人员的独立开发环境对应预发机器上的一个isolate。dart的isolate相当于一个线程,但是不会和其他isolate共享内存,isolate之间的通信通过发送、接收消息完成。闲鱼技术团队使用每个开发人员的代码创建一个isolate,使用工号作为标识,代码可以全量替换掉运行中的isolate,也可以使用热部署增量替换掉isolate中更改的功能。整个过程非常快。在早期使用dart原生的编译器,发现速度较慢(10多秒)后,我们对dart编译器做了裁剪和优化,把编译时间从10多秒降低到几百毫秒(简单来说就是,把dart原生的编译器的附加功能,重新封装,然后通过JIT/AOT生成新的编译工具)。经过我们对dart开发环境的增强,现在开发dart胶水层接口,只需要点击开发工具上的一个按钮,就可以把修改的代码,在几秒内部署到远程的预发环境,并调用当前的开发接口,在本地查看输出。获得和在预发环境上验证一样的效果,但是体验就像在开发一个完全不依赖外部的本地应用程序。

    业务开发架构

    业务开发中最重要的部分是分离出变化和不变的部分,变化的部分用最灵活、快捷的方式实现(变的最多的地方当然用最快的方式处理),不变的部分使用稳定、高效的方式实现。我们已经把dart建设成为一种能够高效开发,并且适合客户端、前端、后端技术人员共同使用的技术。这种技术最适合应用于发生快速变化的接口层,也就是客户端和后端交互的地方,业务需求的变化导致这里的数据结构快速变化,也称之为胶水层。对于相对稳定的数据服务,我们使用java实现为领域服务。

    上图是服务之间的交互图,实现方式如下图所示:

    胶水层dart应用以HTTP协议方式作为MTOP接口提供给客户端调用,往下使用HSF从Java应用中获取数据。
    通常先定义并开发好领域服务,然后再与客户端对接开发出接口,领域服务提供的接口,包含了获取基础数据的所有方法,开发好之后,很少发生变化;胶水层获取领域服务提供的数据,对数据进行加工、裁剪、组装,输出为客户端能够解析的视图数据,客户端解析、渲染、展示为页面。胶水层的代码大致可以分为:获取数据,然后数据处理和组装。抽象出代码模式如下所示:

    // 数据处理和组装
    void putTiger(Zoo zoo, Tiger tiger) => zoo.tiger =  tiger;
    void putDophin(Zoo zoo, Dophin dophin) => zoo.dophin =  dophin;
    void putRatel(Zoo zoo, Ratel ratel) => zoo.ratel =  ratel;
    
    // 发起多个异步请求,所有请求完成后返回所有数据
    Future<T> catchError<T>(Future<T> future) {
      return future.catchError((e, st) => LogUtils.error('$e $st'));
    }
    Future<List<T>> waitFutures<T>(List<Future<T>> futures) {
      Future<List<T>> future = Future.wait(futures.map(catchError));
      return catchError(future);
    }
    
    // 服务接口
    Future<Zoo> process(Parameter param) async {
      var zoo = new Zoo();
      // 数据获取
      await waitFutures(
        Service1.invoke(param).then((animal) -> putTiger(zoo, animal)),
        Service2.invoke(param).then((animal) -> putLion(zoo, dophin)),
        Service3.invoke(param).then((animal) -> putRatel(zoo, animal))
      );
      return finalData;
    }
    

    为了使用java的领域服务,我们首先解决了dart和java之间数据交互问题,主要是通过序列化对java类文件和dart类文件进行合理的转换,保证dart能够透明、简洁地使用java的数据结构,调用java的远程服务;在调用链路上设置全局唯一的上下文id,跨越dart和java调用栈,支持全链路排查;对所有的服务的成功率,rt和额外业务参数有详细的日志,可以配置以日志为数据源的监控告警等等(后续的文章将详细介绍我们对这些问题的详细解决方案,请持续关注哦)。

    服务化详情页主干开发

    闲鱼宝贝详情页是我们使用dart开发的一个重要项目。最早的闲鱼宝贝详情页把各个业务的代码逻辑耦合在一起,导致维护和变更困难,稳定性也难以保证。我们设计的swak框架(更多细节请查看文章swak框架),能够分离垂直业务的共性和差异性,把闲鱼宝贝详情页的实现分割成主干实现和垂直业务实现两块。我们使用自己开发的dart后端开发框架,对swak框架做了最小实现。项目完成了详情页主干的完整功能和基础优化:

    • 垂直业务路由:我们使用dart中的zone存储每个闲鱼商品的业务标识,代码生成的静态代理类依据业务标识调用相应的服务,在主干数据里填充各个业务的独有数据。zone是dart异步代码的执行环境,能够缓存一些可重用数据(业务代码里除非非此不可,尽量不要多用)
    • 作为远程服务的提供方:在hsfcpp对hessian协议的实现基础上做开发,dart也能成为远程服务的提供方
    • 服务调用的优化: 对java远程服务的代理做了优化,隔离业务层面对框架层的感知,做到透明调用
    • 解决缓存调用的差异性:我们依赖缓存的c++接口访问缓存,但是仍然需要处理java/c++缓存读写不兼容问题完成dart和java对同一缓存的同时读写
      项目流程图可见下图:

       ![dart-detail-flow.png](http://gw.alicdn.com/mt/TB1Pyv6V9zqK1RjSZFjXXblCFXa-558-561.png)
      

    实际效果

    目前该项目已经上线超过6周,qps最高可达400,成功率在99.5%以上。整个调用链路的RT与同样功能的java应用持平。由于前期的精心设计,领域服务很少改动,大部分变更发生在dart胶水层。从上线后经历的若干次变更来看,dart胶水层从修改代码结束到提供给客户端使用总耗时不超过2分钟,而相同功能的java应用需要10分钟以上。

    总结

    dart是一门简洁、容易上手、对异步支持良好的编程语言,在flutter的开发中大放异彩。在我们的努力下,dart用于后端开发的支持逐渐完善,前台开发同学和后端开发人员快速高效地开发胶水层接口。我们在很多生产项目中使用了dart用于后端开发,性能、稳定性良好,开发效率大大提高。未来我们会着力于进一步改善dart开发体验、与java项目的兼容性、提升dart远程服务的性能,挖掘dart在后端开发中更大的潜力。


    原文链接
    本文为云栖社区原创内容,未经允许不得转载。

    展开全文
  • 闲鱼前端如何做容灾

    2021-05-14 00:24:12
    项目背景在我们端侧的开发经验里,大概都会遇到这些问题:用户反馈页面有时打不开/白屏;服务端接口返回200,但是页面却没有数据;由于服务端依赖的二方库/服务出现问题等原因,页面接口突然抽风出...

    项目背景

    在我们端侧的开发经验里,大概都会遇到这些问题:

    用户反馈页面有时打不开/白屏;

    服务端接口返回200,但是页面却没有数据;

    由于服务端依赖的二方库/服务出现问题等原因,页面接口突然抽风出错,导致页面展示错乱或者显示内容错误;

    流量高峰时候服务端扛不住压力接口挂了;

    服务端监控显示接口稳定性有99.99%,但是由于网络、客户端环境等原因,接口的真正可用性可能要打个折扣; 

    其实,总结起来这些问题都是由于接口可用性差造成的,而提升接口可用性是解决这些问题的不二法门。

    前端如何提升页面稳定性

    兜底容灾的核心是提前将可用的数据存储在可靠的地方,在需要时能够高效便捷地获取到这份数据。对于这个问题,本地存储往往会浮现在我们的思路中,但是本地存储存在可靠性差、存储容量有限、无法服务端控制等问题。和本地存储相比,OSS + CDN更符合我们的需求,它稳定、可靠、容量不限、可控性强的特点非常适合用于存储兜底数据。(备注:阿里云对象存储服务,简称 OSS,是阿里云提供的海量、安全、低成本、高可靠的云存储服务。)

    但是当我们在具体实现时,会措手不及地发现很多其他问题:

    •如何及时地更新兜底数据?如何保证兜底数据的正确性?

    •如何在前端组件中请求兜底数据?如何保证请求到的兜底数据是正确的?

    •如何确定OSS资源的地址?

    •如何确定某个接口异常的时候该走兜底?

    •如何区分接口是否适用兜底?如果管理?如何判断?

    •如何知道某个接口的到底有没有走兜底容灾的流程?兜底容灾的成功率如何?

    •如何维护管理远程的兜底数据

    等等以上类似问题,但是前端做容灾要解决的问题远远不只是以上这些。

    前端兜底容灾的基本流程

    让我们先一起来看到前端容灾的基本流程, 在设计兜底容灾组件时,除了 CDN 容灾外,我们还考虑了本地存储兜底方案以及离线数据兜底方案:

    本地存储兜底方案:使用端的本地存储能力存储兜底数据 离线数据方案:在离线包发布时将接口数据离线为CDN文件预置其中,作为兜底数据 如此,兜底容灾的基本流程可以简单描述为:

    我们将以上流程封装成前端SDK,然后集成到前端request组件的插件。业务方使用的时候只需要通过参数配置即可引入使用。当然,组件不能仅仅实现功能,还要好用!为此在组件设计时考虑了很多问题,最后呈现的容灾组件具备以下特点:

    •支持无缝接入:无论是接口API、参数,还是返回结果,都和目前通用的接口请求组件保持一致。接入只需要新增容灾参数即可

    •配置控制:对于兜底配置提供了多个参数,提供了简单或复杂的配置方式,开发者可以灵活控制兜底容灾的流程和规则。对于接口的黑白名单和容灾接口管理提供了可视化配置后台。

    •扩展服务:正如流程中所述,组件在关键节点采集了日志信息,借助这些日志信息,提供数据分析、自动注册等服务

    关于容灾地址唯一key计算

    容灾api的地方一定要保证唯一性,主要是参考api地址+参数值+版本号等信息来生成唯一的路由地址,最后将生成的地址转码加密;举个例子:global.alicdn.com/{HTTPTYPE}/{API_NAME}/{API_VERSION}/{PARAMS}

    如果是本地容灾,如何将过期信息加入到存储数据里,我为了更快捷的处理速度。是将这个信息也加入到本地路由key信息里的。举个例子:{UPDATE_TIME}/{TAG}/{API_NAME}/{API_VERSION}/{PARAMS} 本地数据更新时间+特殊标记符+api名称+api版本号+相关参数 这样做的好处就是,当我们需要计算本地当前数据是否过期以及读取的时候十分方便,只需要遍历相关key然后和过期时间做对比即可。

    这边关于相关参数的设置,是否一个接口的所有参数都需要用来使用生成唯一的key;那么显然也是不合理的,比如pageSize等,一些不会影响到数据准确性的参数。所以这边还有一个必要参数的概念,只使用必要参数来生成唯一KEY

    关于数据校验

    如何判断返回的数据是否“正确”,不论是服务端的数据还是我们的容灾数据。基本想到数据校验,第一反应想到是维护一套数据的schame,好非常完美,每个字段都校验到了。但是在实际情况下,每个接口都维护和生成一套schame,操作成本和维护成本都是比较高的事情,更糟糕的是,当这个接口数据接口复杂、大的时候,如果对返回的API数据做大而全的数据校验会影响到页面性能。结合我们业务的实际情况是,99%以上的场景下,接口异常都是结构化的异常,比如一个接口重要属性没有了。这么一来我们只需要判断接口的某个重要属性的正确性就可以了,所以数据校验这块最后我们采取是“克制”的校验方案去做这个事情。

    关于容灾触发校验

    容灾触发校验主要是校验两个方面:

    •是否开启容灾

    •当前接口异常情况是否适用于容灾 过滤掉不能走容灾的接口异常类型(比如未登录、非法请求等接口异常信息) 过滤掉不能走容灾的接口(比如个人信息数据,权益信息数据等等 )

    相关异常类型的配置我们可以在容灾配套的后台系统进行维护,实时生效。

    关于容灾数据配置和维护

    后台系统主要提供的是对CDN容灾数据的,增删改查的功能,还有其他相关配置信息维护管理的功能。

    同时为了保持容灾远程数据的可用性,还承担着对这些数据管理以及定时更新的任务。

    如下图(新增api)

    关于数据分析与容灾系统监控

    对我们的容灾系统运行的到底如何,我们也需要他各个阶段进行稳定性监控以及数据分析;主要包括:

    •容灾成功率

    •容灾开启率

    •接口的错误率

    •接口异常分析

    •系统稳定性分析 

    当我们发现某个接口的异常率激增的时候,会实时反馈给服务端同学进行异常排除;

    业务效果

    如下图某日接口异常时容灾效果:

    某天接口整体出现波动,整体错误率飙升到 8%以上;经过容灾后让错误率降低到0.55%;为什么容灾不是100%,因为在统计上我们把接口的网络异常也计算进来了。CDN容灾的方案无法解决,用户无网络引起的接口异常问题。

    整体系统架构图

    我们整体系统架构主要划分了:

    •端侧:前端容灾SDK

    •容灾服务:提供容灾数据的管理以及容灾服务器的维护能力

    •容灾后台:提供,业务方配置容灾和管理数据的可视化平台

    •数据埋点监控:对整体接口稳定性以及容灾系统稳定性提供数据支持,对异常提供反馈

    其他

    我作为开发,一直有一个习惯就是让我接一个产品或者组件,我接入的成本越少越好。所以在这个过程中,我们也开发了一些浏览器插件等帮助大家快速注册api等方案。当然最好的就是自动接入,关于这块结合我们自身的业务特点。已经开始去做了。有机会可以再和大家聊一下自动接入的相关流程以及方案和我们遇到的问题。然后以上容灾方案最大的问题就是,关于本地容灾这块还是比较单薄,只利用了浏览器的存储能力。关于如何结合客户端的存储能力,提升和优化本地容灾方案也是即将要做的事情。

    至此,有什么建议欢迎留言提问。

    展开全文
  • 闲鱼在数据聚合上的探索与实践

    千次阅读 2018-11-24 11:53:00
    随着业务的不断扩张,各种运营活动越来越多,原有的前端渲染-后端提供业务接口开发方式对于一个生命周期可能只有几天的活动来说成本巨大。闲鱼在降低开发成本,提高整体效率上做了一些尝试和实践。本文介绍闲鱼从...

    概述

    随着业务的不断扩张,各种运营活动越来越多,原有的前端渲染-后端提供业务接口的开发方式对于一个生命周期可能只有几天的活动来说成本巨大。闲鱼在降低开发成本,提高整体效率上做了一些尝试和实践。本文介绍闲鱼从数据聚合方面进行了一些探索和尝试,以及Graphql的引入给闲鱼带了研发效率的提升。

    背景

    长期以来,前端和后端开发中面临一个矛盾:前端希望页面只获取结构化数据,能够直接渲染出页面组件;后端则希望只提供业务领域API服务能力,数据组装和处理由前端完成。mock数据,联调等低价值的工作会耗费很多的成本,原有的开发模式已跟不上业务快速发展的节奏。 因此我们希望前端可以直接获取数据,后端又能从重复的、低价值的消费型开发中解放出来。

    数据聚合是我们解决的一个思路。

    1. 数据聚合的解决方案

    数据聚合是将多个服务请求一起打包给服务端,服务端一次性返回相应请求的结果,这种方式可以降低网络耗时,在数据处理上也会更方便。在入参语法上也有扩展的可能性,比如依赖调用等,是一种比RESTFul更加灵活和高效的查询方式。

    在数据聚合的调用下,由于服务端的业务领域接口已经存在,这些接口认为是可靠的,联调成本将会大大降低,在一些测试环境发生异常的情形,前端甚至可以直接在线上测试。

    设计原则

    • 服务端暴露通用场景的数据服务,即标准业务API,包括数据查询和写入;

    • 尽可能少与前端交互,一次调用获取所有所需数据

    • 并发/异步调用降低耗时

    2. 数据聚合1.0

    闲鱼服务端开发了第一个数据聚合服务。 通过将底层服务暴露出来,从请求总入口进行并发调用具体的服务接口,页面多个服务查询可以一次性将所有的数据返回给前端。 调用过程如下:

    这个框架有如下几个特点:

    • 非常轻量,核心代码1000行左右

    • 去中心化直接部署在应用系统上,不依赖其他二方包和服务系统,

    • 无代码入侵,无需对现有系统服务和代码做改造适配,仅需在注册中心注册服务即可

    • 全并发调用,调用的多个服务API均采用并发方式调用,耗时低 此外我们对其语法结构和功能上进行了扩展:支持字段选取,依赖调用,循环依赖检查,别名等功能:

    2.1 上线效果

    上线半年内,数据聚合服务支撑了30+的页面上线,占同类需求的80%以上,降低了两端的开发成本超过50%。

    2.2 闲鱼聚合服务上线后存在以下问题:

    • 数据响应结构对调用方不够友好,虽然支持依赖调用,但是返回的数据是平级展现形式,对于一些批量接口来说,返回的结构往往是Map结构,这需要调用方进一步处理,增加了复杂度;

    • 安全性问题。multiquery的查询串没有经过加密,一些非法的请求可能会修改查询语句带来系统风险;而且对于一些敏感数据需要加密或脱敏处理,multiquery语法结构上缺乏数据处理的扩展点

    • 研发体系不完善:缺乏对服务的meta信息透出,导致调用方不清楚要用哪个服务,入参是什么出参是什么,双方存在一定的沟通成本。没有ide支撑,书写起来比较困难。

    3. GraphQL-像写sql一样拼装数据

    3.1 什么是Graphql

    Graphql (https://graphql.org ) 是 facebook 推出的一种数据查询语言,其设计的目的是要将不稳定的数据组装部分从稳定的业务数据逻辑中剥离,使数据控制逻辑前移,开发模式由“下发数据”转变成“取数据”的过程。

    Graphql的优势:

    • 结构化清晰:所见即所得,输入和输出结构一致,前端需要什么数据字段,就在ql上填写什么字段,同时支持多层级结构,也可以平级展现,由调用方根据业务决定合适的输出形式。

    • 精细化场景控制:即便是类似的场景,需要的数据也可能不完全相同,graphql中没有一个数据是多余的

    • 数据处理可扩展性强:graphql提供了很多Directives满足日常的开发需求,甚至支持js代码, 开发者也可以自定义一套工具库来扩展

    3.2 GraphQL接入应用改造

    闲鱼选择了TQL作为Graphql的服务端实现。Tql是淘宝提供的对GraphQL的java实现,并解决了开源版graphql的很多局限性和痛点,提供了很多特性,使graphql能够低成本部署在应用上。

    • 接入简单,代码侵入性低:去中心化的设计,不依赖二方服务,应用系统直接引入可用

    • 研发体系支撑:提供了ql编写的在线ide,可展示各个服务的meta信息,提高了开发效率,书写提示,执行耗时日志,调用场景监控等功能便于服务性能优化

    • 多执行策略:提供了并发执行策略和异步执行策略,在多服务调用和层级调用场景上保证了ql的高效运行;

    • 合并调用:执行前合并所依赖的上游数据集中的元素,这样我们就可以充分利用批量接口查询,而不是单个多次调用,性能显著提高

    • 安全性提升: graphql语句从前端请求到服务端,用签名的方式来避免查询串被篡改

    3.2.1服务端改造

    • 统一graphql查询接口

    • 原有的一个业务领域服务再包装一个GraphQL的Function

    Function入参改造: 后台的服务参数对于前端视角可能不同(参数过多,影响数据的参数等) 

    非批量Function出参:一般无需改造,同上 

    支持批量的Function改造,返回结构必须是有序List 

    非标准DO参数:由于输出是JSON,存在循环依赖的java对象使用fastjson输出时会造成栈溢出

    开发GraphQL API

    下面的示例可以提供一个Graphql的API。@GraphQLDataFetcher 注解表示该方法可以作为Graphql的数据源,supportBatch = true表示支持合并调用,执行前会将依赖的数据结果合并成一个List作为入参;

    支持合并调用情况下要求我们保证两点:

    • 入参userids的长度和返回的List长度一致,否则无法回填到相应的字段上;

    • 返回的数据顺序要和入参的顺序对应,否则会造成数据错误。

    统一graphql Gateway API

    执行时会自动引入当前请求的全局信息,如登录用户,设备id,设备机型等,如UserAgent可获取用户的机型,系统等信息,不需要前端和后端额外处理,可以直接使用。

    3.2.2前端调用改造

    • 调用接口改为后端提供的统一graphql接口

    • 根据业务需求使用graphql语法表达所需数据 

    调用graphql gateway API:

    编写GraphQL 查询语句,获取结构化数据:

    随着前端团队增多和人员流动,我们对Graphql的学习成本,ql复用以及管理上提出了更高的要求。

    4. GraphqlQL管理平台

    管理平台给开发者提供了很多便利。在线编编辑ql和保存,在线调试,即时发布,多人可见,有示例参考,降低了graphql的学习成本。

    前端在页面请求时只需要传入对应的Id,不再需要graphql查询语句

    GraphQL给闲鱼带来的变化

    研发成本降低

    引入Graphql后两端的研发成本显著降低,运营类场景整体上线时间明显缩短,大部分情况下,服务端的成本为0。而前端在编写graphql语句+自测的时间可能只有10分钟。

    GraphqlQL可以与前端页面搭建平台完美结合,如TMS,已经有很多页面组件是基于GraphqlQL来完成的.借助graphql可以很方便地构建出各种各样的页面组件,对研发无人化的方向上也有积极作用。

    耗时

    通过分析graphql的执行日志,主要耗时在实际调用的接口耗时,graphql自身的耗时一般在20ms以下,某些情况下耗时较长。graphql耗时点包括:

    • ql复杂度

    • @js指令 后端执行js脚本会引起较多耗时增加

    • 合并调用时的入参数据处理与回填也有一定影响

    5. 总结

    本文介绍了闲鱼在数据聚合上的一些探索和尝试,并介绍了Graphql的引入和应用改造。从自研服务到Graphql的引入,研发效率不断提升,并取得了很好的效果。 目前,graphql还只在weex/h5的场景上使用,将来我们会在native上使用并逐步扩大。

    GraphqlQL的引入使前端/客户端和服务端的编程模式发生了很大的改变。

    • 服务端从此只需专注于建设稳定的业务领域模型,不再维护不稳定的、容易变化的VO层,也不需要与前端反复沟通结构定义。

    • 前端/客户端 不再依赖服务端特定的接口,而是通过 graphql 来自由组合服务端提供各种数据服务,也可以更方便的进行页面搭建,服务端基本不需要参与。

    • 前端/客户端 对业务模型也会有更深入的理解。

     

    展开全文
  • 此源码采用PHP+Mysql开发 使用方法:自己摸索 免责声明:本程序仅供学习参考,切勿违法使用,因个人违规使用所造成的违法行为与本程序作者无关。
  • 概述随着业务的不断扩张,各种运营活动越来越多,原有的前端渲染-后端提供业务接口开发方式对于一个生命周期可能只有几天的活动来说成本巨大。闲鱼在降低开发成本,提高整体效率上...
  • php,js,html,thinkphp官网开发,后台管理系统开发,二次开发,公众号开发,小程序开发接口对接,网站开发,支付对接,聊天系统 , 环境搭建,网站源码搭建等,技术牛。开发的代码高复用,冗余低,静态化响应快,...
  • 明天开始正式继续更新接口测试平台系列哈~ 今天主要处理了一下公司内积压的任务,ui新架构专用的 设备集群也搭建起来了,自掏腰包买了4台二手手机,拼多多上拼了个架子,齐活。 凑手机的时候一定要注意,...
  • 一般采用的都会是混合形式的开发,混合开发目前Flutter官方提供的不太完善、iOS和Android有差异性、接口也不同意、很麻烦,也有一些大厂为了实现高效率高可用自己实现一套混合开发技术,比如闲鱼的flutter_boost和...
  • 作为一个Android开发,基本没怎么接触后台开发的东西,对这方面也有点兴趣,一直都想写套接口实现下简单的替换服务玩一玩。看了下闲鱼的文章,兴趣就来了,有兴趣就有学习热情。于是将Dart的学习了一下,实现了一个...
  • 作为较早在客户端侧选择Flutter方案的技术团队,性能和用户体验一直是闲鱼技术团队在开发中比较关注的点。而Metal这样的直接操作GPU的底层接口无疑会给闲鱼技术团队突破性能瓶颈提供一...
  • 前言 最近在跟小伙伴合作开发一款app,苦于没有合适的公共api接口,所以决定自己搭建一个服务器,目前已实现注册、登录,爬虫定时拉取闲鱼首页更新数据库。 app传送门 一个极致的集前言最近在跟小伙伴合作开发一款app...
  • 最近在跟小伙伴合作开发一款app,苦于没有合适的公共api接口,所以决定自己搭建一个服务器,目前已实现注册、登录,爬虫定时拉取闲鱼首页更新数据库。 app传送门 一个极致的集大成App 搭建服务器需要些什么工作 ...
  • PHP项目常见类型

    2018-11-03 17:10:50
    组件接口包:如:微信PHP开发包,统一支付接口SDK,统一地图接口SDK等 游戏类:非实时类游戏服务端程序 较容易的: 在线考试系 在线投票系统 在线问答系统 学生信息发布系统(多社团版,单社团版太容易,...
  • 新技术如雨后春笋般快速出现,尤其是各种小程序陆续推出,相关的信息、文章也铺天盖地的遍布在各处,我们有时候会困惑,不知道哪些信息对于自己是有价值的,那么 TNFE 团队启动了这个周刊项目,为所有的前端开发人员...

空空如也

空空如也

1 2
收藏数 21
精华内容 8
关键字:

闲鱼开发接口