分布式数据库中schema的lease

2016-11-28

或者叫分布式数据库里面如何通过lease维护schema的一致性。突然想写写lease这个话题,是因为最近review代码的一些感触。记得上次写lease相关的还是在好多年前了。

数据库会有schema,就是数据库里面会有表,字段,索引这些信息。分布式对的数据库的schema会有什么影响?一个机器上改了schema,另一个机器上必须感知,否则,表A原来是a b c三列,后来一个机器里面执行操作加了一列变成四列,另一个机器里面还是插入三列的数据,就乱了。

也就是说对于分布式中的每台机器,大家对schema的认知必须达成一致。怎么做?我们可以先假设schema信息有一个中心化的存储,但是对于分布式的节点,每个操作前都去中心化存储那边拉一遍全量的schema数据,schema数据那么多,拉一遍要好久,这么naive的想法肯定是不靠谱的。

于是就有了缓存,每个节点将中心化存储里面的schema信息在本地缓存起来,只有当中心化存储的scheam变了,本地才需要更新缓存去中心化存储那边同步数据。缓存数据带了版本,检查版本就知道数据变没变,没变就可以用本地schema。但是检查版本要走网络,如果每次普通的增删改查操作都这么做还是不靠谱的。

于是就有了lease。从中心化存储那边拉schema时带这些信息:schema数据本身,schema的版本(或者用最后更新时间当版本),lease颁发时间。

lease是一个时间,从得到lease颁发的时间,到这个时间加lease这段,本地缓存的schema数据是不过期的。至于超了这个时间数据的有效性就不能保证了,客户端需要再去拉一下schema信息。

举个例子说一下,某个分布式节点全量拉了一次schema,数据是blah blah blah,版本是10,lease发布的时间是11点46分10秒,如果lease时间设置的是10秒,那意味着直到11点46分20秒,本地的数据都是有效的。而同时也意味着,中心化存储那边承诺了在11点46分20秒之前,schema是不会再更新的。

有一个问题:时间!schema信息里面到底用的什么时间?既然是由中心化存储颁发的,那就是指中心化服务的时间,不是指分布式节点的本地时间,本地时间是不能保证一致的。

插入说一句,也就是开头的,为什么写这篇文章。我们的数据库相应的模块实现,最近老是出一些问题。所以我觉得有点乱,乱的原因是复杂,复杂的原因是没想清楚。如果没想清楚,很容易各种修修补补,改一个bug却引入二个bug,然后代码越来越复杂,越改越乱走向失控。而如果真的想清楚了,写代码应该是非常清晰明了,读的人能一眼看明白的。我当然是站着说话不腰疼,因为在review代码的时候发现自己不太明白了,打死不会承认自己不懂,而是怪写得不够明白。哈哈,逃~


理解了实现时才能写简单的代码,为此先研究复杂性的来源。

  • 并发与加锁

schema信息需要是全局变量,CURD的线程读它,更新schema的线程写它。并发要加锁,schema全局变量的字段比较多,区分哪些是有意义的哪些是无意义的,哪些需要记录哪些不需要。修改相应字段的线程也比较多,合理规划好。

最核心的三个:schema数据本身,schema的版本,schema的lease颁发时间。这必须是同一组才有意义。

至于本地维护schema的后台线程,在什么本地时间跟远端做过同步,最后一次同步时成功了还是失败了,下次在什么时间同步,这些信息跟外界毫不相关,不应该维护在全局变量。即使有什么并发,它们的锁跟外界也毫无关联。

先获取版本和更新时间,只有当版本不一致才拉具体数据信息,这属于怎么实现的范畴。至于拉数据的时候,如果发现版本跟本地差异不太大,则只拉差异的数据,如果版本落的太多刚拉全量;拉数据的时候用并行的拉,这属于优化。怎么实现和优化不应该对于理解问题以及代码逻辑制造混乱。

维护lease的线程只管不停地检查版本和lease颁发时间,至于检测周期是多久一次,这个后面再说。

做CURD的并发是比较高的,这些操作必须检查本地的schema是否有效,还好是读锁。CURD需要知道的信息仍然是最核心的那三个。至于本地最后一次去远程同步是在什么时间,同步是成功还是失败,CURD的线程不需要care。

拉到数据后无非有三种情况:有效,无效,未知。

CURD落在lease内的有效,落在lease以外可能有效可能无效,要根据schema版本变没变再做判断。真正的复杂性来源于:可能上一次同步schema时操作失败了,本地的schema信息不是同步的。并且落在lease外面,那CURD即不能判定成功,又不能判定失败,只能等重新拉。一个细节是千万不能带着锁去sleep,那就要死锁了。

  • 时间

我觉得真正引入混乱的就是时间,本地时间和中心化时间。一旦把 "最后一次去跟远程同步数据" 的本地时间 跟 "lease颁发时间"或者"schema版本最后更新时间" 混为一谈,就会乱了。

后台的schema同步线程,无非是本地以某个频率跟中心化的服务同步一下数据。这个动作发生的时间是本地时间,只有这个动作取到的数据里面,存的才是真实时间。仅仅是这个频率,会跟lease时间长短会有一点点的关系,而已。

另一个是,"lease颁发时间",跟 "schema版本有没发生并化",这两项是不需要关联的。拿"schema最后更新时间"的当时来当作schema的版本,并不会造成问题,但是拿它当lease颁布时间就乱了。

schema同步应该按什么样的频率?假设lease比较大,而同步间隔无限小,那本地与远程几乎是保持同步的。而假设lease比较小,同步间隔非常大,那拿到数据的时候lease已经过期了。

  • 拉数据耗时太长

发现schema过期了,去重新load schema数据,但是load的时间太长了,导致schema数据load下来的时候,lease又已经过了。这样schema永远是无效的。

这个问题是lease的设置问题,lease设的太短了,比load数据花费时间都短的话,整个机制就无法工作了。

  • 频繁schema变更

由于引入了lease,schema必须保证在lease时间之内是不会发生变化的,也就导致每次改完schema,至少要等待一段时间。在正常的使用场景这并没有什么问题,但是假设现在是正在导数据,导数据开始时会频繁的建库,建表,建索引等操作。那schema就是不停地变化,而如果lease设置过长,这个过程就慢如蜗牛了。

这个时候实际上面临的一个进退两难:lease设置太短,在lease时间内完不成load数据,那拿到schema的时候schema已经过期了,服务不可用。而lease设置的太长,导数据过程会太慢。

其实针对导数据时的特殊场景,如果是第一次导入,可关掉lease机制,只要业务方能保证所有schema完成之后才会导入数据。

仍然,lease可以变这个设定会引入的复杂度过高,不应该为此做通用的支持,而使得实现上的复杂度失控。


续租问题是另外的问题,在这里暂时不做讨论。

这里只是通俗的讲lease技术用于解决分布式数据库里面的schema问题,以及思考在实现过程中会遇到的一些复杂性因素。但是跟我们实际上项目的实现并没有关联性,因为我们实现用了不同的算法,没有单独的中心化schema存储,并不依赖所有分布式的节点看到完全一致的schema版本,约束也有一些变化。不过lease用于维护相应的约束,思想倒还是相通的。

leaseschema