文档章节

【转】聊聊java高并发系统之异步非阻塞

javahongxi
 javahongxi
发布于 2017/08/25 19:15
字数 1988
阅读 88
收藏 1

[京东技术]声明:本文转载自微信公众号“开涛的博客”,转载务必声明。

 

在做电商系统时,流量入口如首页、活动页、商品详情页等系统承载了网站的大部分流量,而这些系统的主要职责包括聚合数据拼装模板、热点统计、缓存、下游功能降级开关、托底数据等等。其中聚合数据需要调用其它多个系统服务获取数据、拼装数据/模板然后返回给前端,聚合数据来源主要有依赖系统/服务、缓存、数据库等;而系统之间的调用可以通过如http接口调用(如HttpClient)、SOA服务调用(如dubbo、thrift)等等。

 

在Java中,如使用Tomcat,一个请求会分配一个线程进行请求处理,该线程负责获取数据、拼装数据或模板然后返回给前端;在同步调用获取数据接口的情况下(等待依赖系统返回数据),整个线程是一直被占用并阻塞的。如果有大量的这种请求,每个请求占用一个线程,但线程一直处于阻塞,降低了系统的吞吐量,这将导致应用的吞吐量下降;我们希望在调用依赖的服务响应比较慢,此时应该让出线程和CPU来处理下一个请求,当依赖的服务返回了再分配相应的线程来继续处理。而这应该有更好的解决方案:异步/协程。而Java是不支持协程的(虽然有些Java框架说支持,但还是高层API的封装),因此在Java中我们还可以使用异步来提升吞吐量。目前java一些开源框架(HttpClient\HttpAsyncClient、dubbo、thrift等等)大部分都支持。

 

几种调用方式

同步阻塞调用

即串行调用,响应时间为所有服务的响应时间总和;

 

半异步(异步Future)

线程池,异步Future,使用场景:并发请求多服务,总耗时为最长响应时间;提升总响应时间,但是阻塞主请求线程,高并发时依然会造成线程数过多,CPU上下文切换;

 

全异步(Callback)

Callback方式调用,使用场景:不考虑回调时间且只能对结果做简单处理,如果依赖服务是两个或两个以上服务,则不能合并两个服务的处理结果;不阻塞主请求线程,但使用场景有限。

 

异步回调链式编排

异步回调链式编排(JDK8 CompletableFuture),使用场景:其实不是异步调用方式,只是对依赖多服务的Callback调用结果处理做结果编排,来弥补Callback的不足,从而实现全异步链式调用。

 

接下来看看如何设计利用全异步Callback调用和异步回调链式编排处理结果来实现全异步系统设计。

 

同步阻塞调用

public class Test {

   public static void main(String[] args) throws Exception {

       RpcService rpcService = new RpcService();

       HttpService httpService = new HttpService();

       //耗时10ms

       Map<String, String> result1 = rpcService.getRpcResult();

       //耗时20ms

       Integer result2 = httpService.getHttpResult();

       //总耗时30ms

    }

   static class RpcService {

       Map<String, String> getRpcResult() throws Exception {

           //调用远程方法(远程方法耗时约10ms,可以使用Thread.sleep模拟)

       }

    }

   static class HttpService {

       Integer getHttpResult() throws Exception {

           //调用远程方法(远程方法耗时约20ms,可以使用Thread.sleep模拟)

           Thread.sleep(20);

           return 0;

       }

    }

}

 

半异步(异步Future)

public class Test {

   final static ExecutorService executor = Executors.newFixedThreadPool(2);

   public static void main(String[] args) {

       RpcService rpcService = new RpcService();

       HttpService httpService = new HttpService();

       Future<Map<String, String>> future1 = null;

       Future<Integer> future2 = null;

       try {

           future1 = executor.submit(() -> rpcService.getRpcResult());

           future2 = executor.submit(() -> httpService.getHttpResult());

           //耗时10ms

           Map<String, String> result1 = future1.get(300, TimeUnit.MILLISECONDS);

           //耗时20ms

           Integer result2 = future2.get(300, TimeUnit.MILLISECONDS);

           //总耗时20ms

       } catch (Exception e) {

           if (future1 != null) {

                future1.cancel(true);

           }

           if (future2 != null) {

                future2.cancel(true);

           }

           throw new RuntimeException(e);

       }

    }

   static class RpcService {

       Map<String, String> getRpcResult() throws Exception {

           //调用远程方法(远程方法耗时约10ms,可以使用Thread.sleep模拟)

       }

    }

   static class HttpService {

       Integer getHttpResult() throws Exception {

           //调用远程方法(远程方法耗时约20ms,可以使用Thread.sleep模拟)

       }

    }

}

 

全异步(Callback)

public class AsyncTest {

public staticHttpAsyncClient httpAsyncClient;

