Servlet3之NIO线程池隔离

线程隔离主要是针对业务中不同业务场景,按照权重区分使用不同的线程池,以达到某一个业务出现问题,不会将故障扩散到其他的业务线程池,从而达到保证主要业务高可用。
本案例主要讲解基于servlet3的线程隔离术。
首先我们回忆一下在tomcat6,tomcat6只支持BIO,它的处理流程如下:
1).tomcat负责接收servletRequest请求
2).将接收的请求分配给servlet处理业务
3).处理完请求之后,通过servletResponse写会数据
上面这三步都是在一个线程里面完成的,也就是同步进行。
如下图:


t6.png

tomcat7之后版本引入了servlet3,它基于NIO能处理更大的并发数。
我们可以将整个请求改造成如下步骤:
1).tomcat单线程进行请求解析
2).解析完之后将task放入队列(可以根据不同业务类型放入不同的队列)
3).每个队列指定相应业务线程池对task进行处理
这样改造以后就可以把业务按照重要性发送到不同线程池,两个线程池分开独立配置,互不干扰。当非核心的业务出现问题之后,不会影响核心的业务。另外由于此线程池是有我们创建的,我们可以对该线程池进行监控,处理,灵活了很多。
如下图:


t7.png

下面是实现代码:

接口层调用

@RestController
@RequestMapping("/app")
public class NIOCtrl {
    @Autowired
    private LocalNewsAsyncContext localNewsAsyncContext;
    @Autowired
    private NewsService newsService;

    @RequestMapping("/news")
    public void getNews(HttpServletRequest request,@RequestParam(value = "type",required = false) String type){
        if("1".equals(type)){
            localNewsAsyncContext.submit(request, () -> newsService.getNews());
            return;
        }
        localNewsAsyncContext.submit(request, () -> newsService.getNewsMap());
    }
}

将请求丢进指定线程池

@Service
public class LocalNewsAsyncContext {
    private final static Long timeOutSeconds= 5L;
    @Autowired
    private CustomAsyncListener asyncListener;
    @Autowired
    private ThreadPoolExecutor executor;

    public void submit(final HttpServletRequest request,final Callable<Object> task){
        final String uri= request.getRequestURI();
        final Map<String,String[]> params= request.getParameterMap();
        //开启异步上下文
        final AsyncContext asyncContext= request.startAsync();
        asyncContext.getRequest().setAttribute(Constant.URI,uri);
        asyncContext.getRequest().setAttribute(Constant.PARAMS, params);
        asyncContext.setTimeout(timeOutSeconds * 1000);
        if(asyncContext!=null){
            asyncContext.addListener(asyncListener);
        }
        executor.submit(new CustomCallable(asyncContext, task));

    }
}

自定义线程处理

public class CustomCallable implements Callable{
    private static final Logger LOG = LoggerFactory.getLogger(CustomCallable.class);

    public AsyncContext asyncContext;
    private Callable<Object> task;
    private String uri;
    private  Map<String,String[]> params;

    public CustomCallable(AsyncContext asyncContext, Callable<Object> task){
        this.asyncContext= asyncContext;
        this.task= task;
        this.uri= (String) asyncContext.getRequest().getAttribute(Constant.URI);
        this.params= (Map<String, String[]>) asyncContext.getRequest().getAttribute(Constant.PARAMS);
    }
    @Override public Object call() throws Exception {
        Object o= task.call();
        if(o==null){
            callback(asyncContext,o);
        }else if(o instanceof String){
            callback(asyncContext, o);
        }else if(o instanceof CompletableFuture){
            CompletableFuture<Object> future= (CompletableFuture<Object>) o;
            future.thenAccept(o1 -> callback(asyncContext, o1))
                    .exceptionally(throwable -> {
                        callback(asyncContext,"");
                        return null;
                    });
        }else {
            callback(asyncContext, o);
        }
        return null;
    }

