Delta 如何解决并发写冲突(乐观锁)

2022-04-02 15:53:14 浏览数 (1)

首先,delta不存在读写冲突。原因是因为在Delta中写不影响读。那为什么Delta写不影响读呢?很简单,delta能够保持版本,而且版本随着写入不断递增,之前的版本不会有变化。这意味着你读的那一瞬间,后面有什么新写入,你肯定是看不到的,后面有什么删除,也不会对你有影响。

那么delta真正需要解决的是并发写冲突。一般而言,写分成三种情况:

  1. 需要读取当前表的数据,然后计算,接着写入新的文件,删除旧的文件。这种模式典型的是upsert操作。
  2. 只是写新文件,不会使用表已有的数据。这种模式典型的append操作。
  3. 删除数据,会使用当前表的数据,删除旧文件。

一个复杂的commit可能存在(1,3),(2,3) 组合。

假设有两个写操作A,B, 假设他们都是1操作,这意味着他们都需要依赖当前的历史数据。这个时候,他们都会各自进行操作,在最后进行commit操作的时候,只会有一个成功,失败的那个只有两个选择:

  1. 抛出异常,进程退出
  2. 重试。重试主要是重新读取包含新commit的数据,然后再次进行写操作。

如果A是1,B是2, B失败了,只要重新进行commit就好,而无需在进行完整的写操作。而如果A失败了,那么A需要走完整的写流程。

如果A是2,B也是2,那么失败一方都只需要重新进行commit操作,而不需要在重新生成数据。

对于包含3的动作,处理方式和1一样。

另外,值得注意的是,A,B两个写动作,可以在不同的Spark实例,也可以在相同的Spark实例。上面的处理机制都是通用 的。但是同一个实例的A,B并发写动作,可以使用内存中的锁,从而可以等待对方释放锁,而无需像上面那样。

这点也是Delta 做的比较好的地方,这意味着你可以不同实例对同一个delta表进行不同类型并发的写,同时还不影响数据的并发读。缺点也比较明显,很多写操作是比较重的,比如upsert,失败了之后要重新基于新更新的数据做计算,然后再次尝试进行commit操作,所以写的并发度是很低的。

关于delta的版本,本质上是每次写入的时候就会搞一次commit,每次commit就形成了一个版本。读的时候可以读最新版本,也可以读以前任意一个版本。每个版本都相当于一个SNAPSHOT,他会重放当前版本以及之前版本的文件操作从而得到该SNAPSHOT。Delta会定期将老的版本删除(七天之前的),同时还会降多个commit合并成一个文件,方便更加快速的读取。

0 人点赞