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() 可能导致未被新增的数据丢失 还有其他缺点欢迎在评论区指出

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

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值