公用池化套件 Commons Pool 2
我們先研究一下Java中通用的物件池化套件Commons Pool 2,以了解一般的物件池結構。
根據我們的業務需求,使用這套 API 能夠輕鬆實現物件的池化管理。
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.11.1</version> </dependency>
GenericObjectPool 是物件池的核心類,透過傳入一個物件池的配置和一個物件的工廠,即可快速建立物件池。
public GenericObjectPool( final PooledObjectFactory<T> factory, final GenericObjectPoolConfig<T> config)
案例
Redis 的常用客戶端 Jedis,就是使用 Commons Pool 管理連接池的,可以說是一個最佳實踐。下圖是 Jedis 使用工廠創建物件的主要程式碼區塊。
物件工廠類別最主要的方法就是makeObject,它的傳回值是 PooledObject 類型,可以將物件使用 new DefaultPooledObject(obj) 進行簡單包裝回傳。
redis.clients.jedis.JedisFactory,使用工廠建立物件。
@Override public PooledObject<Jedis> makeObject() throws Exception { Jedis jedis = null; try { jedis = new Jedis(jedisSocketFactory, clientConfig); //主要的耗时操作 jedis.connect(); //返回包装对象 return new DefaultPooledObject<>(jedis); } catch (JedisException je) { if (jedis != null) { try { jedis.quit(); } catch (RuntimeException e) { logger.warn("Error while QUIT", e); } try { jedis.close(); } catch (RuntimeException e) { logger.warn("Error while close", e); } } throw je; } }
我們再來介紹一下對象的生成過程,如下圖,對像在進行獲取時,將首先嘗試從對像池裡拿出一個,如果對像池中沒有空閒的對象,就使用工廠類提供的方法,產生一個新的。
public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception { //此处省略若干行 while (p == null) { create = false; //首先尝试从池子中获取。 p = idleObjects.pollFirst(); // 池子里获取不到,才调用工厂内生成新实例 if (p == null) { p = create(); if (p != null) { create = true; } } //此处省略若干行 } //此处省略若干行 }
那物件是存在什麼地方的呢?這個儲存的職責,就是由一個叫作LinkedBlockingDeque 的結構來承擔的,它是一個雙向的佇列。
接下來看一下 GenericObjectPoolConfig 的主要屬性:
// GenericObjectPoolConfig本身的属性 private int maxTotal = DEFAULT_MAX_TOTAL; private int maxIdle = DEFAULT_MAX_IDLE; private int minIdle = DEFAULT_MIN_IDLE; // 其父类BaseObjectPoolConfig的属性 private boolean lifo = DEFAULT_LIFO; private boolean fairness = DEFAULT_FAIRNESS; private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS; private long minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS; private long evictorShutdownTimeoutMillis = DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS; private long softMinEvictableIdleTimeMillis = DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS; private int numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN; private EvictionPolicy<T> evictionPolicy = null; // Only 2.6.0 applications set this private String evictionPolicyClassName = DEFAULT_EVICTION_POLICY_CLASS_NAME; private boolean testOnCreate = DEFAULT_TEST_ON_CREATE; private boolean testOnBorrow = DEFAULT_TEST_ON_BORROW; private boolean testOnReturn = DEFAULT_TEST_ON_RETURN; private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE; private long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; private boolean blockWhenExhausted = DEFAULT_BLOCK_WHEN_EXHAUSTED;
參數很多,要想了解參數的意義,我們先來看看一個池化物件在整個池子中的生命週期。
如下圖所示,池子的操作主要有兩個:一個是業務線程,一個是偵測線程。
物件池在進行初始化時,要指定三個主要的參數:
maxTotal 物件池中管理的物件上限
maxIdle 最大空閒數
#minIdle 最小空閒數
其中maxTotal 和業務執行緒有關,當業務執行緒想要取得物件時,會先偵測是否有空閒的物件。
如果有,則傳回一個;否則進入建立邏輯。如果池中已經達到最大值,建立物件將會失敗並傳回空物件。
物件在取得的時候,有一個非常重要的參數,那就是最大等待時間(maxWaitMillis),這個參數對應用方的效能影響是比較大的。此參數預設為 -1,表示永不逾時,直到有物件空閒。
如下圖,如果物件建立非常緩慢或使用非常繁忙,業務執行緒會持續阻塞 (blockWhenExhausted 預設為 true),進而導致正常服務也無法運作。
面試題
在面試時通常會被問到:"你會將超時參數設定為多久?" 我的做法是將最大等待時間設為介面能夠忍受的最大延遲。
例如,一個正常服務回應時間 10ms 左右,達到 1 秒就會感覺到卡頓,那麼這個參數設定成 500~1000ms 都是可以的。
逾時之後,會拋出 NoSuchElementException 異常,請求會快速失敗,不會影響其他業務線程,這種 Fail Fast 的思想,在互聯網應用非常廣泛。
帶有 evcit 字樣的參數,主要是處理物件逐出的。池化物件的建立和銷毀操作不僅耗費時間,在運作時還會佔用系統資源。
例如,連接池會佔用多條連接,執行緒池會增加調度開銷等。當業務遇到突發流量時,它會從池子中申請超出正常情況所需的物件資源。等這些物件不再被使用,我們就需要把它清理掉。
超出minEvictableIdleTimeMillis 參數指定值的對象,就會被強制回收掉,這個值預設是30 分鐘;softMinEvictableIdleTimeMillis 參數類似,但它只有在當前物件數量大於minIdle 的時候才會執行移除,所以前者的動作要更暴力一些。
還有 4 個 test 參數:testOnCreate、testOnBorrow、testOnReturn、testWhileIdle,分別指定了在建立、取得、歸還、空閒偵測的時候,是否對池化物件進行有效性檢測。
開啟這些偵測,能保證資源的有效性,但它會耗費效能,所以預設為 false。
在生產環境上,建議只將 testWhileIdle 設定為 true,並透過調整空閒偵測時間間隔(timeBetweenEvictionRunsMillis),例如 1 分鐘,來確保資源的可用性,同時也保證效率。
JMH 測試
使用連接池和不使用連接池,它們之間的效能差距到底有多大呢?
下面是一個簡單的 JMH 測試範例(請參閱倉庫),進行一個簡單的 set 操作,為 redis 的 key 設定一個隨機值。
@Fork(2) @State(Scope.Benchmark) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @BenchmarkMode(Mode.Throughput) public class JedisPoolVSJedisBenchmark { JedisPool pool = new JedisPool("localhost", 6379);
@Benchmarkpublicvoid testPool() { Jedis jedis = pool.getResource(); jedis.set("a", UUID.randomUUID().toString()); jedis.close(); } @Benchmarkpublicvoid testJedis() { Jedis jedis = new Jedis("localhost",6379); jedis.set("a", UUID.randomUUID().toString()); jedis.close(); }//此处省略若干行}
将测试结果使用 meta-chart 作图,展示结果如下图所示,可以看到使用了连接池的方式,它的吞吐量是未使用连接池方式的 5 倍!
数据库连接池 HikariCP
HikariCP 源于日语“光る”,光的意思,寓意软件工作速度和光速一样快,它是 SpringBoot 中默认的数据库连接池。
数据库是我们工作中经常使用到的组件,针对数据库设计的客户端连接池是非常多的,它的设计原理与我们在本文开头提到的基本一致,可以有效地减少数据库连接创建、销毁的资源消耗。
同是连接池,它们的性能也是有差别的,下图是 HikariCP 官方的一张测试图,可以看到它优异的性能,官方的 JMH 测试代码见 Github。
一般面试题是这么问的:HikariCP 为什么快呢?
主要有三个方面:
它使用 FastList 替代 ArrayList,通过初始化的默认值,减少了越界检查的操作
优化并精简了字节码,通过使用 Javassist,减少了动态代理的性能损耗,比如使用 invokestatic 指令代替 invokevirtual 指令
实现了无锁的 ConcurrentBag,减少了并发场景下的锁竞争
HikariCP 对性能的一些优化操作,是非常值得我们借鉴的,在之后的博客中,我们将详细分析几个优化场景。
数据库连接池同样面临一个最大值(maximumPoolSize)和最小值(minimumIdle)的问题。一个在面试中经常被问到的问题是:你通常会将连接池设置为多大?
许多学生误以为,连接池设置得越大越好,有些学生甚至将该值设置为1000以上。
根据经验,数据库连接,只需要 20~50 个就够用了。具体的大小,要根据业务属性进行调整,但大得离谱肯定是不合适的。
HikariCP 官方是不推荐设置 minimumIdle 这个值的,它将被默认设置成和 maximumPoolSize 一样的大小。如果你的数据库Server端的连接资源空闲很多,你可以考虑禁用连接池的动态调整功能。
另外,根据数据库查询和事务类型,一个应用中是可以配置多个数据库连接池的,这个优化技巧很少有人知道,在此简要描述一下。
业务类型通常有两种:一种需要快速的响应时间,把数据尽快返回给用户;另外一种是可以在后台慢慢执行,耗时比较长,对时效性要求不高。
如果这两种业务类型,共用一个数据库连接池,就容易发生资源争抢,进而影响接口响应速度。
虽然微服务能够解决这种情况,但大多数服务是没有这种条件的,这时就可以对连接池进行拆分。
如图,在同一个业务中,根据业务的属性,我们分了两个连接池,就是来处理这种情况的。
HikariCP 还提到了另外一个知识点,在 JDBC4 的协议中,通过 Connection.isValid() 就可以检测连接的有效性。
这样,我们就不用设置一大堆的 test 参数了,HikariCP 也没有提供这样的参数。
结果缓存池
到了这里你可能会发现池(Pool)与缓存(Cache)有许多相似之处。
它们有一个共同点,即将经过加工的对象存储在相对高速的区域中。我习惯性将缓存看作是数据对象,而把池中的对象看作是执行对象。缓存中的数据有一个命中率问题,而池中的对象一般都是对等的。
考虑下面一个场景,jsp 提供了网页的动态功能,它可以在执行后,编译成 class 文件,加快执行速度;再或者,一些媒体平台,会将热门文章,定时转化成静态的 html 页面,仅靠 nginx 的负载均衡即可应对高并发请求(动静分离)。
这些时候,你很难说清楚,这是针对缓存的优化,还是针对对象进行了池化,它们在本质上只是保存了某个执行步骤的结果,使得下次访问时不需要从头再来。
我通常把这种技术叫作结果缓存池(Result Cache Pool),属于多种优化手段的综合。
小结
下面我来简单总结一下本文的内容重点:我们从 Java 中最通用的公用池化包 Commons Pool 2 说起,介绍了它的一些实现细节,并对一些重要参数的应用做了讲解。
Jedis 就是在 Commons Pool 2 的基础上封装的,通过 JMH 测试,我们发现对象池化之后,有了接近 5 倍的性能提升。
接下来介绍了数据库连接池中速度很快的 HikariCP ,它在池化技术之上,又通过编码技巧进行了进一步的性能提升,HikariCP 是我重点研究的类库之一,我也建议你加入自己的任务清单中。
总体来说,当你遇到下面的场景,就可以考虑使用池化来增加系统性能:
对象的创建或者销毁,需要耗费较多的系统资源
对象的创建或者销毁,耗时长,需要繁杂的操作和较长时间的等待
对象创建后,通过一些状态重置,可被反复使用
将对象池化之后,只是开启了第一步优化。要想达到最优性能,就不得不调整池的一些关键参数,合理的池大小加上合理的超时时间,就可以让池发挥更大的价值。和缓存的命中率类似,对池的监控也是非常重要的。
如下图,可以看到数据库连接池连接数长时间保持在高位不释放,同时等待的线程数急剧增加,这就能帮我们快速定位到数据库的事务问题。
平常的编码中,有很多类似的场景。比如 Http 连接池,Okhttp 和 Httpclient 就都提供了连接池的概念,你可以类比着去分析一下,关注点也是在连接大小和超时时间上。
在底层的中间件,比如 RPC,也通常使用连接池技术加速资源获取,比如 Dubbo 连接池、 Feign 切换成 httppclient 的实现等技术。
你会发现,在不同资源层面的池化设计也是类似的。在后续的文章中,我们将介绍线程池的功能,例如利用队列对任务进行二层缓冲、提供多种拒绝策略等。
线程池的这些特性,你同样可以借鉴到连接池技术中,用来缓解请求溢出,创建一些溢出策略。
现实情况中,我们也会这么做。那么具体怎么做?有哪些做法?这部分内容就留给大家思考了。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ykq</groupId> <artifactId>qy151-springboot-vue</artifactId> <version>0.0.1-SNAPSHOT</version> <name>qy151-springboot-vue</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>com.spring4all</groupId> <artifactId>swagger-spring-boot-starter</artifactId> <version>1.9.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
以上是SpringBoot HikariCP連接池怎麼創建的詳細內容。更多資訊請關注PHP中文網其他相關文章!

javaispopularforcross-platformdesktopapplicationsduetoits“ writeonce,runany where”哲學。 1)itusesbytiesebyTecodeThatrunsonAnyJvm-備用Platform.2)librarieslikeslikeslikeswingingandjavafxhelpcreatenative-lookingenative-lookinguisis.3)

在Java中編寫平台特定代碼的原因包括訪問特定操作系統功能、與特定硬件交互和優化性能。 1)使用JNA或JNI訪問Windows註冊表;2)通過JNI與Linux特定硬件驅動程序交互;3)通過JNI使用Metal優化macOS上的遊戲性能。儘管如此,編寫平台特定代碼會影響代碼的可移植性、增加複雜性、可能帶來性能開銷和安全風險。

