【干货】异步并发实战

11,825 阅读8分钟

觉得不错请按下图操作,掘友们,哈哈哈!!! image.png

一:前言

在目前比较常见的网站中,首页、活动页、商品详情页等系绕承载了网站的大部分流量,而这些系统的主要职责包括数据的聚合拼裝、热点数据统计、缓存、下游功能降级开关、兜底数据等。其中数据聚合需要调用多个其他服务获取数据、拼裝数据以及相应模板,然后返回给前端,聚合数据的数据源主要有来源于依赖系统活着服务、级存、数据库等。而系统之间的调用可以通过如HTTP 接口调用 (如 HttpClient)、RPC 服务调用(如dubbo、thrift), 等实现。 在我们比较常见的Java应用中,比如使用 Tomcat,一个请求会分配一个线程进行请求处理,该线程负责 获取数据、拼装数据,然后返回给前端。在同步调用获取数据接口的情况下(等待依赖系统返回数据),整个线程是一直被占用并阻塞的。如果有大量的这种请求,则每个请求占用一个线程,但线程一直处于阻塞,降低了系统的吞吐量,这将导致应用的吞吐量下降。我们希望,在调用依赖的服务响应比较慢时,应该让出线程和 CPU 来处理下一个请求,当依赖的服务返回后再分配相应的线程来继续处理。而这应该有更好的解决方案:异步/协程。而 Java 是不支持协程的(虽然有些 Java 框架号称支持,但还是高层 API 的封裝),Python支持比较好,因此,在Java 中我们可以使用异步来提升吞吐量。目前大部分 Java开源框架 (HttpAsynClient、 Dubbo、thrift 等)都支持。 另外,应用中一个服务可能会调用多个依赖服务来处理业务,而这些依赖服务是可以同时调用的。如果顺序调用的话需要耗时 500ms,而并发调用只需要 50ms, 那么可以使用 Java 并发机制来并发调用依赖服务,从而降低该服务的响应时间,后边的异步编排会讲的很清晰。

在实际开发应用系统过程中,通过异步并发并不能使响应变得更快,更多是为了提升吞吐量、对请求更细粒度控制,或是通过多依赖服务并发调用降低服务响应时间。当一个线程在处理任务时,通过Fork多个线程来处理任务并等待这些线程的处理结果,这种应用并不是真正的异步。异步是针对CPU和IO来说的的,当一个线程的IO没有就绪时要让出CPU来处理其他任务,这才是异步。在这里就不介绍异步并发实现原理,主要介绍在Java应用中如何运用这些技术,而且大多数场景并不是真正的异步化,在Java中真正实现异步化是非常难的事情,如MySQLJDBC驱动等很多都是BIO设计,大多数情况下说的异步并发是通过线程池模拟实现。 发是通过线程池模拟 实现。

二:同步阻塞调用

虽然很简单,但是还是搞出来一个例子比较直观:

public class BlogCode {
    //即串行调用,响应时间为所有依赖服务的响应时间总和。
    public static void main(String[] args) throws Exception {
        RpcService rpcService = new RpcService();
        HttpService httpService = new HttpService();
        //耗时为10ms
        Map<String, String> result = rpcService.getRpcResult();
        //耗时为20ms
        Map<String, String> result2 = httpService.getHttpResult();
        //总耗时为30ms
    }

