在java的世界里由于大多数接口和API都是阻塞式的交互,进而影响到很多童靴的编程思想和编程习惯。因而,有一些专家讲java的编程模型是阻塞式模型(与Node.js区别大),不是没有道理的。从高性能的视角看,任何阻塞点都可能导致性能的退步。而响应式编程其天然就是非阻塞的,当数据准备完成后自动触发下一个动作而不是等待数据完成。这种思想再结合异步化编程使得我们在统一线程模型,降低多线程编程成本的同时提升整个系统的吞吐量。
划重点
异步化编程不同于传统的阻塞式编程,在任务处理上异步化编程的关注点已发生转移:
以前一个线程中执行完所有的computation任务和IO任务>>>>如今需要computation任务和IO任务分离进行处理(CPU只关注computation任务)
响应式架构
由业务组件构成,业务组件由事件异步驱动
那如何结合业务进行优化呢?
首先要找出业务系统中的阻塞点,进行分类,各个击破。
应用日志
日志的性能显得很重要,虽然log4j和logback都提供了异步方式,但是它们本质上还是基于锁来实现。log4j2是新一代的基于LMAX Disruptor的无锁异步日志系统,在多线程程序中,其吞吐量比log4j和logback高10倍左右。
HTTP请求
服务端异步其实之前也讲过,了解spring mvc的异步特性介绍
RPC调用
客户端异步改造其实不难
其实应用日志、HTTP请求、RPC调用...等改造起来不难,最麻烦最有挑战性的是执行范式上(串行请求)的改造。
实践1:根据订单号需查询:商品信息、商品详情(需要更新缓存)、浏览量、留言信息
感受下,是不是感觉世界美好了很多!
//根据订单号查订单
Flowable<Order> orderFlow = Flowable.fromCallable(() -> queryOrder(orderId));
//查商品
Flowable<Item> itemFlow = orderFlow.flatMap(order -> Flowable.fromCallable(() -> queryItem(order.getId())));
//查询商品相关信息
Flowable<Detail> detailFlow = itemFlow.flatMap(item -> {
//浏览量
Flowable<Long> pvFlow = Flowable.fromCallable(() -> queryItemPv(item.getId()));
//留言
Flowable<Comment> commentFlow = Flowable.fromCallable(() -> queryItemComment(item.getId()));
//浏览,留言,商品共同组成detail
return Flowable.zip(pvFlow,commentFlow,(pv,comment) -> buildItemDetail(item,pv,comment));
});
//更新缓存(doOnNext不会对流的结果造成任何影响,只是触发一个操作)
detailFlow.doOnNext(detail -> cache(detail));
实践2:支付
Response response=Observable.zip(
callAsync(()->updateStock()),
callAsync(()->updatPayHistroy()),
callAsync(()->updateOrder()),
(response1,response2,response3)-> response()
).blockingGet();
目前响应式纯异步编程在业务系统的应用仍然处于起步阶段,暂时没法完全消除阻塞点,改造的过程中需要重点关注:
- 业务组件的抽象及依赖上治理
- cpu核数个线程来调度所有的computation任务
- 底层的IO线程模型统一(RPC,HTTP,缓存都是各自维护自己的线程池),理论上这些线程池都可以趋于统一