架构设计内容分享(二十):阿里面试:页面调10 个上游接口,如何做高并发?

正确、最佳的答题姿势

今天给大家梳理一下正确、最佳的答题姿势。有了这个姿势,面试官自然爱到 “不能自已、口水直流”

页面调10 个上游接口,如何做高并发?

首先, 告诉面试官, 解决这个问题,需要  全链路、多层次、多维度进行 优化和改造

图片

全链路如何拆分后,进行优化和改造呢?

庖丁解牛,但是也不需要太复杂,可以把简单的把全链路拆分如下:

  • 接受下游请求环节

  • 上游请求分裂后入列环节

  • 执行上游调用环节

  • 上游结果聚合和响应环节

多层次如何拆分后,进行优化和改造呢?

核心的措施是:异步化, 但是,要分层进行异步化

可以简单的, 把用户的api调用解耦为三层,   如下图所示:

  • 应用层:编程模型的异步化

  • 框架层:IO线程的异步化

  • OS层:IO模型的异步化

解耦之后,再庖丁解牛,一层一层的进行异步化架构。

多维度优化和改造,主要有哪些呢?

其实有很多, 但是主要聚焦的点是:

业务线程,IO线程,如何进行优化和改造呢?

前面已经庖丁解牛,但是也不需要太复杂,可以把简单的把全链路拆分如下:

  • 接受下游请求环节

  • 上游请求分裂后入列环节

  • 执行上游调用环节

  • 上游结果聚合和响应环节

第二个环节涉及到了 业务线程池,第三个环节设计到了 IO线程池

都需要合理的设置线程池的大小、拒绝策略。 并且结合不同的框架和组件,结合自己的业务场景实现异步。

比如:

方案一:使用 CompletableFuture (自定义业务线程池)  + httpClient (池化同步io框架) + CountDownLatch闭锁(聚合结果)

方案二:CloseableHttpAsyncClient(池化异步io框架) + CountDownLatch闭锁(聚合结果)

方案三:自研Netty 异步IO框架+ CountDownLatch闭锁(聚合结果)

网络传输维度,如何进行优化和改造呢?

核心就是两点:

  • 连接复用

  • 多路复用

首先看 连接复用。也就是 短链接,变成长连接

http1.0协议头里可以设置Connection:Keep-Alive。在header里设置Keep-Alive可以在一定时间内复用连接,具体复用时间的长短可以由服务器控制,一般在15s左右。

到http1.1之后Connection的默认值就是Keep-Alive,如果要关闭连接复用需要显式的设置Connection:Close。

图片

多路复用代替原来的序列和阻塞机制。所有就是请求的都是通过一个 TCP 连接并发完成。

因为在多路复用之前所有的传输是基于基础文本的,在多路复用中是基于二进制数据帧的传输、消息、流,所以可以做到乱序的传输。

多路复用对同一域名下所有请求都是基于流,所以不存在同域并行的阻塞。

多路复用复用场景,多次请求如下图:

图片

http2.0

提示:由于http1.2 目前普及度不够,一般还是考虑 http1.1连接复用

方案一实操

使用 CompletableFuture (自定义业务线程池)  + httpClient (池化同步io框架) + CountDownLatch闭锁(聚合结果)

static final HttpClient httpClient = HttpClientBuilder.create().build();

@Benchmark
@Test
public void HttpClient() {
    List<String> apis = Arrays.asList(
        "http://192.168.56.121/echo?i=1",
        "http://192.168.56.121/echo?i=2",
        "http://192.168.56.121/echo?i=3"
    );

    CountDownLatch latch = new CountDownLatch(apis.size());
    Map<String, String> results = new ConcurrentHashMap<>();
    for (String api : apis) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            HttpResponse response = null;
            try {
                response = httpClient.execute(new HttpGet(api));
            } catch (IOException e) {
                return null;
            }

            if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                try {
                    return EntityUtils.toString(response.getEntity());
                } catch (Exception e) {
                    log.error("error", e);
                    throw new RuntimeException(e);
                }
            }
            return null;
        });
        future.whenComplete((result, throwable) -> {
            latch.countDown();
            results.put(api, result);
        }
                           );
    }

    try {
        latch.await(10000000, TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        log.error("error", e);

        //            throw new RuntimeException(e);
    }

    //        System.out.println(results.toString());
}

方案一性能基线测试

这里使用linux + jmh 框架,完成性能基线测试

达到 5000qps

图片

方案二实操

方案二:CloseableHttpAsyncClient(池化异步io框架) + CountDownLatch闭锁(聚合结果)

这里去掉了业务线程池。提升了性能

@Benchmark
@Test
public void HttpAsyncClient() {

    List<String> urls = Arrays.asList(
        "http://192.168.56.121/echo?i=1",
        "http://192.168.56.121/echo?i=2",
        "http://192.168.56.121/echo?i=3"
    );

    CountDownLatch latch = new CountDownLatch(urls.size());
    Map<String, String> results = new ConcurrentHashMap<>();

    for (int i = 0; i < urls.size(); i++) {
        final String url = urls.get(i);
        Future<HttpResponse> f0 = asyncClient.execute(new HttpGet(url), new FutureCallback<HttpResponse>() {
            public void completed(HttpResponse response) {

                latch.countDown();

                if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                    try {
                        String result = EntityUtils.toString(response.getEntity());
                        results.put(url, result);
                    } catch (IOException e) {
                        log.error("error", e);
                        //                            throw new RuntimeException(e);
                    }
                }

            }

            public void failed(Exception ex) {
                latch.countDown();
                log.error("error", ex);
                //                    ex.printStackTrace();
            }

            public void cancelled() {
                latch.countDown();
            }
        });
    }


    try {
        latch.await(10000000, TimeUnit.MILLISECONDS);
    } catch (
        InterruptedException e) {
        throw new RuntimeException(e);
    }

    //        System.out.println("results = " + results);
}

方案二性能基线测试

这里使用linux + jmh 框架,完成性能基线测试

达到 4300qps

图片

理论上性能会更优,但是,看上去性能更差。

why?

方案三实操

方案三:自研Netty 异步IO框架+ CountDownLatch闭锁(聚合结果)

在网上找了一个 生产环境上的 qps达到 9000的 自研Netty 异步IO框架

完成了实验

@Benchmark
@Test
public void nettyGet() {

    //        setup();
    List<String> urls = Arrays.asList(
        "/echo?i=1",
        "/echo?i=2",
        "/echo?i=3"
    );

    CountDownLatch latch = new CountDownLatch(urls.size());
    Map<Integer, Object> results = new ConcurrentHashMap<>();


    ReqOptions options = new ReqOptions(TypeReference.from(String.class));
    for (int i = 0; i < urls.size(); i++) {

        int finalI = i;
        longConnHttpClient.getAsync("/echo?i=" + i, options, response -> {

            Object content = response.content();
            results.put(finalI, content);

            latch.countDown();
        }, e -> {
            e.printStackTrace();
            latch.countDown();
        });

    }

    try {
        latch.await(10000000, TimeUnit.MILLISECONDS);
    } catch (
        InterruptedException e) {
        throw new RuntimeException(e);
    }
    //        System.out.println("results = " + results);

}
方案三性能基线测试

这里使用linux + jmh 框架,完成性能基线测试

第一次个版本,性能就 300 ops

图片

进行优化之后,第二次验证, 也就4300qps, 但是,看上去性能也很差。

不用jmh进行基线测试, 达到 6000多qps,这下明白了。

图片

为啥 技术越高明,性能反而不高呢?

还有有原因的,当然目前来看,具体的原因也是猜测,不能大放厥词。

 

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值