【转】商品详情页系统的Servlet3异步化实践

我将从如下几点阐述Servlet3异步化之后的好处:

1、为什么实现请求异步化需要使用Servlet3

2、请求异步化后得到的好处是什么

3、如何使用Servlet3异步化

4、一些Servlet3异步化压测数据

 

首先说下一般http请求处理的流程:

1、容器负责接收并解析请求为HttpServletRequest;

2、然后交给Servlet进行业务处理;

3、最后通过HttpServletResponse写出响应。

 

在Servlet2.x规范中,所有这些处理都是同步进行的,也就是说必须在一个线程中完成从接收请求、业务处理到响应。

 

1、为什么实现请求异步化需要使用Servlet3

此处已Tomcat6举例子,Tomcat6没有实现Servlet3规范,它在处理请求时是通过如下方式实现的:

org.apache.catalina.connector.CoyoteAdapter#service

Java代码  

  1. // Recycle the wrapper request and response  
  2.   if (!comet) {  
  3.       request.recycle();    
  4.       response.recycle();  
  5.   } else {  
  6.       // Clear converters so that the minimum amount of memory  
  7.       // is used by this processor  
  8.       request.clearEncoders();  
  9.       response.clearEncoders();  
  10.   }  

在请求结束时会同步进行请求的回收,也就是说请求解析、业务处理和响应必须在一个线程内完成,不能跨越线程界限。

 

这也就说明了必须使用实现了Servlet3规范的容器进行处理,如Tomcat 7.x。

 

2、请求异步化后得到的好处是什么

2.1、更高的并发能力;

2.2、请求解析和业务处理线程池分离;

2.3、根据业务重要性对业务分级,并分级线程池;

2.4、对业务线程池进行监控、运维、降级等处理。

 

2.1、更高的并发能力

得益于技术的升级,在JDK7配合Tomcat7压测中获得了不错的性能表现。

 

2.2、请求解析和业务处理线程池分离

在引入Servlet3之前我们的线程模型是如下样子的:


 
整个请求解析、业务处理、生成响应都是由Tomcat线程池进行处理,而且都是在一个线程中处理;不能分离线程处理;比如接收到请求后交给其他线程处理,这样不能灵活定义业务处理模型。

 

引入Servlet3之后,我们的线程模型可以改造为如下样子:

 
此处可以看到请求解析使用Tomcat单线程;而解析完成后会扔到业务队列中,由业务线程池进行处理;这种处理方式可以得到如下好处:

1、根据业务重要性对业务进行分级,然后根据分级定义线程池;

2、可以拿到业务线程池,可以进行很多的操作,比如监控、降级等。

 

2.3、根据业务重要性对业务分级,并分级线程池

在一个系统的发展期间,我们一般把很多服务放到一个系统中进行处理,比如库存服务、图书相关服务、延保服务等等;这些服务中我们可以根据其重要性对业务分级别和做一些限制:

1、可以把业务分为核心业务级别和非核心业务级别;

2、为不同级别的业务定义不同的线程池,线程池之间是隔离的;

3、根据业务量定义各级别线程池大小。

 

此时假设非核心业务因为数据库连接池或者网络问题抖动造成响应时间慢,不会对我们核心业务产生影响。

 

2.4、对业务线程池进行监控、运维、降级等处理

因为业务线程池从Tomcat中分离出来,可以进行线程池的监控,比如查看当前处理的请求有多少,是否到了负载瓶颈,到了瓶颈后可以进行业务报警等处理。


上图是我们一个简陋的监控图,可实时查看到当前处理情况:正在处理的任务有多少,队列中等待的任务有多少;可以根据这些数据进行监控和预警。

 

另外我们还可以进行一些简单的运维:

对业务线程池进行扩容,或者业务出问题时立即清空线程池防止容器崩溃等问题;而不需要等待容器重启(容器重启需要耗费数十秒甚至数几十毫秒、而且启动后会有预热问题,而造成业务产生抖动)。

 

如果发现请求处理不过来,负载比较高,最简单的办法就是直接清空线程池,将老请求拒绝掉,而没有雪崩效应。

 

 

因为业务队列和业务线程池都是自己的,可以对这些基础组件做很多处理,比如定制业务队列,按照用户级别对用户请求排序,高级别用户得到更高优先级的业务处理。

 