   public static CompletableFuture<String> getHttpData(String url) {

       CompletableFuture asyncFuture = new CompletableFuture();

       HttpPost post = new HttpPost(url);

       HttpAsyncRequestProducer producer = HttpAsyncMethods.create(post);

       AsyncCharConsumer<HttpResponse> consumer = newAsyncCharConsumer<HttpResponse>() {

            HttpResponse response;

           protected HttpResponse buildResult(final HttpContext context) {

                return response;

           }

…...

       };

       FutureCallback callback = new FutureCallback<HttpResponse>() {

           public void completed(HttpResponse response) {

               asyncFuture.complete(EntityUtils.toString(response.getEntity()));

           }

…...

       };

       httpAsyncClient.execute(producer, consumer, callback);

       return asyncFuture;

    }

 

   public static void main(String[] args) throws Exception {

       AsyncTest.getHttpData("网页链接);

       Thread.sleep(1000000);

    }

}

 

本示例使用HttpAsyncClient演示。

 

异步回调链式编排

CompletableFuture提供了50多个API,可以满足所需的各种场景的异步处理的编排,在此列举三个场景:

 

场景1:三个服务并发异步调用,返回CompletableFuture,不阻塞主线程;


方法test1:

   public static void test1() throws Exception {

       HelloClientDemoTest service = new HelloClientDemoTest();

       /**

        * 场景1 两个以上服务并发异步调用,返回CompletableFuture,不阻塞主线程

        * 并且两个服务也是异步非阻塞调用

        */

       CompletableFuture future1 = service.getHttpData("网页链接);

       CompletableFuture future2 = service.getHttpData("网页链接);

       CompletableFuture future3 =service.getHttpData("网页链接);

       List<CompletableFuture> futureList = Lists.newArrayList(future1,future2, future3);

       CompletableFuture<Void> allDoneFuture =CompletableFuture.allOf(futureList.toArray(newCompletableFuture[futureList.size()]));

       CompletableFuture<String> future4 =allDoneFuture.thenApply(v -> {

            List<Object> result =futureList.stream().map(CompletableFuture::join)

                   .collect(Collectors.toList());

            //注意顺序

            String result1 = (String)result.get(0);

            String result2 = (String)result.get(1);

            String result3 = (String)result.get(2);

            //处理业务....

            return result1 + result2 + result3;

        }).exceptionally(e -> {

            //e.printStackTrace();

            return "";

        });

       //返回

    }

 

场景2、两个服务并发异步调用,返回CompletableFuture,不阻塞主线程;


方法test2:

   public void test2() throws Exception {

       HelloClientDemoTest service = new HelloClientDemoTest();

       /**

        * 场景2 两个接口并发异步调用,返回CompletableFuture,不阻塞主线程

        * 并且两个服务也是异步非阻塞调用

        */

       CompletableFuture future1 = service.getHttpData("网页链接);

       CompletableFuture future2 =service.getHttpData("网页链接);

       CompletableFuture future3 =future1.thenCombine(future2, (f1, f2) -> {

            //理业务....

            return f1 + "," + f2;

        }).exceptionally(e -> {

            return "";

        });

       //返回

    }

 

场景3、两个服务,并发异步调用两个服务,并且一个服务的结果返回后再次调用另一服务,然后将三个结果后并处理,返回CompletableFuture,整个处理过程中不阻塞任何线程;

方法test3:

    publicvoid test3() throws Exception {

       HelloClientDemoTest service = new HelloClientDemoTest();

       /**

        * 场景3 两请求依赖调用,然后与另一服务结果组合处理,返回CompletableFuture,不阻塞主线程

        * 并且两个服务也是异步非阻塞调用

        */

        CompletableFuture future1 = service.getHttpData("网页链接);

        CompletableFuture future2 = service.getHttpData("网页链接);

        CompletableFuture<String> future3= future1.thenApply((param) -> {

            CompletableFuture future4 =service.getHttpData("网页链接);

            return future4;

        });

        CompletableFuture future5 =future2.thenCombine(future3, (f2, f3) -> {

            //....处理业务

            return f2 + "," + f3;

        }).exceptionally(e -> {

            return "";

        });

        //返回future5

    }

 

全异步Web系统设计

主要技术:servlet3,JDK8 CompletableFuture,支持异步Callback调用的RPC框架。

 

先看一下处理流程图:

servlet3:Servlet 接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;接着,Servlet 线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。servlet3可参考商品详情页系统的Servlet3异步化实践,结合其中讲解的servlet3整合:

public void submitFuture(finalHttpServletRequest req, final Callable<CompletableFuture> task) throwsException{

       final String uri = req.getRequestURI();

       final Map<String, String[]> params = req.getParameterMap();

       final AsyncContext asyncContext = req.startAsync();

       asyncContext.getRequest().setAttribute("uri", uri);

       asyncContext.getRequest().setAttribute("params", params);

       asyncContext.setTimeout(asyncTimeoutInSeconds * 1000);

       if(asyncListener != null) {

           asyncContext.addListener(asyncListener);

       }

       CompletableFuture future = task.call();

       future.thenAccept(result -> {

           HttpServletResponse resp = (HttpServletResponse)asyncContext.getResponse();

           try {

                if(result instanceof String) {

                    byte[] bytes = new byte[0];

                    if (StringUtils.isBlank(result)){

                       resp.setContentType("text/html;charset=gbk");

                       resp.setContentLength(0);

                    } else {

                        bytes =result.getBytes("GBK");

                    }

                   //resp.setBufferSize(bytes.length);

                   resp.setContentType("text/html;charset=gbk");

                   if(StringUtils.isNotBlank(localIp)) {

                       resp.setHeader("t.ser", localIp);

                    }

                   resp.setContentLength(bytes.length);

                   resp.getOutputStream().write(bytes);

                } else {

                    write(resp,JSONUtils.toJSON(result));

                }

           } catch (Throwable e) {

               resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); //程序内部错误

                try {

                    LOG.error("get infoerror, uri : {},  params : {}", uri,JSONUtils.toJSON(params), e);

                } catch (Exception ex) {

                }

           } finally {

                asyncContext.complete();

           }

       }).exceptionally(e -> {

           asyncContext.complete();

           return null;

       });

}

 

另外还有Java中协程库Quasar,可参考《Java的纤程库 - Quasar》,目前没有在应用中使用并在测试FiberHttpServlet的时候遇到很多坑,日后把Quasar自如运用后形成日记,希望能结实更多的朋友一起研究,踩坑。

 

作者介绍:孙伟,目前负责京东商品详情页统一服务系统,写过java,写过ngx_lua,还写过storm等,喜欢学习研究新事物。

© 著作权归作者所有

javahongxi
粉丝 168
博文 259
码字总数 779422
作品 0
朝阳
程序员
私信 提问
是什么让Node.js比Java更快?

每隔几个星期,就有人发表Java和Node比较的性能评测,像PayPal 或者 Joey Whelan 发表的帖子.作为Node很多公共管理模块核心的维护者和贡献者之一,Strong Loop 很高兴看到Node的获胜。每个人都...

Kris_zcl
2014/05/15
1K
6
漫话:如何给女朋友解释什么是BIO、NIO和AIO?

周末午后,在家里面进行电话面试,我问了面试者几个关于IO的问题,其中包括什么是BIO、NIO和AIO?三者有什么区别?具体如何使用等问题,但是面试者回答的并不是很满意。于是我在面试评价中写...

漫话编程
07/01
421
0
Reactor和Proactor模式

在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作。 同步和异步 同步和异步是针对应用程序和内核的交互而言的,...

ksfzhaohui
2012/12/14
336
0
是什么让Node.js比Java更快?

每隔几个星期,就有人发表Java和Node比较的性能评测,像PayPal 或者 Joey Whelan 发表的帖子.作为Node很多公共管理模块核心的维护者和贡献者之一,Strong Loop 很高兴看到Node的获胜。每个人都...

modernizr
2014/05/20
3.6K
5
Java中BIO,NIO,AIO的理解

在高性能的I/O体系设计中,有几个概念常常会使我们感到迷惑不解。具体如下: 1 什么是同步? 2 什么是异步? 3 什么是阻塞? 4 什么是非阻塞? 5 什么是同步阻塞? 6 什么是同步非阻塞? 7 ...

圣洁之子
03/13
48
0

没有更多内容

加载失败,请刷新页面

加载更多

Mybatis Plus删除

/** @author beth @data 2019-10-17 00:30 */ @RunWith(SpringRunner.class) @SpringBootTest public class DeleteTest { @Autowired private UserInfoMapper userInfoMapper; /** 根据id删除......

一个yuanbeth
今天
4
0
总结

一、设计模式 简单工厂:一个简单而且比较杂的工厂,可以创建任何对象给你 复杂工厂:先创建一种基础类型的工厂接口,然后各自集成实现这个接口,但是每个工厂都是这个基础类的扩展分类,spr...

BobwithB
今天
5
0
java内存模型

前言 Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚。比如本文我们要讨论的JVM内存结构、Java内存模...

ls_cherish
今天
4
0
友元函数强制转换

友元函数强制转换 p522

天王盖地虎626
昨天
5
0
js中实现页面跳转(返回前一页、后一页)

本文转载于:专业的前端网站➸js中实现页面跳转(返回前一页、后一页) 一:JS 重载页面,本地刷新,返回上一页 复制代码代码如下: <a href="javascript:history.go(-1)">返回上一页</a> <a h...

前端老手
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部