V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
frank1256
V2EX  ›  Java

Servlet3.0 的异步,实战中有哪些场景呢

  •  
  •   frank1256 · 2022-02-22 15:23:21 +08:00 · 2195 次点击
    这是一个创建于 1006 天前的主题,其中的信息可能已经有所发展或是发生改变。

    恕 op 愚钝,虽然知道可以增加吞吐,但是具体会在哪些场景中使用到呢?

    第 1 条附言  ·  2022-02-23 10:05:58 +08:00

    public class AsyncServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { super.init(config); System.out.println("AsyncServlet init---> get Param"+config.getInitParameter("name")); }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    
        AsyncContext asyncContext = req.startAsync();
        PrintWriter writer = resp.getWriter();
        asyncContext.start(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"async run......");
                resp.setContentType("text/html");
                resp.setCharacterEncoding("UTF-8");
                //我的数据库io都在这里操作,io依然是同步的,但是我这样写,就是异步线程去处理这个请求了。
                //我想问,这样写法是可行的吗
                writer.println("async fuck you too");
                writer.flush();
                asyncContext.complete();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    
        System.out.println(Thread.currentThread().getName()+"stand run......");
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        writer.println("async fuck you");
        writer.flush();
    
    }
    

    }

    20 条回复    2022-04-07 17:15:27 +08:00
    koloonps
        1
    koloonps  
       2022-02-22 16:00:16 +08:00
    读取设备配置文件
    RedBeanIce
        2
    RedBeanIce  
       2022-02-22 16:04:53 +08:00
    没用过,over
    cheng6563
        3
    cheng6563  
       2022-02-22 16:05:12 +08:00
    异步可以单线程多任务->省线程->省开销->提高性能
    不要用异步调用链去处理复杂业务,会变得不幸
    实在要性能建议直接用 go
    BBCCBB
        4
    BBCCBB  
       2022-02-22 16:31:17 +08:00
    long polling 就可以用. 保持大量连接.

    比如各种配置中心里实现的配置秒生效.

    nacos & apollo 等等.
    BBCCBB
        5
    BBCCBB  
       2022-02-22 16:32:29 +08:00   ❤️ 1
    最重要的是不阻塞 servlet 容器的 http 线程池
    frank1256
        6
    frank1256  
    OP
       2022-02-22 17:29:09 +08:00
    @BBCCBB 我看到 nacos 中有用到,请教下大佬,既然可以提高吞吐,传统的业务为什么不用异步呢,现在的 crud 是不是也都可以做异步呢。
    BBCCBB
        7
    BBCCBB  
       2022-02-22 17:47:21 +08:00
    要提高吞吐, 光连接这里搞个异步没有用==, java 传统的业务基本都是阻塞 IO 模型的, 用异步比默认同步模型更复杂.

    除非 java 的 loom 项目搞出来. 实现协程. 或者 现阶段用 vertx 这种异步框架.
    byte10
        8
    byte10  
       2022-02-22 18:13:44 +08:00
    @frank1256 好家伙,就差你啦,快点去看我的视频啦 https://www.bilibili.com/video/BV1FS4y1o7QB 你看完就知道啥叫高吞吐的服务设计。代码地址 https://github.com/traceless/nio-server 。用 servlet 3.0 异步的方式,”几乎“可以无视 IO 时间。
    @RedBeanIce 没用过,可以看看我的教程,附有代码。用异步的方式,”几乎”可以无视 IO 时间,非常的吊。
    @koloonps 用于异步处理业务。

    记得点赞,小三连。谢谢哇
    bthulu
        9
    bthulu  
       2022-02-23 09:45:34 +08:00   ❤️ 1
    @frank1256 用了异步, 就必须处处异步, 有一处 IO 没用异步, 就会成为瓶颈. nacos 它才几个依赖, 都它自己的, 当然可控.
    业务代码业务过于复杂, 引入依赖过多, java 数据库操作也多是同步 IO, 你怎么用异步?
    frank1256
        10
    frank1256  
    OP
       2022-02-23 10:00:21 +08:00 via iPhone
    @bthulu 是异步请求,io 还是同步的
    frank1256
        11
    frank1256  
    OP
       2022-02-23 10:06:45 +08:00
    @bthulu append 一个代码,大佬可以看下,就是请求用异步线程去处理,数据库操作依然是同步的。这样不是提高了吞吐了吗?
    anonydmer
        12
    anonydmer  
       2022-02-23 10:34:53 +08:00   ❤️ 1
    1. 首先,楼主你要区分负责连接的线程和业务的线程。 异步的目的就是把这两个线程区分开。Servlet3.0 的异步是指容器管理的这部分线程变成异步。 带来的问题就是你必须自己去管理业务线程,比如用个线程池去处理业务
    2. 传统方案下你想想如果业务处理很慢,比如一个请求要 1 秒;即使 tomcat 容器线程池配了 200 个线程,你能达到的并发是多少
    3. 大白话说,实用的场景就是有大量并发的请求,你又不能拒绝服务的时候,这时候让你先连上,业务处理后面咱们可以慢慢处理,比如用个放到队列里,用有限的业务线程池慢慢处理
    4. CRUD 目前不做异步的原因是绝大部分的数据库驱动不支持
    5. 传统业务它的场景就不一样,并发并不是他的主要需求;但是互联网业务不一样

    @frank1256
    frank1256
        13
    frank1256  
    OP
       2022-02-23 10:44:32 +08:00
    @anonydmer 感谢解答,有点理解了
    NeoZephyr
        14
    NeoZephyr  
       2022-02-23 10:47:27 +08:00
    我觉得只适合那种处理时间比较长的请求,这样不阻塞 web 容器里面的线程。对提高并发度没有影响
    bthulu
        15
    bthulu  
       2022-02-23 12:43:23 +08:00
    @frank1256 start 要从线程池里捞一个线程来执行的, 异步线程池是有限的, 而你执行任务如果是同步的, 比如说你的 sleep3 秒钟, 那这 3 秒钟这个线程就傻等着也干不了别的.
    假如线程池里有 100 个线程, 这时候有 100 人调你这个接口, 那第 101 个人再调, 就得等 3 秒后才有空闲线程来处理.
    而如果异步 sleep(类似 c#里的 Task.Delay), 那这 3 秒等待时间它可以继续干别的活. 还是按上面的例子, 100 个人来调这个接口, sleep 的时候, 这 100 个线程又能用了, 就不会阻塞第 101 个人的访问了
    frank1256
        16
    frank1256  
    OP
       2022-02-23 13:07:56 +08:00
    @bthulu 感谢回答
    byte10
        17
    byte10  
       2022-02-28 18:35:18 +08:00
    @frank1256 少年,你应该看看我的视频呀,有说明的。或者你去下载这个代码 https://github.com/traceless/nio-server ,里面有 NioServerDemo.java , 它模拟了 servlet 3.0 异步的实现( ps:实际有差异),但是你看完这个 demo ,你就知道它是怎么回事了,几百行代码。

    测试方案,先启动里面有一个 nodejs 服务 7080 ,然后再启动 java 服务 8080 ,用浏览器随便访问一个 8080 地址即可,你就可以知道 全部真相了,非常叼。你一步一步的调试,就知道 nio 的异步写法是怎么回事了。现在回想下,其实 servlet3.0 就是解决 NIO 客户端实现的带来异步处理的问题。

    另外你的代码例子有问题,没人这样用的,你这样用,死掉了啦。asyncContext.start() 用的是 tomcat/undertow 的 work 线程,等于白搞了。。。楼上 @BBCCBB @bthulu 2 位讲的都是很 oK 的,但是你理解的有偏差。你要用 servlet3.0 异步,正确使用如下图:

    ```
    public void redisAsync( HttpServletRequest req) throws IOException {
    final AsyncContext ctx = req.startAsync();
    String key = "user:test:key";
    this.repository.monoFindById(key).subscribe(data -> {
    try {
    ctx.getResponse().getWriter().print(data);
    ctx.complete();
    } catch (IOException e) {
    e.printStackTrace();
    }
    });
    }
    ```

    @NeoZephyr 你也要看看视频啊啦 https://www.bilibili.com/video/BV1FS4y1o7QB ,nio 对吞吐量提升很大。传统的 IO 因为是会阻塞的且阻塞时间不一致,所以需要不定量的线程,而 NIO 几个线程就 OK 了,无视 IO 时间,看完你肯定会很惊讶,记得三连哦。
    NeoZephyr
        18
    NeoZephyr  
       2022-03-01 10:37:03 +08:00
    @byte10 怎么可能就几个线程。你有压测对比过吗,而且有不同的场景
    byte10
        19
    byte10  
       2022-03-01 13:17:01 +08:00
    @NeoZephyr (⊙o⊙)… 视频不是给出了 测试结果了吗?有多线程和 nio 服务的测试啊,也有代码,你按视频的方式,跑一把就知道啦,如果是 IO 密集型,NIO 几个线程就可以做到上万并发了。你直接跑一把代码有可以的 https://github.com/traceless/nio-server ,nodejs 单线程都去到上万了,java 使用 nio 的话 单线程也可以做到的。只是视频测试中设置的 tomcat 线程数是 20 个,不过你可以改成 2-4 个效果是一样的,只要跟 CPU 核心相同就好了。

    另外我也写了一个 NioServerDemo.java ,这个是可以帮组你了解为啥 NIO 那么强,几百行代码,你一步步调试 就可以了解全部真相,2 个线程都够了,一个 NIO 服务端线程,一个 NIO 客户端线程。

    至于你说不同的场景,无外乎就是调用网络 IO 这个场景,你有其他场景可以提一下。至于文件 IO 貌似 NIO 效果一般,linux 最新的 AIO 可以处理。
    ROMANLIU
        20
    ROMANLIU  
       2022-04-07 17:15:27 +08:00
    @bthulu
    start 的 JavaDoc 里是这样描述的:
    Causes the container to dispatch a thread, possibly from a managed thread pool, to run the specified Runnable. The container may propagate appropriate contextual information to the Runnable.

    其中的 managed thread pool 应该和 Http Worker TheadPool 不一样,是 Servlet Container 管理的默认的业务线程池,所有调用 start 的地方都会共享这个默认的线程池。
    使用自行的 ThreadPoolExecutor 应该是更好的选择。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3019 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 88ms · UTC 00:10 · PVG 08:10 · LAX 16:10 · JFK 19:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.