记一次使用GenericObjectPool的体验,避免自己重复造轮子

概述

相信大家在日常的开发中,或多或少都接触过对象池连接池等池化的内容,可能还自己手动实现或维护过这种 “池子”。

我也曾干过这种事情。当时要连接一个第三方平台进行接口调用,为了减少耗时和提升稳定性,手动实现了一个连接池。

当时对接完成后,刚开始效果并不理想。从请求到收到响应,整个过程耗时超过了 1 秒,且 RT (响应时间) 有抖动,有时会超过 1.5 秒。

排查后发现,建立连接这个过程的耗时超过了 200 毫秒,并且会有抖动。造成这个现象的原因是,建立连接的过程比较复杂,链路建立成功后,会升级协议、服务端客户端校验、交换秘钥、....。

为了提升效率,避免连接过程的耗时和耗时抖动,将连接过程提前,实现了一个连接池。

这个连接池定期检测连接情况,维持连接数量,可复用其维持的连接对象。

最终将请求的耗时下降到了600毫秒左右,减少了 RT 抖动。

但是,自己手动实现的 “池子” 造价高昂,而且可能还会产生 BUG,会发生生产事故。排查问题的过程,也会很让人头疼。

那有没有什么好的办法,可以帮我我们解决这种问题呢?让程序不至于太复杂,又可以有很高的健壮性?

答案,就是本文章的主题 GenericObjectPool

什么是 GenericObjectPool?

GenericObjectPool,是 Apache Commons Pool 库中的一个类,它提供了一个通用的对象池的实现,主要用于复用昂贵或稀缺资源,通过维护一组已创建的对象实例来减少创建和销毁对象的开销,从而提高系统性能

GenericObjectPool 允许用户配置和管理一个对象池,包括设置池的最大和最小对象数量、空闲对象过期时间、borrow(获取)和return(归还)对象时的行为策略等。这个类是高度可配置和灵活的,可以适用于多种不同类型的对象和使用场景。

GenericObjectPool 的特点:

  1. 对象回收与复用:当对象不再使用时,可以归还到池中,供后续请求重复使用,避免频繁创建和销毁对象的开销。
  2. 线程安全:内部实现支持多线程环境下的安全操作,允许多个线程同时从池中借用和归还对象。
  3. 可配置的参数:如最大对象数(maxTotal)、最大空闲对象数(maxIdle)、最小空闲对象数(minIdle)、超时设置等,可以根据具体需求调整池的行为。
  4. 监听器支持:允许添加事件监听器来监控对象池的状态变化,比如对象借用、归还、移除等事件。
  5. 异常处理策略:定义了对池满、池空等情况的处理逻辑,可以通过自定义或配置已有策略来应对这些情况。

GenericObjectPool maven 依赖

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.4.2</version></dependency>

GenericObjectPool 使用举例

可使用单例模式,来使用连接池。具体情况具体分析。