3、如何使用Servlet3异步化

对于Servlet3的使用,可以参考我之前的博客:

  Servlet3.1规范(最终版)中文版下载 

  Servlet3.1学习示例

 

而在我项目里使用就比较简单:

 

1、接收请求 

Java代码  

  1. @RequestMapping("/book")  
  2. public void getBook(HttpServletRequest request, @RequestParam(value="skuId") final Long skuId,  
  3.                     @RequestParam(value="cat1") final Integer cat1, @RequestParam(value="cat2") final Integer cat2) throws Exception {  
  4.   
  5.     oneLevelAsyncContext.submitFuture(request, () -> bookService.getBook(skuId, cat1, cat2));  
  6. }  

通过一级业务线程池接收请求,并提交业务处理到该线程池;  

 

 

2、业务线程池封装

Java代码  

  1. public void submitFuture(final HttpServletRequest req, final Callable<Object> task) {  
  2.     final String uri = req.getRequestURI();  
  3.     final Map<String, String[]> params = req.getParameterMap();  
  4.     final AsyncContext asyncContext = req.startAsync();  //开启异步上下文  
  5.     asyncContext.getRequest().setAttribute("uri", uri);  
  6.     asyncContext.getRequest().setAttribute("params", params);  
  7.     asyncContext.setTimeout(asyncTimeoutInSeconds * 1000);  
  8.     if(asyncListener != null) {  
  9.         asyncContext.addListener(asyncListener);  
  10.     }  
  11.     executor.submit(new CanceledCallable(asyncContext) { //提交任务给业务线程池  
  12.         @Override  
  13.         public Object call() throws Exception {  
  14.             Object o = task.call();  //业务处理调用  
  15.             if(o == null) {  
  16.                 callBack(asyncContext, o, uri, params);  //业务完成后,响应处理  
  17.             }  
  18.             if(o instanceof CompletableFuture) {  
  19.                 CompletableFuture<Object> future = (CompletableFuture<Object>)o;  
  20.                 future.thenAccept(resultObject -> callBack(asyncContext, resultObject, uri, params))  
  21.                 .exceptionally(e -> {  
  22.                     callBack(asyncContext, "", uri, params);  
  23.                     return null;  
  24.                 });  
  25.             } else if(o instanceof String) {  
  26.                 callBack(asyncContext, o, uri, params);  
  27.             }  
  28.             return null;  
  29.         }  
  30.     });  
  31. }  

Java代码  

  1. private void callBack(AsyncContext asyncContext, Object result, String uri, Map<String, String[]> params) {  
  2.     HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();  
  3.     try {  
  4.         if(result instanceof String) {  
  5.             write(resp, (String)result);  
  6.         } else {  
  7.             write(resp, JSONUtils.toJSON(result));  
  8.         }  
  9.     } catch (Throwable e) {  
  10.         resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); //程序内部错误  
  11.         try {  
  12.             LOG.error("get info error, uri : {},  params : {}", uri, JSONUtils.toJSON(params), e);  
  13.         } catch (Exception ex) {  
  14.         }  
  15.     } finally {  
  16.         asyncContext.complete();  
  17.     }  
  18. }  

 

  

线程池的初始化

