背景
hive表是不支持并发插入的,因为涉及到文件操作,并发写可能导致写过程中争夺一个文件失败,故想要实现并发写需要其他的方式来实现
方案一:采用hive事务,虽然控制了并发数,采用一个一个任务写,但是性能不高
方案二:采用分区,分区可以隔离文件操作,避免争夺文件导致失败,但是需要控制分区数量,分区太多会导致性能下降
方案三:替换其他数据库,如hbase
方案四:批量写入,将所有要写入的量汇总成一个批次写入,也就没有并发可言了。
技术选型
没有最好的方案,只有更合适的方案,结合目前团队业务,最好的还是采用分区写,但是需要控制分区数量。
实现方案
通过令牌桶实现,令牌数量是固定的,每次插入前来拿一个令牌,用完之后放回去,令牌桶用完了再来取的线程报错
业务目标
-
均匀插入到不同分区,使数据分散,这样能解决spark数据倾斜问题,提高查询效率
-
分区不能重复,会存在并发问题,同一时刻只能有一个线程往这个分区插入
因为要解决并发,首先就要考虑redis,然后就是redis数据类型的选型,这里采用redis zset,因为我们均匀插入不同分区,即要实现滚动向前的操作,所以利用score每次操作之后进行自增来实现滚动操作,zset的member存储所有值,score来解决占用和释放问题,和滚动向前的操作
member=1,score=1;
member=2,score=2;
.......
member=200,score=200;
操作步骤
-
线程1 获取分区号 从score[1~+inf]中获取一个,因为第一次获取,所以得到member=1,score=1;
-
线程1 获取到之后将score更新为member=1,score=0;
-
然后线程1 执行spark任务操作
-
此时线程2 也来获取分区号,同样从score[1~+inf]中获取一个,因为1被线程1 使用,这里获取到member=2,score=2;
-
线程2 获取到之后将score更新为member=2,score=0;
-
假设 线程2 处理较快,比线程1 先结束,线程2 开始释放分区号,member=2,score=max(score)+1即=201;
-
线程1执行结束开始释放分区号member=1,score=max(score)+1即=202;