import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;
@Slf4j
public class BatchInsertHandlerUtil<T> {
/**
* 容器
*/
private List<T> batch;
/**
* 批量条数
*/
private final int batchSize;
/**
* 批量要做的事情
*/
private final Consumer<List<T>> flushFunction;
/**
* 关闭批量标识
*/
private transient volatile boolean closed = false;
/**
* 定时任务service
*/
private transient ScheduledExecutorService scheduler;
/**
* 定时任务执行结果
*/
private transient ScheduledFuture<?> scheduledFuture;
/**
* 异常信息
*/
private transient volatile Exception flushException;
/**
* 操作失败,重试次数
*/
private final int exceptionRetryNum;
/**
* 定时任务周期
*/
private final long period;
/**
* 线程工厂
*/
private final transient ThreadFactory factory;
/**
* 构建函数
*
* @param batchSize 批量条数
* @param flushFunction 批量要做的事情
* @param factory 线程工厂
* @param period 定时任务周期
* @param exceptionRetryNum 批量执行失败重试次数
*/
public BatchInsertHandlerUtil(int batchSize, Consumer<List<T>> flushFunction, ThreadFactory factory, long period, int exceptionRetryNum) {
this.batchSize = Math.max(batchSize, 1);
this.flushFunction = flushFunction;
this.factory = factory;
this.batch = Lists.newArrayList();
this.exceptionRetryNum = Math.max(exceptionRetryNum, 1);
this.period = period < 0 ? 0 : period;
this.open(this.period);
}
/**
* 开启定时任务
*
* @param period 定时任务周期
*/
public void open(long period) {
if (period <= 0) {
return;
}
this.scheduler = new ScheduledThreadPoolExecutor(1, factory);
this.scheduledFuture = this.scheduler.scheduleWithFixedDelay(() -> {
synchronized (BatchInsertHandlerUtil.this) {
if (!closed) {
try {
log.info("定时任务触发批量添加,size{}", batch.size());
flush();
} catch (Exception e) {
flushException = e;
}
}
}
}, period, period, TimeUnit.SECONDS);
}
/**
* 检查是否发生了刷新操作的异常,如果有异常,则抛出一个包含异常信息的运行时异常。
*
* @throws RuntimeException 如果刷新操作发生异常,则抛出包含异常信息的运行时异常。
*/
private void checkFlushException() {
if (flushException != null) {
throw new RuntimeException("flush操作失败.", flushException);
}
}
/**
* 向容器中添加记录(record)。
*
* @param record 要添加到容器的记录
*/
public synchronized void add(T record) {
// 检测是否出现异常,出现后不可再往容器中添加数据
checkFlushException();
batch.add(record);
int size = batch.size();
if (size >= batchSize) {
log.info("数量触发批量添加,size{}", size);
flush();
}
}
/**
* 执行批量操作
*/
public synchronized void flush() {
// 检测是否出现异常,异常不再执行flush操作
checkFlushException();
// 重试机制,重试exceptionRetryNum次没有成功则抛异常
for (int i = 0; i <= exceptionRetryNum; i++) {
try {
flushFunction.accept(batch);
batch.clear();
break;
} catch (Exception e) {
log.error("批量操作,执行第:{}次flush失败", i, e);
if (i >= exceptionRetryNum) {
throw new RuntimeException("批量操作,执行flush失败。", e);
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new RuntimeException("批量操作,执行flush失败。", e);
}
}
}
}
/**
* 关闭定时任务
*/
public synchronized void close() {
if (!closed) {
closed = true;
// 关闭定时任务,如果在执行等待执行完关闭
if (this.scheduledFuture != null) {
scheduledFuture.cancel(false);
this.scheduler.shutdown();
}
// 检查容器中是否存在内容,存在添加完再关闭
if (batch.size() > 0) {
try {
flush();
} catch (Exception e) {
throw new RuntimeException("关闭定时任务执行flush方法失败。", e);
}
}
}
}
}
IOPS问题-批量操作
最新推荐文章于 2024-07-09 16:46:46 发布