https://mp.weixin.qq.com/s/7-h2w_iXrM5861iGTpftNQ

引言

响应式编程最简单的定义是Reactive programming is programming with asynchronous data streams。无论是从Spring5中引入的响应式编程框架还是java9中集成的响应式流,都能看到响应式编程的影子。可以说响应式编程代表了未来编程的方向。

响应式编程其天然就是非阻塞的,当数据准备完成后自动触发下一个动作而不是等待数据完成。这种思想再结合异步化编程使得我们在统一线程模型,降低多线程编程成本的同时提升整个系统的吞吐量。

闲鱼当前的业务场景本身足够复杂,然而目前大规模使用的仍然是阻塞式编程,每个业务场景的流程被串行同步执行。它最大的问题在于大量线程等待IO(日志记录,RPC调用,HTTP请求等)完成。有鉴于此,闲鱼引入RxJava 2.0来作为响应式编程框架对应用进行异步化改造。目前app内的鱼塘首页,留言列表以及鱼塘详情已经完成异步化改造并上线。

响应式异步化编程实践

异步化编程不同于传统的阻塞式编程,他们的区别可以用下面的图来表达,C代表computation任务,I代表IO任务。原来的一个线程中执行完所有的computation和IO任务转变成现在computation和IO任务分离,这样做的好处是CPU只关注computation任务,理论上CPU核数个线程就能满足所有的computation任务,大大减少线程切换开销。

线程模型 RxJava框架背后是线程模型的演进。在引入RxJava前传统Java自带多线程框架(Executor)和异步Future,它们的问题在于

  • 线程池无法统一。开发在自己的场景下都可能去定义线程池。

  • 上下文切换。随着线程数的增多,线程上下文的切换必然增多。

  • Future异步方式仍然是一种阻塞等待式方法。

RxJava结合全异步编程方式的优势在于

  • 线程利用率大幅提高。当线程需要做阻塞操作时及时切换避免长时间占有线程,整个流程无阻塞。

  • RxJava统一了线程池模型。我们可以根据不同的场景选择对应的线程调度模型。

  • 极致线程模型变得可能。理论上只需要cpu核数个线程就可以运行所有computation任务,这意味线程切换带来的开销几乎被消除。

应用改造

响应式全异步编程天生拒绝阻塞,任何阻塞点都可能导致性能的退步。更严重的如果我们控制了线程数,当任务因为阻塞而产生堆积,随着堆积的任务变多应用会gc影响线上服务。目前闲鱼应用中存在4类阻塞点

  • 应用日志。

  • HTTP请求。

  • RPC调用。

  • 缓存读写。

闲鱼中的日志不仅仅作为异常输出手段,也是数据统计的一个主要方法。因为日志的性能显得很重要。虽然log4j和logback都提供了异步方式,但是它们本质上还是基于锁来实现。log4j2是新一代的基于LMAX Disruptor的无锁异步日志系统,在多线程程序中,其吞吐量比log4j和logback高10倍左右。

而HTTP,RPC以及缓存的读写都需要改造成纯异步方式:当请求发生时线程被释放,请求完成后继续在新的线程中执行余下业务流程。

执行范式

对闲鱼中的场景进行一下归纳,可以梳理出三种执行范式,下面会分别简单展示这三种范式。为了简单起见,其中涉及到的IO操作都用一个函数加以抽象。

串行请求 以常用的电商场景为例,查看我买的商品详情。业务流程上要先查询订单详情,然后从订单详情中拿到商品,最后根据商品去查询商品详情。

  1. //查订单

  2. Flowable<Order> orderFlow = Flowable.fromCallable(() -> queryOrder(orderId));

  3. //查商品

  4. Flowable<Item> itemFlow = orderFlow.flatMap(order -> Flowable.fromCallable(() -> queryItem(order.getId())));

