为了账号安全,请及时绑定邮箱和手机立即绑定

Java中的连接池和线程池设置

Java中的连接池和线程池设置

慕虎7371278 2023-10-13 16:35:44
使用 Hikari 池的 Spring 应用程序。现在,对于来自客户端的单个请求,我必须查询 10 个表(业务需要),然后将结果组合在一起。而查询每个表可能要花费50ms到200ms。为了加快响应时间,我FixedThreadPool在服务中创建一个来查询不同线程中的每个表(伪代码):class MyService{    final int THREAD_POOL_SIZE = 20;    final int CONNECTION_POOL_SIZE = 10;    final ExecutorService pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);    protected DataSource ds;    MyClass(){        Class.forName(getJdbcDriverName());        HikariConfig config = new HikariConfig();        config.setMaximumPoolSize(CONNECTION_POOL_SIZE);        ds = new HikariDataSource(config);    }    public Items doQuery(){        String[] tables=["a","b"......]; //10+ tables        Items result=new Items();        CompletionService<Items> executorService = new ExecutorCompletionService<Items>(pool);        for (String tb : tables) {            Callable<Item> c = () -> {                Items items = ds.getConnection().query(tb); ......                return Items;            };            executorService.submit(c);        }        for (String tb: tables) {            final Future<Items> future = executorService.take();            Items items = future.get();            result.addAll(items);        }    }}现在对于单个请求,平均响应时间可能是 500ms。但对于并发请求,平均响应时间会迅速增加,请求越多,响应时间就越长。我想知道如何设置正确的连接池大小和线程池大小以使应用程序有效工作?顺便说一句,数据库在云中使用 RDS,具有 4 个 cpu 16GB 内存,最大连接数为 2000,最大 IOPS 为 8000。
查看完整描述

3 回答

?
DIEA

TA贡献1820条经验 获得超2个赞

您可能需要考虑更多参数:
1. 数据库的最大并发请求参数。云提供商对不同层的并发请求有不同的限制,您可能需要检查您的。

2.当你说50-200ms时,虽然很难说,但是平均有8个50ms的请求和2个200ms的请求还是都差不多?为什么?您的 doQuery 可能会受到查询占用最大时间(即 200 毫秒)的限制,但花费 50 毫秒的线程将在其任务完成后被释放,使其可用于下一组请求。

3. 您期望获得的 QPS 是多少?

一些计算:如果单个请求需要 10 个线程,并且您配置了 100 个连接和 100 个并发查询限制,假设每个查询 200 毫秒,那么您一次只能处理 10 个请求。如果大多数查询需要 50 毫秒左右的话,可能会比 10 好一点(但我并不乐观)。

当然,如果您的任何查询花费> 200毫秒(网络延迟或其他任何东西),那么其中一些计算就会被折腾,在这种情况下,我建议您在连接端有一个断路器(如果允许您中止)超时后的查询)或在 API 端。

注意最大连接限制与最大并发查询限制不同。

建议:由于您需要 500 毫秒以下的响应,因此您还可以在池上设置约 100-150 毫秒的连接超时。最坏情况:150 毫秒连接超时 + 200 毫秒查询执行 + 100 毫秒应用程序处理 < 500 毫秒响应。作品。


查看完整回答
反对 回复 2023-10-13
?
慕的地10843

TA贡献1785条经验 获得超8个赞

您可以创建自定义线程执行器


public class CustomThreadPoolExecutor extends ThreadPoolExecutor {


    private CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize,

                                     long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {

        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);

    }


    /**

     * Returns a fixed thread pool where task threads take Diagnostic Context from the submitting thread.

     */


    public static ExecutorService newFixedThreadPool(int nThreads) {

        return new CustomThreadPoolExecutor(nThreads, nThreads,

                0L, TimeUnit.MILLISECONDS,

                new LinkedBlockingQueue<Runnable>());

    }

}

在配置中,您可以如下配置ExecutorService bean


@Bean

    public ExecutorService executeService() {

        return CustomThreadPoolExecutor.newFixedThreadPool(10);

    }

这是创建自定义线程池执行器的最佳实践


查看完整回答
反对 回复 2023-10-13
?
守着一只汪

TA贡献1872条经验 获得超3个赞

调整连接池大小的正确方法通常是将其保留为默认值。

如果您有 10,000 个前端用户,那么拥有 10,000 个连接池将是疯狂的。1000还是太恐怖了 即使是 100 个连接,也太过分了。您需要一个最多包含几十个连接的小型池,并且希望其余应用程序线程阻塞在池中等待连接。如果池经过适当调整,它将被设置为数据库能够同时处理的查询数量的限制——如上所述,该限制很少超过(CPU 核心 * 2)。

如果您知道每个请求将消耗 10 个线程,那么您想打破这个建议并采用更多线程 - 将其保持在小于 100 的数字可能会提供足够的容量。

我会像这样实现控制器:

使用 s让您的控制器/服务类中的查询异步CompletableFuture,并让连接池担心保持其线程繁忙。

所以控制器可能看起来像这样(我从其他一些代码中改编了它,这些代码不像这个例子那样工作,所以对这段代码持怀疑态度):

public class AppController { 


    @Autowired private DatabaseService databaseService; 


    public ResponseEntity<Thing> getThing() { 

        CompletableFuture<Foo> foo = CompletableFuture.runAsync(databaseService.getFoo());

        CompletableFuture<Bar> bar = CompletableFuture.runAsync(databaseService.getBar());

        CompletableFuture<Baz> baz = CompletableFuture.runAsync(databaseService.getBaz());


        // muck around with the completable future to return your data in the right way

        // this will be in there somewhere, followed by a .thenApply and .join

        CompletableFuture<Void> allFutures = CompletableFuture.allOf(foo, bar, baz);


        return new ResponseEntity<Thing>(mashUpDbData(cf.get()));

    }    

}

控制器将生成您允许ForkJoinPool使用的尽可能多的线程,它们将同时锤击所有数据库,并且连接池可以担心保持连接处于活动状态。

但我认为您在小负载下看到响应时间井喷的原因是,根据 JDBC 的设计,它会在等待数据从数据库返回时阻塞线程。

要阻止阻塞对响应时间产生如此大的影响,您可以尝试Spring Boot 反应式。这使用异步 io 和背压来匹配 IO 生产和消耗,基本上这意味着应用程序线程尽可能繁忙。这应该会阻止响应时间以线性方式增加的负载下的行为。

请注意,如果您确实采用反应式路径,jdbc 驱动程序仍然会阻塞,因此 spring 大力推动创建反应式数据库驱动程序。


查看完整回答
反对 回复 2023-10-13
  • 3 回答
  • 0 关注
  • 106 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信