Java代码  

  1. @Override  
  2. public void afterPropertiesSet() throws Exception {  
  3.     String[] poolSizes = poolSize.split("-");  
  4.     //初始线程池大小  
  5.     int corePoolSize = Integer.valueOf(poolSizes[0]);  
  6.     //最大线程池大小  
  7.     int maximumPoolSize = Integer.valueOf(poolSizes[1]);  
  8.     queue = new LinkedBlockingDeque<Runnable>(queueCapacity);  
  9.     executor = new ThreadPoolExecutor(  
  10.             corePoolSize, maximumPoolSize,  
  11.             keepAliveTimeInSeconds, TimeUnit.SECONDS,  
  12.             queue);  
  13.   
  14.     executor.allowCoreThreadTimeOut(true);  
  15.     executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {  
  16.         @Override  
  17.         public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {  
  18.             if(r instanceof CanceledCallable) {  
  19.                 CanceledCallable cc = ((CanceledCallable) r);  
  20.                 AsyncContext asyncContext = cc.asyncContext;  
  21.                 if(asyncContext != null) {  
  22.                     try {  
  23.                         String uri = (String) asyncContext.getRequest().getAttribute("uri");  
  24.                         Map params = (Map) asyncContext.getRequest().getAttribute("params");  
  25.                         LOG.error("async request rejected, uri : {}, params : {}", uri, JSONUtils.toJSON(params));  
  26.                     } catch (Exception e) {}  
  27.                     try {  
  28.                         HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();  
  29.                         resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);  
  30.                     } finally {  
  31.                         asyncContext.complete();  
  32.                     }  
  33.                 }  
  34.             }  
  35.         }  
  36.     });  
  37.   
  38.     if(asyncListener == null) {  
  39.         asyncListener = new AsyncListener() {  
  40.             @Override  
  41.             public void onComplete(AsyncEvent event) throws IOException {  
  42.             }  
  43.   
  44.             @Override  
  45.             public void onTimeout(AsyncEvent event) throws IOException {  
  46.                 AsyncContext asyncContext = event.getAsyncContext();  
  47.                 try {  
  48.                     String uri = (String) asyncContext.getRequest().getAttribute("uri");  
  49.                     Map params = (Map) asyncContext.getRequest().getAttribute("params");  
  50.                     LOG.error("async request timeout, uri : {}, params : {}", uri, JSONUtils.toJSON(params));  
  51.                 } catch (Exception e) {}  
  52.                 try {  
  53.                     HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();  
  54.                     resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);  
  55.                 } finally {  
  56.                     asyncContext.complete();  
  57.                 }  
  58.             }  
  59.   
  60.             @Override  
  61.             public void onError(AsyncEvent event) throws IOException {  
  62.                 AsyncContext asyncContext = event.getAsyncContext();  
  63.                 try {  
  64.                     String uri = (String) asyncContext.getRequest().getAttribute("uri");  
  65.                     Map params = (Map) asyncContext.getRequest().getAttribute("params");  
  66.                     LOG.error("async request error, uri : {}, params : {}", uri, JSONUtils.toJSON(params));  
  67.                 } catch (Exception e) {}  
  68.                 try {  
  69.                     HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();  
  70.                     resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);  
  71.                 } finally {  
  72.                     asyncContext.complete();  
  73.                 }  
  74.             }  
  75.   
  76.             @Override  
  77.             public void onStartAsync(AsyncEvent event) throws IOException {  
  78.   
  79.             }  
  80.         };  
  81.     }  
  82.   
  83. }  

 

 

3、业务处理

执行bookService.getBook(skuId, cat1, cat2)进行业务处理。

 

4、返回响应

在之前封装的异步线程池上下文中直接返回。

 

5、Tomcat server.xml的配置

Java代码  

  1. <Connector port="1601" asyncTimeout="10000" acceptCount="10240" maxConnections="10240" acceptorThreadCount="1"  minSpareThreads="1" maxThreads="1" redirectPort="8443" processorCache="1024" URIEncoding="UTF-8" protocol="org.apache.coyote.http11.Http11NioProtocol" enableLookups="false"/>  

我们升级到了jdk1.8.0_51 +tomcat
8.0.26,在使用Http11Nio2Protocol时遇到一些问题,暂时还是使用的Http11Nio1Protocol。此处可以看到Tomcat线程池我们配置了maxThreads=1,即一个线程进行请求解析。

 

 

 

4、一些Servlet3异步化压测数据

压测机器基本环境:32核CPU、32G内存;jdk1.7.0_71 + tomcat 7.0.57,服务响应时间在20ms+,使用最简单的单个URL压测吞吐量:

 

1、使用同步方式压测

Java代码  

  1. siege-3.0.7]# ./src/siege -c100 -t60s -b http://***.item.jd.com/981821   
  2. Transactions: 279187 hits  
  3. Availability: 100.00 %  
  4. Elapsed time: 59.33 secs  
  5. Data transferred: 1669.41 MB  
  6. Response time: 0.02 secs  
  7. Transaction rate: 4705.66 trans/sec  
  8. Throughput: 28.14 MB/sec  
  9. Concurrency: 99.91  
  10. Successful transactions: 279187  
  11. Failed transactions: 0  
  12. Longest transaction: 1.04  
  13. Shortest transaction: 0.00  

 