并发请求 商品详情中往往还会有一些额外的信息,比如浏览量以及留言内容。浏览量和留言它们彼此是互不依赖的,但是都依赖查到的商品信息。

  1. Flowable<Detail> detailFlow = itemFlow.flatMap(item -> {

  2.    //浏览量

  3.    Flowable<Long> pvFlow = Flowable.fromCallable(() -> queryItemPv(item.getId()));

  4.    //留言

  5.    Flowable<Comment> commentFlow = Flowable.fromCallable(() -> queryItemComment(item.getId()));

  6.    //浏览,留言,商品共同组成detail

  7.    return Flowable.zip(pvFlow,commentFlow,(pv,comment) -> buildItemDetail(item,pv,comment));

  8. });

更新缓存 热门商品详情往往意味高并发访问,我们可以将这些数据缓存来减轻数据库的压力。但是缓存成功与否都不影响本次请求(缓存失败导致下次请求仍然走db,本次请求的数据仍然返回给用户)。更新缓存本质上代表这样一类操作,它们在请求完成后执行一些额外的操作(缓存,通知用户,日志记录等),这些额外操作的成功与否不对主流程造成任何影响。

  1. //更新缓存,doOnNext不会对流的结果造成任何影响,只是触发一个操作

  2. detailFlow.doOnNext(detail -> cache(detail));

需要强调的是上面流中所有的方法都对应着一次IO操作(RPC,缓存读写等),这些IO操作都应该是全异步方式实现(不能等待IO完成,而是IO完成后主动唤醒)。幸运的是已经有一些第三方库来帮我们完成这些IO异步化。

改造结果

我们对改造完的接口进行了一轮性能测试,分别从接口rt,线程数以及cpu利用率三个方面对阻塞式执行和响应式纯异步方式进行对比

rt 因为我们在异步方式中增加了并行操作, 所以rt降低是必然的,rt下降50%左右。当请求QPS达到650的时候,传统阻塞方式rt飙升,服务开始不可用;响应式纯异步方式rt较为稳定,QPS达到850的时候开始明显上涨。

线程数 阻塞式执行方式因为在IO发生时线程会等待IO完成,而异步方式下线程直接释放,所以异步方式下线程利用效率明显更高。下面的测试结果也表面异步方式的线程数一直较为稳定;阻塞模式下线程在QPS到达650时被耗尽,这意味着新的请求将被直接拒绝。

CPU 由于异步模式下我们增加了并发,因此CPU使用必然会较阻塞模式高,测试结果也说明了这一点。然而当QPS到达650的时候,阻塞模式下服务已经不可用,因此CPU利用率最高只能到达75%左右;而异步模式CPU能够到达97%。

总结

本文介绍了响应式编程在闲鱼的应用现状,我们选用了RxJava作为响应式编程框架,并选取了闲鱼留言列表,鱼塘首页以及鱼塘详情页进行改造并进行了性能测试。测试数据表明闲鱼群聊首页场景下,在rt降低50%的同时整个系统的吞吐量能提升约30%。

目前响应式纯异步编程在闲鱼仍然处于起步阶段,我们的线程池模型仍然没法做到极致(暂时没法完全消除阻塞点)。接下来会朝着极致线程模型进行尝试,可能的改造点包括

  • 阻塞点消除。

  • cpu核数个线程来调度所有的computation任务。

  • 底层的IO线程模型统一。现在IO操作(RPC,HTTP,缓存)都是各自维护自己的线程池,理论上这些线程池都可以趋于统一。

转载于:https://www.cnblogs.com/davidwang456/articles/10175728.html

