关于数据库连接池:连接池-HikariPool-一-基础框架及初始化过程

后面做了n多筹备,包含同步队列、阻塞队列、线程池、周期性线程池等等,明天终于能够开始深入研究连接池了,从HikariPool开始。 连接池存在的起因和线程池大略相似,因为数据库连贯的获取是有开销的,频繁获取、敞开数据库连贯会带来不必要的开销,影响零碎性能。所以就有了数据库连接池。 数据库连接池技术提前创立好数据库连贯,并对数据库连贯进行池化治理:连贯创立好之后交给连接池,利用须要连贯的时候间接从连接池获取、而不须要创立连贯,应用完之后归还给连接池、而不是敞开连贯。从而缩小频繁创立、敞开数据库连贯锁带来的开销,极大进步零碎性能。 明天从HikariPool开始,从源码角度学习理解数据库连接池的实现原理。HikariPool是目前公认性能最好的数据库连接池,同时也是SpringBoot2.0当前的默认连接池,置信今后会有越来越多的公司和我的项目应用HikariPool连接池。 对于数据库连接池的根本认知先对数据库连接池的根本工作原理做个理解,不论是HikariPool、还是druid,所有的数据库连接池应该都是依照这个基本原理工作和实现的,带着这个思路去学习数据库连接池,防止盲人摸象。 数据库连接池肯定会蕴含以下根本逻辑: 创立连贯并池化:初始化的时候创立、或者是在利用获取连贯的过程中创立,连贯创立好之后放在连接池(内存中的容器,比方List)中保留。获取数据库连贯:接管了获取数据库连贯的办法,从连接池中获取、而不是创立连贯。敞开数据库连贯:接管了敞开数据库连贯的办法,将连贯偿还到连接池、而不是真正的敞开数据库连贯。连接池保护:连接池容量、连贯超时清理等工作。带着这个思路钻研HikariPool的源码,会有事倍功半的效用。 意识HikariPool的构造包含以下几个局部: ConcurrentBag & PoolEntryaddConnectionExecutorhouseKeeperTaskHikariProxyConnectioncloseConnectionExecutorConcurrentBag直译就是“连贯口袋”,就是放数据库连贯的口袋,也就是连接池。 ConcurrentBag有3个保留数据库连贯的中央(池): threadList:是一个ThreadLocal变量,保留以后线程持有的数据库连贯。sharedList:是一个CopyOnWriteArrayList,线程平安的arrayList,数据库连贯创立后首先进入sharedList。handoffQueue:是一个SynchronousQueue,数据库连贯创立、进入sharedList后,也会进入handoffQueue期待连贯申请。除此之外,ConcurrentBag还有一个比拟重要的属性waiters,记录向连接池获取数据库连贯而不得、期待的线程数。 PoolEntry连接池中存储的对象不是Connection,也不是Connection的代理对象,而是PoolEntry。 PoolEntry持有数据库连贯对象Connection,Connection在实例化PoolEntry前创立。PoolEntry创立的过程中会同时创立两个ScheduledFuture工作:endOfLife和keepalive。 endOfLife提交MaxLifetimeTask定时工作,在PoolEntry创立后的maxLifetime(参数指定)执行。MaxLifetimeTask会敞开以后连贯、同时依据须要创立新的连贯退出到连接池。 keepalive提交KeepaliveTask周期性工作,在PoolEntry创立后依照keepalive(参数设定)周期性执行,在以后连贯闲暇的状况下查看连贯是否可用(应用参数指定的ConnectionTestQuery进行测试),如果连贯不可用则敞开并从新创立连贯。 addConnectionExecutor增加数据库连贯的工作管理器。负责数据库连贯的创立以及退出到连接池中(sharedList和handoffQueue)。 留神addConnectionExecutor自身是一个线程池ThreadPoolExecutor,线程池容量为最大数据库连接数,HikariPool初始化的过程中会以多线程(corePoolSize=CPU内核数量)的形式疾速实现连贯创立和池化,之后失常状况下会以单线程(corePoolSize=1)的形式创立数据库连贯。 houseKeeperTask是一个周期性线程池ScheduledExecutorService,初始化的时候创立,定时(housekeepingPeriodMs)执行,清理掉(敞开)连接池中多余的(大于最小闲暇数)闲暇连贯,如果连接池中的连接数没有达到参数设置的数量(最大连接数、或者闲暇连贯没有达到最小闲暇连接数)则创立连贯。 HikariProxyConnectionHikariProxyConnection是数据库连贯Connection的扩大类,应用层通过getConnection办法获取到的数据库连贯其实不是不是数据库连贯Connection、而是这个扩大类HikariProxyConnection。 应用扩大类HikariProxyConnection、而不是原生的数据库连贯Connection的一个最直观的理由是,对于数据库连接池来说,连贯的敞开办法close不是要敞开连贯、而是要把连贯交还给连接池。应用扩大类(或者代理类)就能够很容易的重写其close办法实现目标,而如果间接应用原生Connection的话,就没方法管制了。 closeConnectionExecutor数据库连接池须要敞开的时候,通过closeConnectionExecutor线程池提交,敞开连贯后closeConnectionExecutor还负责调用fillPool(),依据须要填充连接池。 好了,HikariPool的根底构造理解完了,接下来要进入源码剖析了,次要包含: HikariPool的初始化获取数据库连贯 - getConnection办法敞开数据库连贯 - close办法HikariPool的初始化要开始剖析源码了。 HikariPool是实例化的时候在构造方法中实现初始化。代码尽管不是很多,然而内容很多,所以还是分段、一步一步剖析。 public HikariPool(final HikariConfig config) { super(config); this.connectionBag = new ConcurrentBag<>(this); this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK; this.houseKeepingExecutorService = initializeHouseKeepingExecutorService(); checkFailFast();首先调用super也就是PoolBase的构造方法,次要设置配置的相干参数,初始化DataSource(initializeDataSource,获取到数据库连贯的url、username、password等重要参数,创立DataSource)。 之后创立ConcurrentBag、实现ConcurrentBag的初始化(通过构造方法):创立handoffQueue、sharedList以及threadList。 接下来初始化houseKeepingExecutorService,也就是创立一个ScheduledThreadPoolExecutor,筹备接管houseKeep工作。 而后调用checkFailFast(),checkFailFast()的作用是在初始化连接池之前首先做一次疾速尝试:创立一个PoolEntry(通过createPoolEntry办法,稍后剖析源码),如果创立胜利则将其退出连接池后,返回,否则如果失败(数据库连贯创立失败)则一直尝试直到消耗完设置的初始化工夫initializationTimeout。 接着看初始化代码: if (config.getMetricsTrackerFactory() != null) { setMetricsTrackerFactory(config.getMetricsTrackerFactory()); } else { setMetricRegistry(config.getMetricRegistry()); } setHealthCheckRegistry(config.getHealthCheckRegistry()); handleMBeans(this, true); ThreadFactory threadFactory = config.getThreadFactory(); final int maxPoolSize = config.getMaximumPoolSize(); LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(maxPoolSize); this.addConnectionQueueReadOnlyView = unmodifiableCollection(addConnectionQueue); this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy()); this.closeConnectionExecutor = createThreadPoolExecutor(maxPoolSize, poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy()); this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService); this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);以上代码次要实现以下几件事件:MetricsTrackerFactory次要用来创立数据库连贯的统计分析工作。 ...

