上一篇
目前,团队blog和sina 轻博客的发布进度已经完全相同,后续会全部
时间隔了比较久了,因为最近在过年临近,所以都在准备这方面的事情。这里提前祝大家新年快乐。
然后还是回到我们的正题儿吧:)
本章,我们主要来讨论数据的管理和扩容中最重要的一个部分,数据迁移。
数据迁移是数据运维中最为重要的一个部分,在前面的文章中已经提到过,作为有状态的数据节点,在互联网行业的主要追求就是,无限的水平扩展能力,这种水平扩展,主要用于解决两类问题,一类是磁盘空间不足的问题,一类是性能不足的问题。
为了达到这种能力,一般来说主要也就是这样一个思路,尽可能的让数据不动,只通过规则变动的方式来完成扩容,如果这种方式无法满足要求,那么再通过移动数据的方式,来满足其他的一些需求。
下面来进行下分析。
只通过变动规则的方式扩容,举个最简单的例子,就是一组按照时间或自增id的数据。那么最简单的切分方式,就是按照时间或id的范围,将一组数据直接映射到某个具体的机器上,类似
if(gmt> = 2010 and gmt < 2011) returndataNode1; elseif( gmt >= 2011 and gmt < 2012) returndataNode2; elseif(gmt >= 2012 and gmt < 20121223) returndataNode3; …
使用这种方式的好处,显而易见,就是不用动数据,方法简单。
但带来的坏处也明显,就是不移动数据,那么如果一组数据已经成为热点,那么永远也没有机会将热点数据分开到不同的机器里用以减轻热点的损耗了。而,这种情况是非常有可能的,对于一对多的模型,如果按照一去存储数据,那么因为多的数据量的不断扩展,会最终导致单个机器的数据量和io超限。
为了解决上述矛盾,就需要引入数据的迁移的方法了,简单来说,就是按照规则将数据从原来的一组机器上,迁移到新的一组机器上去,这样规则和数据一起变动,就可以有效的解决上面所说的热点问题,尽可能让所有的机器均匀的发挥效用。
思路很简单,但工程实践就复杂多了,下面来描述几种扩容的模式,希望大家能针对这几种场景以及我的分析,对如何解决这个问题有个更深入的认识。
所有有状态的数据,其实都需要有扩容的策略,在这些扩容的模式中,最简单的莫过于对cache节点的扩容了。因为cache本身其实只是一个一致的数据的一个快照,快照的意义就在于:如果你对快照的数据是否正确有异议,可以直接去从数据的源头再查一次写回快照中,即可保证数据的最新。
那么对于缓存数据,一般来说缓存的更新逻辑有两种,一种是写的时候同步更新缓存。一种是先读缓存,缓存没有的时候读数据库读出最新值后更新缓存,一般来说是两种缓存模式组合使用,因为没有副作用。对于这种情况的缓存节点扩容,最简单的做法是,只需要直接改变规则即可。
如,假设原来的数据是按照id% 4进行切分的,那么如果规则换为id% 8.那么有一半的数据就无法被访问到。但没关系,因为业务的实际逻辑是,如果读不到,就读穿缓存去数据库里面取数据再更新回缓存,所以很快,数据会按照新的id% 8 进行填充,扩容就完成了。
当然,实际的扩容比这个要复杂一点,因为,要考虑规则变动后,读穿的次数增多,导致数据库压力上升的问题,所以要尽可能的避免过多的数据读穿缓存,这时候会使用我们在以前的文章中讨论过的一致性hash或虚拟节点hash,使用缓慢更新映射关系的方式,来降低扩容对数据库带来的压力。
以上是最简单的规则和数据一起移动的例子,从上述例子可以分析出,其实规则迁移的最主要问题在于如何保证规则变更时,数据能够在规则发生变动的时候对外部保证数据是最新的读取,在缓存扩容的case中,这个数据保证最新的任务,是由数据库这个组件来完成的。所以缓存扩容是相对最为简单的。
那么,自然的就会产生另外一个疑问:对于数据库,怎么保证这个一致性的读取呢?这也是我们这一章要阐明的最重要的问题。
数据的一致性读,一般来说就只有两种做法。第一种是共享内存指针,说白了就是数据只有一份,但指向该数据的指针可能是多个。还有一种就是数据复制,数据的复制,保证一致性的难度会很大。更多的情况是按照实际的需求,取两种模式的折衷。
对数据节点的扩容而言,其实核心就是数据的复制,既然复制,那么一致性就非常难以保证,于是我们也就只能尽可能巧妙地利用手头的工具,取折衷,用以尽可能的减少不一致的影响。
为了解决这个一致性的问题,我们需要在规则上引入版本,这个概念,主要是用于规定什么时候数据应该以什么规则进行访问。这样,就可以避免数据复制过程中所带来的不一致的问题了。
假设,我们原来的规则,版本号为0,新的规则,版本号为1.那么,开始的时候,客户端所持有的数据的切分规则是版本0,所有数据在老的一组机器上进行读取和写入,不会出现问题。当我给定v0和v1两个版本同时存在时,从客户端就可以意识到,目前的规则是两份并存,数据可能是不一致的,这时候最简单的处理策略是,阻止一切读取和写入,这样数据的不一致就不会发生了(哈哈,因为本身不允许读写了嘛。。),而当规则变为只有v1的时候,那么客户端就可以知道,目前只有一个规则了,按照这个规则,进行数据访问就可以了。
使用版本号,就可以让客户端能够有机会意识到数据在某个时间段可能存在着不一致,应该加以针对性的处理,这样就可以规避数据读写的不一致的问题了。
解决了不一致的问题,下面紧接着要解决的问题有两个:
我如何知道应该让哪些数据移动到哪台机器上?
我如何尽可能的减小规则并存时的停写的数据范围?
针对这个问题,外面开源的社区,最常用的解决方法是一致性hash。
在一致性hash中,在某个地方加一组机器,可以很容易的预测应该将哪个节点的数据移动到新的节点上。同时,又可以预测,哪些节点不会受到影响,哪些不受到影响的节点,完全可以开放读取,而受到影响的节点,则阻止访问即可。
如上图中,
node4和node2中间,加了一个node5,那么很容易的可以知道,只需要将node4中的一部分数据,写入新的node5即可。而node2,node1,node3的数据不受到影响,可以继续允许访问。
这样就可以比较成功的解决上面提到的两个问题了。
但从http://qing.weibo.com/1765738567/693f084733000963.html这篇文章的讨论中,我们也很容易可以看到,一致性hash也有他自己的问题。
于是,自然就有人要问,有没有其他的做法呢?
自然是有啦,下面来介绍一下淘宝TDDL在这方面的工程实践吧。以下是纯粹干货,目前暂时没见过业内使用类似方式,这种模式在淘宝也经历了较多次的自动扩容考验,能够满足我们的需求,相信也一定能满足您的需求,因为它什么都没做,也什么都做了:).
首先是需求描述:分析淘宝的需求,简单概括就是一句话,业务方的规则需求,复杂到无以复加,绝非简单一致性hash或简单btree可以满足,为了不同的业务需求,会有种类很多的切分规则。
需求分析:
需求分析其实就是挖掘需求的含义,找到哪些是真实的需求,哪些不是,将不是的砍掉,看看剩下的能不能满足的过程:)
扩容系统的技术特点:
规则系统要自定义,因为这是业务核心,只有业务知道他们的数据怎么分配会获得比较均匀的访问模型。
扩容“不是”常态,一般来说扩容的周期是3个月~6个月,甚至更长。如果一个业务,每6天要扩容一次,那采购人员绝对会抄家伙找他们team干架去了
扩容本身不是不能做,但难度较大,一般来说需要几个人一起参与,最少有数据运维人员,系统运维人员以及开发人员参与,一帮苦13程序员夜里3点多闹钟叫起来,睡眼朦胧的进行机械的操作。难度可想而知。
基于这些技术特点,可以作如下分析
业务的变化要求数据扩容的规则要尽可能的自定义,可以有些预先定义好的规则模型,但不能强制要求业务必须走定义好的模型。
自动扩容,意义不大,如果只是让业务人员根据数据点个确定,是最容易被接受的扩容模式
要尽可能的避免扩容本身对业务本身带来的影响,同时要尽可能减轻开发人员的熬夜次数。
所以我们设计了如下的系统,他满足以下特性
规则完全自定义,你可以随便写任何的ifelse等脚本代码。
只对扩容需求提供决策支持和方案生成,但决策由人进行。
除了决策