2.1、 使用Servlet3异步化压测 100并发、60秒:

Java代码  

  1. siege-3.0.7]# ./src/siege -c100 -t60s -b http://***.item.jd.com/981821 .  
  2. Transactions: 337998 hits  
  3. Availability: 100.00 %  
  4. Elapsed time: 59.09 secs  
  5. Data transferred: 2021.07 MB  
  6. Response time: 0.03 secs  
  7. Transaction rate: 5720.05 trans/sec  
  8. Throughput: 34.20 MB/sec  
  9. Concurrency: 149.79  
  10. Successful transactions: 337998  
  11. Failed transactions: 0  
  12. Longest transaction: 1.07  
  13. Shortest transaction: 0.00  

 

2.2、使用Servlet3异步化压测 600并发、60秒:

Java代码  

  1. siege-3.0.7]# ./src/siege -c600 -t60s -b http://***.item.jd.com/981821   
  2. Transactions: 370985 hits  
  3. Availability: 100.00 %  
  4. Elapsed time: 59.16 secs  
  5. Data transferred: 2218.32 MB  
  6. Response time: 0.10 secs  
  7. Transaction rate: 6270.88 trans/sec  
  8. Throughput: 37.50 MB/sec  
  9. Concurrency: 598.31  
  10. Successful transactions: 370985  
  11. Failed transactions: 0  
  12. Longest transaction: 1.32  
  13. Shortest transaction: 0.00  

 

  

可以看出异步化之后吞吐量提升了,但是响应时间长了,也就是异步化并不会提升响应时间,但是会增加吞吐量和增加我们需要的灵活性。

 

 

通过异步化我们不会获得更快的响应时间,但是我们获得了整体吞吐量和我们需要的灵活性:请求解析和业务处理线程池分离;根据业务重要性对业务分级,并分级线程池;对业务线程池进行监控、运维、降级等处理。