@Slf4j
public class MyClientPool implements Closeable {private static final MyClientPool INSTANCE = new MyClientPool();public static MyClientPool getInstance() {return INSTANCE;}private final GenericObjectPool<MyClient> internalPool;public MyClientPool() {// 设置池的一些参数GenericObjectPoolConfig<MyClient> poolConfig = new GenericObjectPoolConfig<>();// 设置最大连接数量poolConfig.setMaxTotal(10);// 设置最大空闲数量poolConfig.setMaxIdle(8);// 设置最小空闲数量poolConfig.setMinIdle(5);// 向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为falsepoolConfig.setTestOnBorrow(true);// 当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常。单位,毫秒数;默认为-1.表示永不超时.poolConfig.setMaxWait(Duration.ofSeconds(55));// “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(30));// 创建一个池this.internalPool = new GenericObjectPool<>(new MyPooledObjectFactory(), poolConfig);}// 获取一个连接对象public MyClient borrowObject() {try {MyClient client = internalPool.borrowObject();log.info("borrowObject, [idle={}, active={}]", internalPool.getNumIdle(), internalPool.getNumActive());return client;} catch (Exception e) {log.error("borrowObject exception \n", e);throw new SystemException(e);}}// 返还连接对象,当使用完成后要调用。borrowObject和returnObject需成对出现public void returnObject(MyClient client) {try {internalPool.returnObject(client);log.info("returnObject, [idle={}, active={}]", internalPool.getNumIdle(), internalPool.getNumActive());} catch (Exception e) {log.error("returnObject exception \n", e);throw new SystemException(e);}}// 获取对象池此时状态,供监控平台调用public Map<String, Long> poolStatus() {Map<String, Long> map = new HashMap<>(8);map.put("Total", (long) internalPool.getNumActive() + internalPool.getNumIdle());map.put("Active", (long) internalPool.getNumActive());map.put("Idle", (long) internalPool.getNumIdle());return map;}// 关闭池@Overridepublic void close() {try {internalPool.close();} catch (Exception e) {log.error("internalPool close exception", e);}}// 池对象工厂,用于管理创建、验证、激活、钝化以及销毁对象的方法。private static class MyPooledObjectFactory extends BasePooledObjectFactory<MyClient> {// 创建一个资源@Overridepublic MyClient create() throws Exception {log.info("create");MyClient client = new MyClient();// 如果有需要提前初始化的操作,放在此处进行初始化client.connect();return client;}@Overridepublic PooledObject<MyClient> wrap(MyClient client) {return new DefaultPooledObject<>(client);}// 销毁一个对象@Overridepublic void destroyObject(PooledObject<MyClient> pooledClient) throws Exception {log.info("destroyObject");// 如果有一些释放动作,在此处进行MyClient client = pooledClient.getObject();client.close();}// 检测对象是否有效@Overridepublic boolean validateObject(PooledObject<MyClient> pooledClient) {MyClient client = pooledClient.getObject();boolean result = client.isOpen();log.info("validateObject : {}", result);return result;}// 激活对象@Overridepublic void activateObject(PooledObject<MyClient> pooledClient) {log.info("activateObject");}// 钝化对象@Overridepublic void passivateObject(PooledObject<MyClient> pooledClient) {log.info("passivateObject");}}
}

额外说明 - 检测周期

另外要着重说明下,检测周期这个属性。

// “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(30));

如果你应用于连接池这种场景,建议你还是设置检测周期

检测周期的具体数值,可以根据连接断开情况具体设置,最好,可以在连接断开的时间范围内,检测 2~3 次,这样可以让 GenericObjectPool 及时检测到连接对象是否有效,以便重新创建对象,维护空闲对象数。

另外,设置检测周期还有一个好处,可以在连接池对象激活后,自动创建最小空闲数个对象,并且会自动检测并维护对象池。

例如,前面讲到的连接池场景,我希望应用启动后,就建立与第三方平台的连接,最大程度保证请求的低延时。如果不设置检测周期,那么在当实际请求第三方平台时,才会建立连接,也就是说前期请求延迟还是高,并且在空闲一段时间后,GenericObjectPool 也感知不到对象不可用,造成了系统性能不稳定。

所以,对于类似应用场景的同学,要注意 检测周期 的设置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/49390.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HashMap存储数据的put()方法

1. 计算哈希值 2. 处理哈希值 3.查找链表 4. 检查键的存在性 5. 插入新键值对 6. 检查扩容 总结 put()方法是HashMap中用于插入或更新键值对的基本方法。使用 put()方法时&#xff0c;会执行以下步 骤&#xff1a; 1. 计算哈希值 在调用 put(key, value)方法时&#xff0c;首先…

网友提问:桌面与web开发哪个难度更大?

关于桌面应用开发与Web开发哪个难度更大的问题&#xff0c;实际上并没有绝对的答案&#xff0c;因为这取决于具体的开发任务、所使用的工具和技术栈等因素。不过&#xff0c;我们可以从几个方面来进行比较&#xff1a; 技术栈 Web开发&#xff1a; 前端通常涉及到HTML、CSS、J…

用Python编写自动答题脚本——该如何写呢?

编写一个Python自动答题脚本的复杂性和方法将取决于你所要答题的系统的具体实现和限制。以下是一个简化的流程&#xff0c;以及如何在不同情境下编写自动答题脚本的基本思路。 1. 确定答题系统的交互方式 首先&#xff0c;你需要了解答题系统是如何与用户交互的。这可能包括&…

Django—admin后台管理

Django官网 https://www.djangoproject.com/ 如果已经有了Django跳过这步 安装Django&#xff1a; 如果你还没有安装Django&#xff0c;可以通过Python的包管理器pip来安装&#xff1a; pip install django 创建项目&#xff1a; 使用Django创建一个新的项目&#xff1a; …

[Mysql-DDL数据操作语句]

目录 DDL语句操作数据库 库&#xff1a; 查看&#xff1a;show 创建&#xff1a;creat 删除&#xff1a;drop 使用(切换)&#xff1a;use 表&#xff1a; 查看&#xff1a;desc show 创建&#xff1a;create 表结构修改 rename as add drop modify change rename as …

unity对一些列点按顺序围成的封闭区域进行填充mesh

在Unity中&#xff0c;要对一系列顶点按顺序围成的封闭区域进行填充成Mesh&#xff0c;你可以采取以下步骤&#xff1a; 定义顶点数组&#xff1a; 首先&#xff0c;你需要定义一个顶点数组&#xff0c;其中包含封闭区域的所有顶点的位置信息。 定义三角形索引数组&#xff1a…

探索Linux-1-虚拟机远程登陆XShell6远程传输文件Xftp6

Linux是什么&#xff1f; Linux是一个开源的操作系统内核&#xff0c;由林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;于1991年首次发布。它基于Unix操作系统&#xff0c;但提供了更多的自由和灵活性。Linux内核是操作系统的核心部分&#xff0c;负责管理系统资源、处理…

Spring 集成框架和技术 06

Spring 是一个开放性的框架&#xff0c;设计之初就考虑了与各种第三方框架和技术的集成&#xff0c;以提供更广泛的应用支持。以下是 Spring 可以集成的一些主要框架和技术&#xff1a; 1. 持久化框架&#xff1a; Hibernate&#xff1a;通过 Spring 的 LocalSessionFactoryB…

以线程完成并发的UDP服务端

网络(九)并发的UDP服务端 以线程完成功能 客户端 // todo UDP发送端 #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <stdlib.h> #include <string.h…

MySQL的表,视图,索引创建

一。创建表 1。创建Student表 mysql> create table Student(Sno int primary key auto_increment,Sname varchar(30) not null unique,Ssex varchar(2) check (Ssex 男 or Ssex 女) not null,Sage int not null,Sdept varchar(10) default 计算机 not null); 2.创建Cour…

Infuse Pro for Mac全能视频播放器

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行测试安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件…

java 求两个字符序列的最长公共字符子序列

/* 求两个字符序列的最长公共字符子序列。 给定两个字符串&#xff0c;求解这两个字符串的最长公共子序列&#xff08;Longest Common Sequence&#xff09;。 比如字符串1&#xff1a;BDCABA&#xff1b;字符串2&#xff1a;ABCBDAB&#xff0c; 则这两个字符串的最长公共子序…

Dav_笔记11:SQL Tuning Overview-sql调优 之 5

构建SQL测试用例 对于许多与SQL相关的问题&#xff0c;获得可重现的测试用例可以更轻松地解决问题。从11g第2版&#xff08;11.2&#xff09;开始&#xff0c;Oracle数据库包含SQL测试用例构建器&#xff0c;它可以自动完成收集和复制尽可能多的有关问题及其发生环境的信息的难…

【JavaScript】深入理解 `let`、`var` 和 `const`

文章目录 一、var 的声明与特点二、let 的声明与特点三、const 的声明与特点四、let、var 和 const 的对比五、实战示例六、最佳实践 在 JavaScript 中&#xff0c;变量声明是编程的基础&#xff0c;而 let、var 和 const 是三种常用的变量声明方式。本文将详细介绍这三种变量声…

如何防止用户通过打印功能复制页面文字

简单防白嫖&#xff0c;要让打印出来的页面是空白&#xff0c;通常的做法是在打印时隐藏页面上的所有内容。这可以通过CSS的媒体查询&#xff08;Media Queries&#xff09;来实现&#xff0c;特别是针对media print的查询。 在JavaScript中&#xff0c;你通常不会直接控制打印…

上传文件传参 pc端vue的formData

formData let formData new FormData(); formData.append("file", blob, ref ".png"); //添加参数并且重新命名文件名称 if(ref.toString().indexOf(qrcode) > 0) formData.append(noStbg, true)//添加参数 uploadType(formData, sour…

USB转多路串口-纯硬件实现串口数据传输指示灯电路

前言 串口相关产品往往要求有数据收发时LED闪烁&#xff0c;我们经常会用软件实现&#xff0c;在MCU内注册一个定时器&#xff0c;有数据发送时就闪烁一段时间。软件点灯这种方式存在两个缺陷&#xff0c;一是接收方向不好实现&#xff1b;二是定时器一般用固定频率&#xff0…

Linux系统:date命令

1、命令详解&#xff1a; date 命令可以用来显示或设定系统的日期与时间。 2、官方参数&#xff1a; -d, --dateSTRING 通过字符串显示时间格式&#xff0c;字符串不能是now。-f, --fileDATEFILE 类似 --date 在 DATEFILE 的每一行生效-I[FMT], --iso-8601[FMT…

Redis底层数据结构的实现

文章目录 1、Redis数据结构1.1 动态字符串1.2 intset1.3 Dict1.4 ZipList1.5 ZipList的连锁更新问题1.6 QuickList1.7 SkipList1.8 RedisObject 2、五种数据类型2.1 String2.2 List2.3 Set2.4 ZSET2.5 Hash 1、Redis数据结构 1.1 动态字符串 Redis中保存的Key是字符串&#xf…

【C语言】VS的实用调试技巧

0. 前言 VS(Visual Studio)是集成开发环境&#xff0c;其内置了多种调试工具和技巧帮助开发人员在开发过程中解决问题。包含断点、监视窗口、自动窗口、调用堆栈等&#xff0c;通过这些技巧&#xff0c;开发人员可以有效地调试和解决程序中的问题。我们在VS编译器上写代码&…