mybatisPlus 批量插入插件 高并发下性能指标解决方案

最近有个奇葩性能指标 单个新增接口 要求1000并发下 单个请求响应低于2s 

使用jmeter测试了一个最简单的新增接口 发现在1000并发下 排除日志打印 权限校验等一系列操作后 响应时间仍然有6000+ms 后面尝试使用event事件 刚开始时 响应时间在 900ms左右 但是随之事件越推越多 系统性能主键到达瓶颈 最终响应时间仍然不达标

最终决定应付指标 使用BlockingQueue先将对象进行存储 等请求时间结束统一进行批量新增操作 最后响应时间成功达到900ms 完成指标  

后面又由于项目使用的mybatisPlus 所以对代码进行拓展 以结合mybatisPlus进行使用 代码如下:

@Component
public class BatchInsertServiceImplFactory {
	private Map<String, AbsBaseService> absBaseServiceHashMap=new HashMap<>();

	@Autowired
	private ApplicationContext applicationContext;
	public AbsBaseService getAbs(String serviceBeanName){
		return absBaseServiceHashMap.get(serviceBeanName);
	}
	@PostConstruct
	public void register(){
		String[] beanNamesForType = applicationContext.getBeanNamesForType(AbsBaseService.class);
			for (String s : beanNamesForType) {
				AbsBaseService bean = (AbsBaseService)applicationContext.getBean(s);
				absBaseServiceHashMap.put(bean.getBeanName(),bean);
			}
	}
}

这是一个工厂类 用于在类加载阶段 动态的获取所有AbsBaseService接口的实现类 在原文中 作者使用getBeanOfType 但是我本地在尝试时 这个方法获取不到bean 不知道为什么

public interface AbsBaseService<T> extends BaseService<T> {
	String getBeanName();
}

 这是AbsBaseService接口 定义了一个获取类名方法 同样在AbsBaseEntity定义一个相同的方法 这样可以实现通过AbsBaseEntity与AbsBaseService之间形成映射

public abstract class AbsBaseEntity extends BaseEntity {
	public abstract String getBeanName();
}

这是AbsBaseEntity 用于获取AbsBaseService

@Component
public class BatchInsertService<T extends AbsBaseEntity> {
	private final BlockingQueue<T> queue=new LinkedBlockingQueue<>();
	private static final long TIMEOUT_MS=2000;
	private volatile long lastInsertTime=System.currentTimeMillis();
	@Autowired
	private BatchInsertServiceImplFactory factory;
	public BatchInsertService(){
		new Thread(()->{
			while (true){
				try {
					List<T> batch=new ArrayList<>();
					while (true){
						T poll = queue.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
						if (null!=poll){
							batch.add(poll);
							lastInsertTime=System.currentTimeMillis();
						}
						if (batch.isEmpty()) break;
						long idLeTime=System.currentTimeMillis()-lastInsertTime;
						if (idLeTime>TIMEOUT_MS) break;
					}
					if (!batch.isEmpty()){
						batchInsert(batch);
					}
				}catch (InterruptedException e){
					Thread.currentThread().interrupt();
				}
			}
		}).start();
	}
	public boolean save(T t){
		return queue.add(t);
	}
	private void batchInsert(List<T> ts){
		int batchSize=10000;
		int totalSize=ts.size();
		int batchCount=(int) Math.ceil((double)totalSize/batchSize);
		if (CollectionUtils.isEmpty(ts)){
			return;
		}
		AbsBaseService<T> abs = factory.getAbs(ts.get(0).getBeanName());
		for (int i = 0; i < batchCount; i++) {
			int start=i*batchSize;
			int end=Math.min((i+1)*batchSize,totalSize);
			List<T> ts1 = ts.subList(start, end);
			abs.saveBatch(ts1);
		}
		queue.clear();
	}
}

这是实现批量插入的类 也是核心逻辑

通过定义一个线程安全的队列 将所需要新增的对象暂时存储在内存中  并且定义一个超时时间 如果超过这个时间没有新的对象进入 则进行批量新增操作 

同时暴露一个新增接口 在相关代码逻辑中进行调用

在执行批量新增操作时 同时进行分批插入 以防止过多数据同时插入产生错误

在具体使用时 先把需要执行批量插入的实体类基础AbsBaseEntity 然后将其对应的serviceImpl实现AbsBaseService 实现这两个方法的返回值需要相同 

然后在具体的需要进行批量新增的类中 声明BatchInsertService 并指定泛型  注意 如果是在对应的service中使用 会出现循环依赖问题 这时候可以使用@Lazy解决  或者改变代码中的AbsBaseService为AbsBaseMapper

值得注意的是 这只是一个应付指标的方案 考虑到具体的场景 仍然有很多可以优化的地方 比如 如果请求不断的进入 queue可能越来越大 最终导致OOM  再比如 如果我只想简单的新增一条数据 仍然需要等待两秒的时间  再比如我在最后执行了queue.clear() 可能导致未被新增的数据丢失 还有其他缺点欢迎在评论区指出

工厂方法的部分代码逻辑非本人原创 但是找不到原文了 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值