原文链接:[http://wely.iteye.com/blog/2346283]

时间: 2024-09-09 11:51:27

【转】商品详情页系统的Servlet3异步化实践的相关文章

【转】构建需求响应式亿级商品详情页

商品详情页是什么 商品详情页是展示商品详细信息的一个页面,承载在网站的大部分流量和订单的入口.京东商城目前有通用版.全球购.闪购.易车.惠买车.服装.拼购.今日抄底等许多套模板.各套模板的元数据是一样的,只是展示方式不一样.目前商品详情页个性化需求非常多,数据来源也是非常多的,而且许多基础服务做不了的都放我们这,因此我们需要一种架构能快速响应和优雅的解决这些需求问题.因此我们重新设计了商品详情页的架构,主要包括三部分:商品详情页系统.商品详情页统一服务系统和商品详情页动态服务系统:商品详情页系统

京东技术架构(二)构建需求响应式亿级商品详情页

该文章是根据velocity 2015技术大会的演讲<京东网站单品页618实战>细化而来,希望对大家有用. 商品详情页是什么 商品详情页是展示商品详细信息的一个页面,承载在网站的大部分流量和订单的入口.京东商城目前有通用版.全球购.闪购.易车.惠买车.服装.拼购.今日抄底等许多套模板.各套模板的元数据是一样的,只是展示方式不一样.目前商品详情页个性化需求非常多,数据来源也是非常多的,而且许多基础服务做不了的都放我们这,因此我们需要一种架构能快速响应和优雅的解决这些需求问题.因此我们重新设计了商

亿级流量电商详情页系统的大型高并发与高可用缓存架构实战

对于高并发的场景来说,比如电商类,o2o,门户,等等互联网类的项目,缓存技术是Java项目中最常见的一种应用技术.然而,行业里很多朋友对缓存技术的了解与掌握,仅仅停留在掌握redis/memcached等缓存技术的基础使用,最多了解一些集群相关的知识,大部分人都可以对缓存技术掌握到这个程度.然而,仅仅对缓存相关的技术掌握到这种程度,无论是对于开发复杂的高并发系统,或者是在往Java高级工程师.Java资深工程师.Java架构师这些高阶的职位发展的过程中,都是完全不够用的.技术成长出现瓶颈,在自己

亿级流量电商详情页系统实战:缓存架构+高可用服务架构+微服务架构

<缓存架构+高可用服务架构+微服务架构>深入讲解了亿级流量电商详情页系统的完整大型架构.同时最重要的是,在完全真实的大型电商详情页系统架构下,全流程实战了整套微服务架构,包含了基于领域驱动设计进行微服务建模.Spring Cloud.基于DevOps的持续交付流水线与自动化测试套件.基于Docker的自动化部署.此外,还包含了大型电商详情页系统架构中的多种复杂架构设计的详细介绍. <亿级流量电商详情页系统实战(第一版)>的内容,主要是基于简化以后的大型电商详情页系统的背景,重点包含

android 自定义ViewGroup实现仿淘宝的商品详情页

最近公司在新版本上有一个需要, 要在首页添加一个滑动效果, 具体就是仿照X宝的商品详情页, 拉到页面底部时有一个粘滞效果,  如下图 X东的商品详情页,如果用户继续向上拉的话就进入商品图文描述界面: 刚开始是想拿来主义,直接从网上找个现成的demo来用, 但是网上无一例外的答案都特别统一: 几乎全部是ScrollView中再套两个ScrollView,或者是一个LinearLayout中套两个ScrollView. 通过指定父view和子view的focus来切换滑动的处理界面---即通过vie

安卓(android)仿电商app商品详情页按钮浮动效果_Android

1.效果图如下: 这效果用户体验还是很酷炫,今天我们就来讲解如何实现这个效果. 2.分析 为了方便理解,作图分析 如图所示,整个页面分为四个部分:      1.悬浮内容,floatView      2.顶部内容,headView      3.中间内容,与悬浮内容相同,middleView      4.商品详情展示页面,detailView 因为页面内容高度会超出屏幕,所以用Scrollview实现滚动,悬浮view与scrollview同级,都在一个帧布局或者相对布局中. 当y方向的滚动

B2C网站商品详情页如何设计相关商品推荐?

为什么要做相关商品推荐? 商品详情是可能挖出金子的岛屿,我们都知道. 于是我们使了各种招式,终于让用户来到了商品详情页.我们悄悄念起魔鬼的咒语,恨不得用户马上去点全页最醒目的那个"加入购物车"或"立刻购买".可是,绝大部分B2C商详页的UV转化率不超过5%(何况是PV!),绝大部分用户最终是不会购买这个商品的,有可能他是被大胸的模特图骗进来的,有可能价格不合适,有可能商品细节不喜欢,有可能大多数的好评里有一个让他难以接受的差评,总之,他不想买. 难道让用户就这么流失

仿淘宝商品详情页上拉弹出新ViewController

新项目就要开始做了,里面有购物那块,就试着先把淘宝商品详情页的效果做了一下. 1.需求 1.第一次上拉时,A视图拉到一定距离将视图B从底部弹出,A视图也向上 2.显示B视图时下拉时,有刷新效果,之后将A回到原处,B向下 3.A视图再次上拉时,能看到B视图,拉到一定距离时,和1一样A.B向上 2.思路 刚看到时,我是想到我们小区宝2.0的登录弹出效果,想着A向上滑动一定距离时A.B向上,B向下滑动时让A.B向下滑动,实现了之后和小伙伴讨论了一下,发现了一些细节这样是实现不了的,就是A上拉时可以看到

淘宝无线-仿淘宝商品详情页的&amp;amp;quot;拖动效果&amp;amp;quot;,和拖出来的&amp;amp;quot;导航置顶&amp;amp;quot;滑动导航下的视图

问题描述 仿淘宝商品详情页的"拖动效果",和拖出来的"导航置顶"滑动导航下的视图 csdn也有demo叫仿淘宝商品详情页 大家不要给我推荐那个,因为那个的布局拖动直接置顶的效果是有,但是放的是图片,没有解决下面放viewpager所出现的问题.现在把下面拖出来的布局换成横向滚动的导航+pager内fragment切换.出现如下问题: viewpager 不显示 解决办法有两种. 第 一种方式是给嵌套pager的scrollview 加android:fillView