codis数据迁移期间的一致性
2014-12-13
最近在写我们公司的分布式缓存系统。设计上主要借鉴codis,不过把proxy层的工作移到客户端库实现,东拼西凑了一些其它开源代码。
codis的slot状态是有一个pre_migrate
的。开始是没注意这些细节的,自己写的时候遇到了问题,回头再读它的设计,才理解这codis的保存迁移期间的数据一致性上,做的很精妙。
先说说迁移期间的一致性问题吧。假设有一个中心化的集群状态(其实是否中心化无所谓,去中心化的协议也有一个达成共识的集群状态)。客户端是需要缓存一个集群状态到本地。这个时间我们去迁移数据了,集群状态信息也变了。但问题是客户端缓存的集群状态信息不是实时更新的,可能等服务端开始迁移时,客户端使用的还是老的集群状态。
比如某份数据正从节点A迁移到节点B,但是客户端并不知道这件事。那么客户端对集群的写操作就会引起问题。
aerospike对这种情况的处理,每个服务器节点是实时知道集群状态的。于是它做了一些智能的工作,将错误的请求proxy到正确的节点上。数据迁移过程中,如果客户端向A结点请求数据,A结点正在迁移中,这份数据已经到B节点了。那么A会将请求proxy到B,将B发来的数据返回给客户端。客户端是不需要关心集群是否迁移状态的。
codis的实现中redis纯粹的作为存储结点,并不处理而且也不知道集群状态信息。集群状态信息是由zookeeper维护的。这样实现的好处是简单,不用对redis太多修改。处理迁移期间一致性,可以看作是一个两阶段提交。
第一阶段并不会将zookeeper中的状态修改为migrate。如果直接这么做,假设客户端并没同步到最新状态,它还是在online状态执行操作,整个系统就不能保证将一个key从一个redis节点迁移到另一个redis节点是原子性的了。所以第一阶段会将zookeeper状态设置为pre_migrate
,迁移程序会等待所有proxy的回应,通知它已经知道集群进入到待迁移状态了。
第二阶段,如果迁移程序能够确认所有的proxy进入到了pre_migrate
状态,即收到了所有proxy的回应,那么它就可以修改集群状态为migrate并再次通知所有的proxy这个改动。如果没能收到所有proxy的回应,是不能够进入到migrate状态的,那么要放弃迁移操作。
对于proxy不响应的情况,可以将它标记为offline,迁移程序退出,由管理员来处理异常。
为了保证一致性,可以设定在pre_migrate
状态时proxy是不能写操作的。直到切换到migrate才能再次写。那么这个两阶段提交就有一个锁的粒度问题。幸好,codis的迁移是按slot一个一个进行的。锁的粒度可以比较细,假设分了1024个slot,在pre_migrate
期间受影响不能写入的只有一个slot而已。保证对每一个key的迁移是原子性的。
proxy程序在migrate期间的操作,会先执行slotmigratekey命令确保数据在迁移到了新的节点中,然后再到新节点执行正常的读写操作。这样做的另一个用意是处理迁移程序挂掉的情况,迁移过程也是可中断的,因为即使迁移程序不去向redis发迁移命令,proxy也会慢慢地将迁移操作执行完的。
如果一个proxy节点在pre_migrate
状态丢失了迁移程序发来的migrate通知,会怎么样呢?这个节点就没法写了。也就是可用性的降低,不过系统的一致性是保证的。
唉,自己去写这些东西,才理解了别人做的时候考虑到的方方面面。各种异常情况的处理真是蛋疼。分布式环境下,任何没有想到并处理好的情况,都是一定会发生的。