场景:
全量同步上游酒店数据,需要同步酒店数据量近百万,
上游接口一家酒店需要调用2次接口才能获取完整的酒店数据,并且一次只能查询一家酒店
本地需要更新的表有9张表,7张百万级别,2张千万级别
如果一个一个酒店查询,然后更新就需要更新 近千万次(100w*9),如果一次更新耗时100ms 那么一千万次耗时:270多个小时,那么开了10个线程 也要27个小时
如何加快查询处理速度?
比如有a,b,c,d,e,f,g,h,i这九张表
处理方案:
1.如果存储空间足够可以考虑采取读写分离(每一个表都有备份表, 一个是当前查询使用,一个是当前全量更新使用,全量更新前truncate掉,然后批量插入,全量处理完把 当前全量更新的表切换为 读表) 因为我们这边运维出于成本考虑,不同意数据库扩容
故此方案不可行
2.
业务特点:只有全量更新时候才会更新这些记录
(暂不考虑数据的强一致性,我们可以采取表的维度去处理,失败重试3次,
重试工具类
仍处理失败的插入到补偿处理表,后续补偿处理保证数据最终一致性)
批量处理+多线程
把调用上游的结果存储起来,然后积攒到一定数量,批量更新数据库,按照表维度更新
,以前是数据强一致性,其中 h,i 是我们聚合上游后
的表 只能更新不能删除
// 伪代码
@Transation
public void update(WjsHotelInfo hotelInfo){
// 插入前批量删除 a,b,c,d,e,f,g
delateBeforeInsert(hotelInfo);
// 插入a,b,c,d,e,f,g 表
insert(hotel);
// 处理 h,i
dealAfterUpdate(hotel);
}
批量处理采取方案:
伪代码
public void update(List<WjsHotelInfo> list){
// 插入前批量删除 a,b,c,d,e,f,g
delateBeforeInsert(list);
// 插入a,b,c,d,e,f,g 表
insert(list);
// 处理 h,i
dealAfterUpdate(list);
}
为了解耦查询上游和把查询上游结果存储起来,参考生产者和消费者写了个
工具类
@Slf4j
public abstract class BatchDealUtil<T,R> {
protected ThreadPoolExecutor threadPoolExecutor = null;
protected int corePoolSize;
//批量处理大小
protected int batchSize = 200;
protected volatile ArrayBlockingQueue<R> resultQueue = null;
//生产者队列容量
protected int capacity = 2000;
//消费者最后一次判断
protected volatile boolean lastFlag = false;
protected Consumer<List<R>> consumer = null;
protected Function<T,R> function = null;
Thread thread = null;
protected abstract Consumer<List<R>> initConsumer();
protected abstract Function<T,R> initFunction();
public BatchDealUtil(){
this(4);
}
public BatchDealUtil(int corePoolSize){
this(corePoolSize,100);
}
public BatchDealUtil(int corePoolSize,int batchSize){
this(corePoolSize,batchSize,2000);
}
public BatchDealUtil(int corePoolSize,int batchSize,int capacity){
if(batchSize > capacity){
throw new BaseException("批次处理的条数不能大于队列的大小");
}
this.corePoolSize = corePoolSize;
this.batchSize = batchSize;
this.capacity = capacity;
resultQueue = new ArrayBlockingQueue<>(capacity);
threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,corePoolSize,0, TimeUnit.SECONDS,new ArrayBlockingQueue<>(corePoolSize+1),
new ThreadPoolExecutor.CallerRunsPolicy());
consumer = initConsumer();
function = initFunction();
thread = new Thread(() -> dealValue());
thread.start();
}
public void producer(List<T> list){
if(CollectionUtils.isEmpty(list)){
return;
}
for (T t : list) {
threadPoolExecutor.submit(() -> {
R result = function.apply(t);
try {
resultQueue.put(result);
if(resultQueue.size()>=batchSize){
LockSupport.unpark(thread);
}
}catch (Exception err){
log.info("发生异常",err);
err.printStackTrace();
}
});
}
}
/**
* 消费者
*/
private void dealValue(){
try {
while (true){
int size = 0;
if(lastFlag && resultQueue.size() == 0){
break;
}
while (!lastFlag && resultQueue.size() < batchSize){
LockSupport.park();
}
List<R> list = new ArrayList<>(batchSize);
int total=0;
size = Math.min(resultQueue.size(),batchSize);
while(!resultQueue.isEmpty() && total++ < size){
list.add(resultQueue.poll());
}
consumer.accept(list);
}
}catch (Exception e){
log.info("消费者处理出现异常",e);
e.printStackTrace();
}
}
/**
* 必须调用shutDown方法
*/
public void shutDown(){
threadPoolExecutor.shutdown();
while (threadPoolExecutor.getActiveCount()>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
LockSupport.unpark(thread);
lastFlag = true;
}
}
使用例子:
static class MyBatchDealUtil extends BatchDealUtil<Integer,Integer>{
public MyBatchDealUtil(int corePoolSize, int batchSize, int capacity) {
super(corePoolSize, batchSize, capacity);
}
//消费者,模拟批量插入数据库
@Override
protected Consumer<List<Integer>> initConsumer() {
return (t)->{
for (Integer integer : t) {
System.out.print(integer+"\t");
}
System.out.println("\t-------");
};
}
// 生产者,模拟调用上游接口
@Override
protected Function<Integer, Integer> initFunction() {
return e->{
sleep(100);
return e;
};
}
}
public static void main(String[] args) {
MyBatchDealUtil dealUtil = new MyBatchDealUtil(4,10,100);
for (int i=0;i<10;i++){
List<Integer> list = getList(5);
// System.out.println(list);
dealUtil.producer(list);
}
dealUtil.shutDown();
}
private static List<Integer> getList(int i) {
Random random = new Random();
return random.ints(0,100).limit(i).boxed().collect(Collectors.toList());
}
public static void sleep(long time){
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}