RxJava在闲鱼系统吞吐量提升上的实践相关推荐

  1. 闲鱼java系统_RxJava在闲鱼系统吞吐量提升上的实践

    引言 响应式编程最简单的定义是Reactive programming is programming with asynchronous data streams.无论是从Spring5中引入的响应式 ...

  2. 阿里云助力完美日记半年内系统吞吐量提升50倍

    阿里云 Redis 直播地址 近年来,完美日记的"小黑钻口红""动物眼影盘"等爆款彩妆出现在了越来越多女孩子的化妆台上,完美日记(Perfect Diary)是 ...

  3. 闲鱼单体应用Serverless化拆分实践

    背景 2018,我们在实践中提出了flutter+Dart Faas的云端一体化研发解决方案,该方案借助Serverless 轻(聚焦业务).快(单个接口单个函数,研发.部署快).NoOps(运维平台 ...

  4. 看!闲鱼在ServiceMesh的探索和实践

    背景: 在阿里服务端开发以Java为主的大背景下,其他异构语言业务如何调用现有Java服务,如何与集团中间件打通,就成为使用非Java语言团队必须要解决的首要问题. 已有方案问题: 在ServiceM ...

  5. 推荐算法在闲鱼小商品池的探索与实践

    引言 个性化推荐是提升转化率的有效方法,闲鱼的大量导购场景中已经使用了个性化推荐算法,并取得了很好的效果.但是随着导购场景的微型化,精细化,出现越来越多的万级别以下商品量级导购场景,这些场景也需要引入 ...

  6. 互联网首发 | 闲鱼程序员公开多年 Flutter 实践经验

    简介 flutter-boot核心解决了混合开发模式下的两个问题:flutter混合开发的工程化设计和混合栈.那flutter-boot是如何解决的呢? 首先在工程化设计的问题上,flutter-bo ...

  7. Dubbo Mesh 在闲鱼生产环境中的落地实践

    本文作者至简曾在 2018 QCon 上海站以<Service Mesh 的本质.价值和应用探索>为题做了一次分享,其中谈到了 Dubbo Mesh 的整体发展思路是"借力开源. ...

  8. 闲鱼源码页面SSR最佳实践

    背景 「让每一个用户在最短的时间内看到页面上重要的内容」一直以来都是前端工程师们精益求精的方向.对于一个H5的源码页面,我们已经有了很多缩短首屏渲染时间的方法,比如数据预取,离线缓存.但在目前看来,由 ...

  9. 抓球球的机器人应该怎么玩_闲鱼2019年应该怎么玩?闲鱼上面的几大规则

    闲鱼2019年应该怎么玩?闲鱼上面的几大规则 如今已经是2019年了,还是有好一些的伙伴们在求闲鱼流量,闲鱼新的一年是不是有什么大的改动,还可以照着往年的玩法继续做闲鱼吗,对于这样的问题,我实在有些看 ...

最新文章

  1. OpenStack OVS实现安全组(五)
  2. 来自一年的程序员的困惑
  3. 【Windows10】安装一些更新时出现问题,但我们稍后会重试
  4. orale用户角色管理
  5. 5最后一条记录_在一堆数据中,如何获取最后一次记录?
  6. mac怎么配置php开发环境变量,Mac M1安装mnmp(Mac+Nginx+MySQL+PHP)开发环境
  7. 《C++ primer》--第11章
  8. opentsdb basic install
  9. java调用linux设备,使用Java调用Linux系统命令
  10. mysql 补丁 patch6_系统管理-第6部分――补丁(patch)管理和在线更新
  11. 91卫图助手下载器永久免费啦
  12. java 数据权限控制_数据权限-数据列权限设计方案
  13. windows_clion_Process finished with exit code -1073741515 (0xC0000135)
  14. 南京信息工程大学计算机考研怎么样,南京信息工程大学就业怎么样,考研好不好?...
  15. LVS负载均衡群集——NAT地址转换
  16. 计算机网络自顶向下方法,第7版—第1章习题
  17. 关于乱码(MessyCode)问题
  18. 异步调用RFC:CALL FUNCTION ‘ZXXXXXXXXX‘ IN UPDATE TASK
  19. 微信小程序-实现两个按钮固定在页面底端且不随页面滑动(静态页面)
  20. 北京理工大学自动化考研经验贴

热门文章

  1. 《剑指offer》c++版本 5.替换空格
  2. python元组类型_什么是python元组数据类型
  3. python求平均成绩 输入用空格分隔的两个代码_在Python2.7中,如何计算以空格分隔的多个输入?...
  4. mysql 评论回复表设计_【数据库】评论回复表设计
  5. python获取数据库列名_python sqlite3 查询操作及获取对应查询结果的列名
  6. linux赋予文件夹所有权限_linux – 如何将某些用户权限仅授予子文件夹
  7. 非因果滤波器 matlab,对于无阶跃响应不会过冲的无因果低通滤波器,最清晰的频率响应是什么?...
  8. r指引到文件夹_R语言中进行文件夹及文件夹内文件操作范例
  9. MySQL中的整数类型
  10. Qt中的QTimer