April 9, 2023 · 2 min · jiezi

关于数据库连接池:数据库连接池到底应该设多大

将文章里的观点提炼下: 在设置数据库连接池大小时须要激进一点,不要设置过大在影响数据库的性能瓶颈中,先不思考磁盘、网络而只思考CPU的状况下,线程过多会让CPU在线程上下文切换花掉不少工夫在保持下面两个准则的状况下联合本身零碎来设置连接池最终大小原文连贯:数据库连接池到底应该设多大? | 前言 本文内容95%译自这篇文章:https://github.com/brettwoold...我在钻研HikariCP(一个数据库连接池)时无意间在HikariCP的Github wiki上看到了一篇文章(即后面给出的链接),这篇文章无力地打消了我始终以来的疑虑,看完之后感觉神清气爽。故在此做译文分享。| 注释数据库连接池的配置是开发者们经常搞出坑的中央,在配置数据库连接池时,有几个能够说是和直觉南辕北辙的准则须要明确。 </> 1万并发用户拜访设想你有一个网站,压力尽管还没到Facebook那个级别,但也有个1万高低的并发拜访——也就是说差不多2万左右的TPS。那么这个网站的数据库连接池应该设置成多大呢?后果可能会让你诧异,因为这个问题的正确问法是: “这个网站的数据库连接池应该设置成多小呢?” 上面这个视频是Oracle Real World Performance Group公布的,请先看完: http://www.dailymotion.com/vi... (因为这视频是英文讲解且没有字幕,我替大家做一下简略的概括:)视频中对Oracle数据库进行压力测试,9600并发线程进行数据库操作,每两次拜访数据库的操作之间sleep 550ms,一开始设置的中间件线程池大小为2048: 初始的配置 压测跑起来之后是这个样子的: 2048连贯时的性能数据 每个申请要在连接池队列里期待33ms,取得连贯后执行SQL须要77ms。 此时数据库的期待事件是这个熊样的: 各种buffer busy waits 各种buffer busy waits,数据库CPU在95%左右(这张图里没截到CPU) 接下来,把中间件连接池减到1024(并发什么的都不变),性能数据变成了这样: 连接池降到1024后 获取链接期待时长没怎么变,然而执行SQL的耗时缩小了。 上面这张图,上半局部是wait,下半局部是吞吐量: wait和吞吐量 能看到,中间件连接池从2048减半之后,吐吞量没变,但wait事件缩小了一半。 接下来,把数据库连接池减到96,并发线程数依然是9600不变。 96个连贯时的性能数据 队列均匀期待1ms,执行SQL均匀耗时2ms。 wait事件简直没了,吞吐量回升。 没有调整任何其余货色,仅仅只是放大了中间件层的数据库连接池,就把申请响应工夫从100ms左右缩短到了3ms。 </> But why?为什么nginx只用4个线程施展出的性能就大大超越了100个过程的Apache HTTPD?回忆一下计算机科学的基础知识,答案其实是很显著的。 即便是单核CPU的计算机也能“同时”运行数百个线程。但咱们都[应该]晓得这只不过是操作系统用工夫分片玩的一个小把戏。一颗CPU外围同一时刻只能执行一个线程,而后操作系统切换上下文,外围开始执行另一个线程的代码,以此类推。给定一颗CPU外围,其程序执行A和B永远比通过工夫分片“同时”执行A和B要快,这是一条计算机科学的根本法令。一旦线程的数量超过了CPU外围的数量,再减少线程数零碎就只会更慢,而不是更快。 这 简直 就是真谛了…… </> 无限的资源下面的说法只能说是靠近真谛,但还并没有这么简略,有一些其余的因素须要退出。当咱们寻找数据库的性能瓶颈时,总是能够将其归为三类:CPU、磁盘、网络。把内存加进来也没有错,但比起磁盘和网络,内存的带宽要高出好几个数量级,所以就先不加了。 如果咱们忽视磁盘和网络,那么论断就非常简单。在一个8核的服务器上,设定连贯/线程数为8可能提供最优的性能,再减少连接数就会因上下文切换的损耗导致性能降落。数据库通常把数据存储在磁盘上,磁盘又通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的。读/写头同一时刻只能呈现在一个中央,而后它必须“寻址”到另外一个地位来执行另一次读写操作。所以就有了寻址的耗时,此外还有旋回耗时,读写头须要期待碟片上的指标数据“旋转到位”能力进行操作。应用缓存当然是可能晋升性能的,但上述原理依然成立。 在这一时间段(即"I/O期待")内,线程是在“阻塞”着期待磁盘,此时操作系统能够将那个闲暇的CPU外围用于服务其余线程。所以,因为线程总是在I/O上阻塞,咱们能够让线程/连接数比CPU外围多一些,这样可能在同样的工夫内实现更多的工作。 那么应该多多少呢?这要取决于磁盘。较新型的SSD不须要寻址,也没有旋转的碟片。可别想当然地认为“SSD速度更快,所以咱们应该减少线程数”,恰恰相反,无需寻址和没有旋回耗时意味着更少的阻塞,所以更少的线程[更靠近于CPU外围数]会施展出更高的性能。只有当阻塞发明了更多的执行机会时,更多的线程数能力施展出更好的性能。 网络和磁盘相似。通过以太网接口读写数据时也会造成阻塞,10G带宽会比1G带宽的阻塞少一些,1G带宽又会比100M带宽的阻塞少一些。不过网络通常是放在第三位思考的,有些人会在性能计算中疏忽它们。 上图是PostgreSQL的benchmark数据,能够看到TPS增长率从50个连接数开始变缓。在下面Oracle的视频中,他们把连接数从2048降到了96,实际上96都太高了,除非服务器有16或32颗外围。 </> 计算公式上面的公式是由PostgreSQL提供的,不过咱们认为能够宽泛地利用于大多数数据库产品。你应该模仿预期的访问量,并从这一公式开始测试你的利用,寻找最合适的连贯数值。 连接数 = ((外围数 * 2) + 无效磁盘数) ...

December 14, 2021 · 1 min · jiezi