在闲鱼,咱们如何用Dart作高效后端开发?

背景

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

  • 在预发环境中验证调试比较困难:一方面,每次提交代码、构建、部署、验证的总时间相对较长;另外一方面,多人共用一个部署环境,相互干扰(代码冲突和部署冲突),增长了成本。后端开发人员都渴望有一个独立、高效的开发环境,就像开发一个前端页面那样
  • 前台(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在流量大的时候,内存消耗较大,也须要咱们前期合理评估需求和分配系统资源。java

dart后端开发实战

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

高效的隔离开发环境

咱们以往的开发场景是:提交代码 -> 代码冲突(多人共用一个部署环境) -> 构建/部署 -> 经过接口验证 -> 提交fix -> 构建/部署 -> 验证 的迭代。在这个过程当中,开发人员有可能须要亲自解决代码冲突,或者依赖别人解决代码冲突,须要等待构建/部署的时间(少则5分钟,多则十几分钟)。并且这个过程可能须要迭代屡次,时间成本很高,若是由于其余开发人员的代码分支的问题致使部署失败,那么等待验证的时间成倍增长。这样的开发效率显然不是特别理想。c++

在闲鱼的dart应用中,这种问题会获得缓解。每一个开发人员使用本身独立的开发环境,开发环境使用每一个人的工号惟一识别。在不须要提交代码的状况下,开发人员把代码部署到远程预发环境中,并在本地调用预发服务,查看服务的输出,作到本地验证调试的效果,极大地提升了开发效率。由于只会有开发本身单一分支的代码部署,不会牵扯到代码冲突。整个过程,部署、服务调用过程十分快速,能够在10秒内完成。验证和调试的效率很是高。web

每一个开发人员的独立开发环境对应预发机器上的一个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对同一缓存的同时读写

项目流程图可见下图:

实际效果

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

总结

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



本文做者:闲鱼技术-临耕

阅读原文

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

相关文章
相关标签/搜索