背景:在日常开发中,除了考虑IO操作、线程上下文切换、GC的影响性能外。还通过池化技术提高性能通过循环复用资源,降低资源创建和销毁带来的开销和损失,从而提高性能,例如对象池、内存池、线程池、连接池
一、对象池(JedisPool举例)
1、jedis对象池和非对象池测试用例对比,使用池化技术很大提高OPS性能,测试如下
package com.sk.pool.memory;
import com.sk.util.DateTimeUtils;
import org.openjdk.jmh.annotations.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 1)//预热 5 轮,每次 1s
@Measurement(iterations = 5, time = 1)//测试 5 轮,每次 1s
@Fork(2)//2个线程
@State(Scope.Benchmark)
public class RedisTest {
JedisPool pool = new JedisPool("172.17.1.150", 6379);
@Benchmark
public void redisPool() {
Jedis jedis = pool.getResource();
jedis.auth("***");
jedis.set("value", DateTimeUtils.getCurrentDateTime());
jedis.close();
}
@Benchmark
public void redisSimple() {
Jedis jedis = new Jedis("172.17.1.150", 6379);
jedis.auth("***");
jedis.set("value", DateTimeUtils.getCurrentDateTime());
jedis.close();
}
}
pom.xml引用文件
<dependencies>
<!-- jhm工具包 -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
<scope>provided</scope>
</dependency>
<!--redis工具包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.1</version><!--版本号-->
</dependency>
</dependencies>
JMH结果为
2、reids对象池管理分析
Jedis jedis = pool.getResource();
--main入口
Jedis jedis = pool.getResource();
--pool class
public T getResource() {
try {
return internalPool.borrowObject();
} catch (Exception e) {
throw new JedisConnectionException("Could not get a resource from the pool", e);
}
}
--GenericObjectPool class
public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
...省略
PooledObject<T> p = null;
boolean create;
while (p == null) {
create = false;
//对象池获取对象
p = idleObjects.pollFirst();
if (p == null) {
//对象池未获取到,创建对象
p = create();
if (p != null) {
create = true;
}
}
...省略
return p.getObject();
}
private PooledObject<T> create() throws Exception {
...省略
final PooledObject<T> p;
try {
p = factory.makeObject();
...省略
return p;
}
--jedisFactory class
public PooledObject<Jedis> makeObject() throws Exception {
final HostAndPort hostAndPort = this.hostAndPort.get();
final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), this.timeout);
--redis连接
jedis.connect();
if (null != this.password) {
jedis.auth(this.password);
}
if (database != 0) {
jedis.select(database);
}
if (clientName != null) {
jedis.clientSetname(clientName);
}
--返回reids连接对象
return new DefaultPooledObject<Jedis>(jedis);
}
对象池管理工具类GenericObjectPool依赖Java公用的池化包commons-pool2实现对象池化管理
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
3、对象池管理生命周期,参数设置建GenericObjectPoolConfig
转载:面试官:Java 池化技术你了解多少?_肥肥技术宅的博客-CSDN博客
二、内存池(netty4举例 时间有限后续补充)
内存池技术原理:【池化技术】内存池技术原理和C语言实现_小熊coder的博客-CSDN博客
三、线程池
线程池技术原理想必都知道,这里不赘述
集成多线程怎么保证以下两点:
1、性能稳:怎样保证不知道业务量总数情况下采用多线程 线程队列池:
java.util.Queue 线程隔离器:
CyclicBarrier 或 CountDownLatch
锁
任务执行器(对外)
2、逻辑稳:怎样使改造后逻辑出错率接近为0
逻辑侵入 接近 0
/*
* 线程处理接口
* */
public interface ThreadFacade<T, R> {
/*
* 返回任务集合
* */
public List<R> execute(List<T> task);
}
/*
* 任务执行器
* */
public interface TaskThreadFacade<T,R> {
//任务执行
R execute(T t);
}
public class ThreadTemplate<T,R> implements TaskThreadFacade<T,R> {
/*
* 多线程任务构造器:在这里构造所任务信息
* */
public List<R> batchExecute(List<T>task){
ThreadFacade<T,R> threadService = new ThreadFacadeImpl<T,R>(this,8);
return threadService.execute(task);
}
/*
* 任务实现器:在这里处理需要执行的任务
* */
@Override
public R execute(T t) {
return null;
}
}
--业务调用实现
--1、原来循环代码
List<Object> resultList = new LinkedList<>();
for(Object item:ObjectList){
Object result = ...省略任务执行内容
resultList.add(result);
}
--2、改造后代码
List<Object> resultList = ThreadTemplate.batchExecute(ObjectList);
改造前:批量审核20份以上报告,耗时都比较久。45份耗时33秒,92份耗时67秒,110份耗时72秒
改造后:100份报告耗时8秒
出错率:0次
四、连接池(web请求和druid组件)
1)、web请求伪代码
public static String callWebservcie(String urlKey, String url,String interfaceName,Object[] params) {
String result = "";
Client client = null;
// 创建动态客户端
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
if (!mapHiswebservice.containsKey(urlKey)) {
client = dcf.createClient(url);
mapHiswebservice.put(urlKey, client);
}else{
client =mapHiswebservice.get(urlKey);
}
// 需要密码的情况需要加上用户名和密码
// client.getOutInterceptors().add(new ClientLoginInterceptor(USER_NAME,
// PASS_WORD));
try {
Object[] resultBacks = client.invoke(interfaceName,params);
if (resultBacks != null && resultBacks.length > 0) {
result = resultBacks[0].toString();
}
} catch (Exception e) {
logger.error("原因[{}]异常,异常信息[{}],参数[{}]",
"Webservice调用异常",
e.getMessage(),
JSONArray.toJSON(params),
e);
throw new RuntimeException(e);
}
return result;
}
2)、druid(待更新)
primary.datasource.jdbc.type=com.alibaba.druid.pool.DruidDataSource
primary.datasource.jdbc.queryTimeout=15
#tcp/ip配置方式
primary.datasource.jdbc.maximum-pool-size=300
#配置初始化大小、最小、最大
primary.datasource.jdbc.initial-size=5
primary.datasource.jdbc.min-idle=5
primary.datasource.jdbc.max-active=200
#配置获取连接等待超时的时间
primary.datasource.jdbc.max-wait=10000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒。检测时:1.如果连接空闲并且超过minIdle以外的连接,如果空闲时间超过minEvictableIdleTimeMillis设置的值则直接物理关闭。2.在minIdle以内的不处理。
primary.datasource.jdbc.time-between-eviction-runs-millis=60000
#配置一个连接在池中最小生存的时间,单位是毫秒。testWhileIdle为true时,如果连接空闲时间超过minEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查
primary.datasource.jdbc.min-evictable-idle-time-millis=30000
#检验连接是否有效的查询语句
primary.datasource.jdbc.validation-query=SELECT 1
#设置从连接池获取连接时是否检查连接有效性,true时,每次都检查;false时,不检查
primary.datasource.jdbc.test-on-borrow=false
#设置往连接池归还连接时是否检查连接有效性,true时,每次都检查;false时,不检查
primary.datasource.jdbc.test-on-return=false
#设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过minEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查
primary.datasource.jdbc.test-while-idle=true
#链接使用超过时间限制是否回收
primary.datasource.jdbc.remove-abandoned=true
#超过时间限制时间(单位秒),目前为5分钟,如果有业务处理时间超过5分钟,可以适当调整。
primary.datasource.jdbc.remove-abandoned-timeout=300#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
primary.datasource.filters=stat,wall,log4j