    static class RpcService {
        public Map<String, String> getRpcResult() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    static class HttpService {
        public Map<String, String> getHttpResult() {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

}

三:异步Future

线程池配合Future实现,但是阻塞主请求线程,高并发时依然会造成线程数过多、CPU上下文切换。通过Future可以并发发出N个请求,然后等待最慢的一个返回,总响应时问为最慢的一个请求返回的用时。如下请求如果并发访问,则响应可以在30ms后返回。

image.png

public void BlogFuture() {
    RpcService rpcService = new RpcService();
    HttpService httpService = new HttpService();
    Future<Map<String, String>> futureRpc = null;
    Future<Map<String, String>> futureHttp = null;
    try {
        futureRpc = executor.submit(() -> rpcService.getRpcResult());
        futureHttp = executor.submit(() -> httpService.getHttpResult());
        // 耗时为10ms
        Map<String, String> resultRpc = futureRpc.get(300, TimeUnit.MILLISECONDS);
        // 耗 时 为2 0 ms
        Map<String, String> resultHttp = futureHttp.get(300, TimeUnit.MILLISECONDS);

        // 总耗时为20毫秒
    } catch (Exception e) {
        if (futureRpc != null) {
            futureRpc.cancel(true);
        }
        if (futureHttp != null) {
            futureHttp.cancel(true);
        }
        throw new RuntimeException(e);
    }
}

static class RpcService {
    public Map<String, String> getRpcResult() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

static class HttpService {
    public Map<String, String> getHttpResult() {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

四:异步CallBack

再一种通过回调机制实现,即首先发出对应服务请求,当服务返回时回调相关方法,如HttpAsyncClien 使用基于N1O的异步I/O模型实现,它实现了Rcactor模式,摒弃阻塞IO模型one thread per connection ,采用线程池分发事件通知,从而有效支撑大量并发连接。这种机制并不能提升性能,而是为了支撑大量并发连接或者提升吞吐量。

  public static HttpAsyncClient httpAsyncClient;

    public static CompletableFuture<String> getHttpData (String url) {
        CompletableFuture asyncFuture = new CompletableFuture () ;
        HttpAsyncRequestProducer producer = HttpAsyncMethods.create(new HttpPost(url));
        BasicAsyncResponseConsumer consumer = new BasicAsyncResponseConsumer();
        FutureCallback callback = new FutureCallback<HttpResponse> () {
            @Override
            public void completed(HttpResponse response) {
                asyncFuture.complete(response);
            }

            @Override
            public void failed(Exception e) {
                asyncFuture.completeExceptionally(e);
            }

            @Override
            public void cancelled() {
                asyncFuture.cancel (true);
            }
        };
        httpAsyncClient.execute(producer,consumer,callback);
        return asyncFuture;

    }


public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<String>  feature = getHttpData("www.baidu.com");
    String data = feature.get();
}

这种异步实现可以配合CompletableFuture 达到半异步的效果。

五:异步编排

JDK8 CompletableFuture提供了新的异步编程思路,可以对多个异步处理进行编排,实现更复杂的异步处理。其内部使用ForkJoinPool实现异步处理。使用CompletableFuture可以把回调方式的实现转变为同步调用实现。CompletableFuture提供了50多个API,可以满足各种所需场景的异步处理编排,具体场景使用在我另外一篇文章中有详细demo。

Java CompletableFuture实现多线程异步编排

六:异步web服务

image.png

借助Servlet 3、CompletableFuture 实现异步Web 服务。如下是整个处理流程。

Servlet 容器接收到请求之后,Tomcat 需要先解析请求体,然后通过异步Servlet 将 请求交给异步线程池来完成业务处理,Tomcat 线程释放回容器。通过异步机制可以提升 Tomcat 容器的吞吐量。

import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * @Author xiaomayi
 * @Date 2023/6/28
 */
@Slf4j
@Component
public class OneLevelAsyncContext implements InitializingBean {
    private final String URI = "uri";
    private final String PARAMS = "params";

    private AsyncListener asyncListener;
    private LinkedBlockingDeque<Runnable> queue;
    private ThreadPoolExecutor executor;


//    public void submitFuture(final HttpServletRequest req, final Callable<Object> task) {
//        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(2 * 1000);
//        if(asyncListener != null) {
//            asyncContext.addListener(asyncListener);
//        }
//        executor.submit((Runnable) new CanceledCallable(asyncContext) { 
//            //提交任务给业务线程池
//            @Override
//            public Object call() throws Exception {
//                Object o = task.call();  //业务处理调用
//                if(o == null) {
//                    callBack(asyncContext, o, uri, params);  //业务完成后,响应处理
//                }
//                if(o instanceof CompletableFuture) {
//                    CompletableFuture<Object> future = (CompletableFuture<Object>)o;
//                    future.thenAccept(resultObject -> callBack(asyncContext, resultObject, uri, params))
//                            .exceptionally(e -> {
//                                callBack(asyncContext, "", uri, params);
//                                return null;
//                            });
//                } else if(o instanceof String) {
//                    callBack(asyncContext, o, uri, params);
//                }
//                return null;
//            }
//        });
//    }
//
//    private void callBack(AsyncContext asyncContext, Object result, String uri, Map<String, String[]> params) {
//        HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();
//        try {
//            if(result instanceof String) {
////                write(resp, (String)result);
//            } else {
////                write(resp, JSONUtils.toJSONString(result));
//            }
//        } catch (Throwable e) {
//            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); //程序内部错误
//            try {
//                log.error("get info error, uri : {},  params : {}", uri, JSONUtils.toJSON(params), e);
//            } catch (Exception ex) {
//            }
//        } finally {
//            asyncContext.complete();
//        }
//    }
    
    public Object submitFuture(final HttpServletRequest request, final Callable<Object> task) throws ExecutionException, InterruptedException {
        // 获取请求URI
        final String uri = request.getRequestURI();
        // 获取请求参数
        final Map<String, String[]> params = request.getParameterMap();

        // 开启异步上下文
        final AsyncContext asyncContext = request.startAsync();
        asyncContext.getRequest().setAttribute(URI, uri);
        asyncContext.getRequest().setAttribute(PARAMS, params);
        // 超时设置
        asyncContext.setTimeout(2 * 1000);
        if (Objects.nonNull(asyncListener)) {
            asyncContext.addListener(this.asyncListener);
        }

        // 线程池处理业务
        Future<Object> future = executor.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                // 业务处理
                Object result = task.call();
                return result;
            }
        });
        // 完成异步上下文,否则报超时等异常
        asyncContext.complete();
        return future.get();
    }


    // 完成初始化配置
    @Override
    public void afterPropertiesSet() throws Exception {
        // 线程池大小
        int corePoolSize = Integer.parseInt("100");
        // 最大线程池大小
        int maxNumPoolSize = Integer.parseInt("200");
        // 任务队列
        queue = new LinkedBlockingDeque<Runnable>();
        // 创建线程池
        executor = new ThreadPoolExecutor(corePoolSize, maxNumPoolSize, 100, TimeUnit.MILLISECONDS, queue);

        executor.allowCoreThreadTimeOut(true);
        // 线程池饱和处理
        executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                if (r instanceof CanceledCallable) {
                    CanceledCallable cc = ((CanceledCallable) r);
                    AsyncContext asyncContext = cc.asyncContext;
                    try {
                        ServletRequest request = asyncContext.getRequest();
                        String uri = (String) request.getAttribute(URI);
                        Map params = (Map) request.getAttribute(PARAMS);
                        log.error(String.format("async request %s, uri:%s, params:%s", "rejectedExecution", uri, JSON.toJSONString(params)));
                    } catch (Exception ex) {

                    }
                    try {
                        HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
                        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    } finally {
                        asyncContext.complete();
                    }
                }
            }

        });

