Servlet3之NIO線程池隔離

線程隔離主要是針對業務中不同業務場景,按照權重區分使用不同的線程池,以達到某一個業務出現問題,不會將故障擴散到其他的業務線程池,從而達到保證主要業務高可用。
本案例主要講解基於servlet3的線程隔離術。
首先我們回憶一下在tomcat6,tomcat6只支持BIO,它的處理流程如下:
1).tomcat負責接收servletRequest請求
2).將接收的請求分配給servlet處理業務
3).處理完請求之後,通過servletResponse寫會數據
上面這三步都是在一個線程裏面完成的,也就是同步進行。
如下圖:

 

t6.png

tomcat7之後版本引入了servlet3,它基於NIO能處理更大的併發數。
我們可以將整個請求改造成如下步驟:
1).tomcat單線程進行請求解析
2).解析完之後將task放入隊列(可以根據不同業務類型放入不同的隊列)
3).每個隊列指定相應業務線程池對task進行處理
這樣改造以後就可以把業務按照重要性發送到不同線程池,兩個線程池分開獨立配置,互不干擾。當非核心的業務出現問題之後,不會影響核心的業務。另外由於此線程池是有我們創建的,我們可以對該線程池進行監控,處理,靈活了很多。
如下圖:

 

 

下面是實現代碼:

接口層調用

@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";
}

作者:離別刀
鏈接:https://www.jianshu.com/p/0529c126e166
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發佈了217 篇原創文章 · 獲贊 128 · 訪問量 90萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章