    private void callback(AsyncContext asyncContext,Object result){
        HttpServletResponse response= (HttpServletResponse) asyncContext.getResponse();
        try{
            if(result instanceof String){
                write(response, (String) result);
            }else {
                write(response, JSON.toJSONString(result));
            }
        }catch (Exception e){
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            e.printStackTrace();
            try {
                LOG.error("get info error for uri:{}, params:{}",uri,JSON.toJSONString(params),e);
            }catch (Exception e1){}
        }finally {
            asyncContext.complete();
        }
    }

    private void write(HttpServletResponse response,String result) throws IOException {
        response.getOutputStream().write(result.getBytes());
    }
}

定义业务线程池

@Configuration
public class LocalNewsPoolConfig {
    private final static Logger LOG= LoggerFactory.getLogger(LocalNewsPoolConfig.class);

    @Bean
    public ThreadPoolExecutor init(){
        int corePoolSize= 10;
        int maximumPoolSize= 100;
        int queueCapacity= 200;
        LinkedBlockingDeque<Runnable> queue= new LinkedBlockingDeque<>(queueCapacity);
        ThreadPoolExecutor executor= new ThreadPoolExecutor(corePoolSize,maximumPoolSize,60L, TimeUnit.SECONDS,queue);
        executor.allowCoreThreadTimeOut(true);
        executor.setRejectedExecutionHandler((r, executor1) -> {
            if(r instanceof CustomCallable){
                CustomCallable call= (CustomCallable) r;
                AsyncContext asyncContext= call.asyncContext;
                if(asyncContext!=null){
                    handler(asyncContext);
                }
            }
        });
        return executor;
    }

    private static void handler(AsyncContext asyncContext){
        try{
            ServletRequest req= asyncContext.getRequest();
            String uri= (String) req.getAttribute(Constant.URI);
            Map params= (Map) req.getAttribute(Constant.PARAMS);
            LOG.error("async req rejected. uri :{},params:{}",uri, JSON.toJSONString(params));
        }catch (Exception e){
            e.printStackTrace();
            try{
                HttpServletResponse response= (HttpServletResponse) asyncContext.getResponse();
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }catch (Exception e1){}
        }finally {
            asyncContext.complete();
        }
    }
    
}

定义异步请求监听

@Component
public class CustomAsyncListener implements AsyncListener {
    private Logger LOG= LoggerFactory.getLogger(CustomAsyncListener.class);
    @Override public void onComplete(AsyncEvent asyncEvent) throws IOException {

    }

    @Override public void onTimeout(AsyncEvent asyncEvent) throws IOException {
        AsyncContext asyncContext= asyncEvent.getAsyncContext();
        try{
            ServletRequest req= asyncContext.getRequest();
            String uri= (String) req.getAttribute(Constant.URI);
            Map params= (Map) req.getAttribute(Constant.PARAMS);
            LOG.error("async req timeOut. uri :{},params:{}",uri, JSON.toJSONString(params));
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                HttpServletResponse response= (HttpServletResponse) asyncContext.getResponse();
                response.setStatus(HttpServletResponse.SC_REQUEST_TIMEOUT);
            }catch (Exception e1){}
            asyncContext.complete();
        }
    }

    @Override public void onError(AsyncEvent asyncEvent) throws IOException {
        AsyncContext asyncContext= asyncEvent.getAsyncContext();
        try{
            ServletRequest req= asyncContext.getRequest();
            String uri= (String) req.getAttribute(Constant.URI);
            Map params= (Map) req.getAttribute(Constant.PARAMS);
            LOG.error("async req error. uri :{},params:{}",uri, JSON.toJSONString(params));
        }catch (Exception e){
            e.printStackTrace();
            try{
                HttpServletResponse response= (HttpServletResponse) asyncContext.getResponse();
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }catch (Exception e1){}
        }finally {
            asyncContext.complete();
        }
    }

    @Override public void onStartAsync(AsyncEvent asyncEvent) throws IOException {

    }
}

业务处理和常量类

@Service
public class NewsService {
    public String getNews(){
        return "servlet3 nio test.";
    }
    public StringBuilder getNewsMap(){
        return new StringBuilder("I do and i understand.");
    }
}
public class Constant {
    public static final String URI= "uri";
    public static final String PARAMS= "params";
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容