        // 创建监听器
        if (Objects.isNull(asyncListener)) {
            asyncListener = new AsyncListener() {
                @Override
                public void onComplete(AsyncEvent asyncEvent) throws IOException {

                }

                @Override
                public void onTimeout(AsyncEvent asyncEvent) throws IOException {
                    AsyncContext asyncContext = asyncEvent.getAsyncContext();
                    try {
                        ServletRequest request = asyncContext.getRequest();
                        String uri = (String) request.getAttribute(URI);
                        Map params = (Map) request.getAttribute(PARAMS);

                        log.error(String.format("async request timeout, uri:%s, params:%s", uri, JSON.toJSONString(params)));
                    } catch (Exception e) {
                    }
                    try {
                        HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    } finally {
                        asyncContext.complete();
                    }
                }

                @Override
                public void onError(AsyncEvent asyncEvent) throws IOException {
                    AsyncContext asyncContext = asyncEvent.getAsyncContext();
                    try {
                        ServletRequest request = asyncContext.getRequest();
                        String uri = (String) request.getAttribute(URI);
                        Map params = (Map) request.getAttribute(PARAMS);

                        log.error(String.format("async request error, uri:%s, params:%s", uri, JSON.toJSONString(params)));
                    } catch (Exception e) {
                    }
                    try {
                        HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    } finally {
                        asyncContext.complete();
                    }
                }

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

                }

            };
        }
    }

}

import javax.servlet.AsyncContext;
import java.util.concurrent.Callable;

/**
 * @Author xiaomayi
 * @Date 2023/6/28
 */
public class CanceledCallable implements Runnable, Callable<Object> {
    public AsyncContext asyncContext;


    public CanceledCallable(AsyncContext asyncContext){
        this.asyncContext = asyncContext;
    }


    @Override
    public void run() {

    }

    @Override
    public Object call() throws Exception {
        return null;
    }
}

import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @Author xiaomayi
 * @Date 2023/6/28
 */

@RestController
public class OneLevelController {
    @Resource
    private XiaoMaYiService xiaoMaYiService;

    @Resource
    private OneLevelAsyncContext oneLevelAsyncContext;

    @PostMapping("/oneLevelAsyncContext")
    public Response<Object> testOneLevelAsyncContext(HttpServletRequest request, XiaoMaYi tab) {
        try {
            Object result = oneLevelAsyncContext.submitFuture(request, () -> xiaoMaYiService.queryAll(tab));
            return Response.success(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Response.error("请稍后重试");
    }

}

往期经典:

【干货】使用Canal 解决数据同步场景中的疑难杂症!!!

干货】常见库存设计方案-各种方案对比总有一个适合你

JYM来一篇消息队列-Kafaka的干货吧!!!

设计模式:

JYM 设计模式系列- 单例模式,适配器模式,让你的代码更优雅!!!

JYM 设计模式系列- 责任链模式,装饰模式,让你的代码更优雅!!!

JYM 设计模式系列- 策略模式,模板方法模式,让你的代码更优雅!!!

JYM 设计模式系列-工厂模式,让你的代码更优雅!!!

Spring相关:

Spring源码解析-老生常谈Bean ⽣命周期 ,但这个你值得看!!!

Spring 源码解析-JYM你值得拥有,从源码角度看bean的循环依赖!!!

Spring源码解析-Spring 事务

本文正在参加「金石计划」