Java將通過雲原生應用、多平台部署和跨語言互操作進一步提昇平台獨立性。 1)雲原生應用將使用GraalVM和Quarkus提升啟動速度。 2)Java將擴展到嵌入式設備、移動設備和量子計算機。 3)通過GraalVM,Java將與Python、JavaScript等語言無縫集成,增強跨語言互操作性。

Java的強類型系統通過類型安全、統一的類型轉換和多態性確保了平台獨立性。 1)類型安全在編譯時進行類型檢查,避免運行時錯誤;2)統一的類型轉換規則在所有平台上一致;3)多態性和接口機制使代碼在不同平台上行為一致。

JNI會破壞Java的平台獨立性。 1)JNI需要特定平台的本地庫,2)本地代碼需在目標平台編譯和鏈接,3)不同版本的操作系統或JVM可能需要不同的本地庫版本,4)本地代碼可能引入安全漏洞或導致程序崩潰。

新興技術對Java的平台獨立性既有威脅也有增強。 1)雲計算和容器化技術如Docker增強了Java的平台獨立性,但需要優化以適應不同雲環境。 2)WebAssembly通過GraalVM編譯Java代碼,擴展了其平台獨立性,但需與其他語言競爭性能。

不同JVM實現都能提供平台獨立性,但表現略有不同。 1.OracleHotSpot和OpenJDKJVM在平台獨立性上表現相似,但OpenJDK可能需額外配置。 2.IBMJ9JVM在特定操作系統上表現優化。 3.GraalVM支持多語言,需額外配置。 4.AzulZingJVM需特定平台調整。

平台獨立性通過在多種操作系統上運行同一套代碼,降低開發成本和縮短開發時間。具體表現為:1.減少開發時間,只需維護一套代碼;2.降低維護成本,統一測試流程;3.快速迭代和團隊協作,簡化部署過程。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Dreamweaver Mac版
視覺化網頁開發工具

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

Dreamweaver CS6
視覺化網頁開發工具