淘宝分布式NOSQL框架:Tair

Tair 分布式K-V存储方案

 

tair 是淘宝的一个开源项目,它是一个分布式的key/value结构数据的解决方案。前端

做为一个分布式系统,Tair由一个中心控制节点(config server)和一系列的服务节点(data server)组成,java

  • config server 负责管理全部的data server,并维护data server的状态信息;为了保证高可用(High Available),config server可经过hearbeat 以一主一备形式提供服务;
  • data server 对外提供各类数据服务,并以心跳的形式将自身情况汇报给config server;全部的 data server 地位都是等价的。

 

 

 

tair集群的基本概念:web

  • configID,惟一标识一个tair集群,每一个集群都有一个对应的configID,在当前的大部分应用状况下configID是存放在diamond中的,对应了该集群的configserver地址和groupname。业务在初始化tair client的时候须要配置此ConfigID。
  • namespace,又称area, 是tair中分配给应用的一个内存或者持久化存储区域, 能够认为应用的数据存在本身的namespace中。 同一集群(同一个configID)中namespace是惟一的。经过引入namespace,咱们能够支持不一样的应用在同集群中使用相同的key来存放数据,也就是key相同,但内容不会冲突。一个namespace下是若是存放相同的key,那么内容会受到影响,在简单K/V形式下会被覆盖,rdb等带有数据结构的存储引擎内容会根据不一样的接口发生不一样的变化。
  • quota配额,对应了每一个namespace储存区的大小限制,超过配额后数据将面临最近最少使用(LRU)的淘汰。持久化引擎(ldb)自己没有配额,ldb因为自带了mdb cache,因此也能够设置cache的配额。超过配额后,在内置的mdb内部进行淘汰。
  • expireTime,数据的过时时间。当超过过时时间以后,数据将对应用不可见,不一样的存储引擎有不一样的策略清理掉过时的数据。

 


 

存储引擎

tair 分为持久化和非持久化两种使用方式:redis

  • 非持久化的 tair 能够当作是一个分布式缓存;
  • 持久化的 tair 将数据存放于磁盘中,为了解决磁盘损坏致使数据丢失,tair 能够配置数据的备份数目。tair 自动将一份数据的不一样备份放到不一样的主机上,当有主机发生异常,没法正常提供服务的时候,其他的备份会继续提供服务。


Tair的存储引擎有一个抽象层,只要知足存储引擎须要的接口,即可以很方便的替换Tair底层的存储引擎。好比你能够很方便的将bdb、tc、redis、leveldb甚至MySQL做为Tair的存储引擎,而同时使用Tair的分布方式、同步等特性。算法

Tair主要有下面三种存储引擎:数组

  • mdb,定位于cache缓存,相似于memcache。支持k/v存取和prefix操做;
  • rdb,定位于cache缓存,采用了redis的内存存储结构。支持k/v,list,hash,set,sortedset等数据结构;
  • ldb,定位于高性能存储,采用了levelDB做为引擎,并可选择内嵌mdb cache加速,这种状况下cache与持久化存储的数据一致性由tair进行维护。支持k/v,prefix等数据结构。从此将支持list,hash,set,sortedset等redis支持的数据结构。

 

 

 

MDB流程

 

 

RDB流程

 

 

LDB流程

 

 

fastdump

大数据量导入:数据预排序,按桶分memtable。缓存

 

 

 


 

分布式策略

tair 的分布采用的是一致性哈希算法,对于全部的key,分到Q个桶中,桶是负载均衡和数据迁移的基本单位。config server 根据必定的策略把每一个桶指派到不一样的data server上,由于数据按照key作hash算法,因此能够认为每一个桶中的数据基本是平衡的,保证了桶分布的均衡性, 就保证了数据分布的均衡性。安全

具体说,首先计算Hash(key),获得key所对应的bucket,而后再去config server查找该bucket对应的data server,再与相应的data server进行通讯。也就是说,config server维护了一张由bucket映射到data server的对照表,好比:服务器

bucket   data server
0 192.168.10.1 1 192.168.10.2 2 192.168.10.1 3 192.168.10.2 4 192.168.10.1 5 192.168.10.2

这里共6个bucket,由两台机器负责,每台机器负责3个bucket。客户端将key hash后,对6取模,找到负责的数据节点,而后和其直接通讯。表的大小(行数)一般会远大于集群的节点数,这和consistent hash中的虚拟节点很类似。网络

假设咱们加入了一台新的机器——192.168.10.3,Tair会自动调整对照表,将部分bucket交由新的节点负责,好比新的表极可能相似下表:

0    192.168.10.1
1    192.168.10.2
2    192.168.10.1
3    192.168.10.2
4    192.168.10.3
5    192.168.10.3

在老的表中,每一个节点负责3个桶,当扩容后,每一个节点将负责2个桶,数据被均衡的分布到全部节点上。

若是有多个备份,那么对照表将包含多列,好比备份是为3,则表有4列,后面的3列都是数据存储的节点。

为了加强数据的安全性,Tair支持配置数据的备份数(COPY_COUNT)。好比你能够配置备份数为3,则每一个bucket都会写在不一样的3台机器上。当数据写入一个节点(一般咱们称其为主节点)后,主节点会根据对照表自动将数据写入到其余备份节点,整个过程对用户是透明的。

当有新节点加入或者有节点不可用时,config server会根据当前可用的节点,从新build一张对照表。数据节点同步到新的对照表时,会自动将在新表中不禁本身负责的数据迁移到新的目标节点。迁移完成后,客户端能够从config server同步到新的对照表,完成扩容或者容灾过程。整个过程对用户是透明的,服务不中断。

为了更进一步的提升数据的安全性,Tair的config server在build对照表的时候,能够配置考虑机房和机架信息。好比你配置备份数为3,集群的节点分布在两个不一样的机房A和B,则Tair会确保每一个机房至少有一份数据。当A机房包含两份数据时,Tair会确保这两份数据会分布在不一样机架的节点上。这能够防止整个机房发生事故和某个机架发生故障的状况。这里提到的特性须要节点物理分布的支持,当前是经过可配置的IP掩码来区别不一样机房和机架的节点。

 

Tair 提供了两种生成对照表的策略:

  1. 负载均衡优先,config server会尽可能的把桶均匀的分布到各个data server上,所谓尽可能是指在不违背下面的原则的条件下尽可能负载均衡:每一个桶必须有COPY_COUNT份数据; 一个桶的各份数据不能在同一台主机上;
  2. 位置安全优先,通常咱们经过控制 _pos_mask(Tair的一个配置项) 来使得不一样的机房具备不一样的位置信息,一个桶的各份数据不能都位于相同的一个位置(不在同一个机房)。

位置优先策略还有一个问题,假如只有两个机房,机房1中有100台data server,机房2中只有1台data server。这个时候,机房2中data server的压力必然会很是大,因而这里产生了一个控制参数 _build_diff_ratio(参见安装部署文档),当机房差别比率大于这个配置值时,config server也再也不build新表,机房差别比率是如何计出来的呢?首先找到机器最多的机房,不妨设使RA,data server数量是SA,那么其他的data server的数量记作SB,则机房差别比率=|SA – SB|/SA,由于通常咱们线上系统配置的COPY_COUNT=3,在这个状况下,不妨设只有两个机房RA和RB,那么两个机房什么样的data server数量是均衡的范围呢? 当差别比率小于 0.5的时候是能够作到各台data server负载都彻底均衡的。这里有一点要注意,假设RA机房有机器6台,RB有机器3台,那么差别比率 = 6 – 3 / 6 = 0.5,这个时候若是进行扩容,在机房A增长一台data server,扩容后的差别比率 = 7 – 3 / 7 = 0.57,也就是说,只在机器数多的机房增长data server会扩大差别比率。若是咱们的_build_diff_ratio配置值是0.5,那么进行这种扩容后,config server会拒绝再继续build新表。

 

 

一致性和可靠性

分布式系统中的可靠性和一致性是没法同时保证的,由于咱们必须容许网络错误的发生。tair 采用复制技术来提升可靠性,而且为了提升效率作了一些优化。事实上在没有错误发生的时候,tair 提供的是一种强一致性,可是在有data server发生故障的时候,客户有可能在必定时间窗口内读不到最新的数据,甚至发生最新数据丢失的状况。

  

version

Tair中的每一个数据都包含版本号,版本号在每次更新后都会递增。这个特性能够帮助防止数据的并发更新致使的问题。

如何获取到当前key的version?

get接口返回的是DataEntry对象,该对象中包含get到的数据的版本号,能够经过getVersion()接口得到该版本号。

在put时,将该版本号做为put的参数便可。 若是不考虑版本问题,则可设置version参数为0,系统将强行覆盖数据,即便版本不一致。

 

不少状况下,更新数据是先get,而后修改get回来的数据,再put回系统。若是有多个客户端get到同一份数据,都对其修改并保存,那么先保存的修改就会被后到达的修改覆盖,从而致使数据一致性问题,在大部分状况下应用可以接受,但在少许特殊状况下,这个是咱们不但愿发生的。

好比系统中有一个值”1”, 如今A和B客户端同时都取到了这个值。以后A和B客户端都想改动这个值,假设A要改为12,B要改为13,若是不加控制的话,不管A和B谁先更新成功,它的更新都会被后到的更新覆盖。Tair引入的version机制避免了这样的问题。刚刚的例子中,假设A和B同时取到数据,当时版本号是10,A先更新,更新成功后,值为12,版本为11。当B更新的时候,因为其基于的版本号是10,此时服务器会拒绝更新,返回version error,从而避免A的更新被覆盖。B能够选择get新版本的value,而后在其基础上修改,也能够选择强行更新。

 

Version改变的逻辑以下:

  1. 若是put新数据且没有设置版本号,会自动将版本设置成1;
  2. 若是put是更新老数据且没有版本号,或者put传来的参数版本与当前版本一致,版本号自增1;
  3. 若是put是更新老数据且传来的参数版本与当前版本不一致,更新失败,返回VersionError;
  4. put时传入的version参数为0,则强制更新成功,版本号自增1。

 

version具体使用案例,若是应用有10个client会对key进行并发put,那么操做过程以下: 

  1. get key,若是成功,则进入步骤2;若是数据不存在,则进入步骤3;
  2. 在调用put的时候将get key返回的verison从新传入put接口,服务端根据version是否匹配来返回client是否put成功;
  3. get key数据不存在,则新put数据。此时传入的version必须不是0和1,其余的值均可以(例如1000,要保证全部client是一套逻辑)。由于传入0,tair会认为强制覆盖;而传入1,第一个client写入会成功,可是新写入时服务端的version以0开始计数啊,因此此时version也是1,因此下一个到来的client写入也会成功,这样形成了冲突

 

version分布式锁
Tair中存在该key,则认为该key所表明的锁已被lock;不存在该key,在未加锁。操做过程和上面类似。业务方能够在put的时候增长expire,已避免该锁被长期锁住。
固然业务方在选择这种策略的状况下须要考虑并处理Tair宕机带来的锁丢失的状况。

 

 

 


 

config server

client 和 config server的交互主要是为了获取数据分布的对照表,当client启动时获取到对照表后,会cache这张表,而后经过查这张表决定数据存储的节点,因此请求不须要和config server交互,这使得Tair对外的服务不依赖configserver,因此它不是传统意义上的中心节点,也并不会成为集群的瓶颈

config server维护的对照表有一个版本号,每次新生成表,该版本号都会增长。当有data server状态发生变化(好比新增节点或者有节点不可用了)时,configserver会根据当前可用的节点从新生成对照表,并经过数据节点的心跳,将新表同步给data server。当client请求data server时,后者每次都会将本身的对照表的版本号放入response中返回给客client,client接收到response后,会将data server返回的版本号和本身的版本号比较,若是不相同,则主动和config server通讯,请求新的对照表。

这使得在正常的状况下,client不须要和configserver通讯,即便config server不可用了,也不会对整个集群的服务形成大的影响。有了config server,client不须要配置data server列表,也不须要处理节点的的状态变化,这使得Tair对最终用户来讲使用和配置都很简单。

 

容灾

当有某台data server故障不可用的时候,config server会发现这个状况,config server负责从新计算一张新的桶在data server上的分布表,将原来由故障机器服务的桶的访问从新指派到其它有备份的data server中。这个时候,可能会发生数据的迁移,好比原来由data server A负责的桶,在新表中须要由 B负责,而B上并无该桶的数据,那么就将数据迁移到B上来。同时,config server会发现哪些桶的备份数目减小了,而后根据负载状况在负载较低的data server上增长这些桶的备份。

 

 

扩容

当系统增长data server的时候,config server根据负载,协调data server将他们控制的部分桶迁移到新的data server上,迁移完成后调整路由。

 

注意:

无论是发生故障仍是扩容,每次路由的变动,config server都会将新的配置信息推给data server。在client访问data server的时候,会发送client缓存的路由表的版本号,若是data server发现client的版本号过旧,则会通知client去config server取一次新的路由表。若是client访问某台data server 发生了不可达的状况(该 data server可能宕机了),客户端会主动去config server取新的路由表。

 

迁移

当发生迁移的时候,假设data server A 要把 桶 3,4,5 迁移给data server B。由于迁移完成前,client的路由表没有变化,所以对 3, 4, 5 的访问请求都会路由到A。如今假设 3还没迁移,4 正在迁移中,5已经迁移完成,那么:

  • 若是是对3的访问,则没什么特别,跟之前同样;
  • 若是是对5的访问,则A会把该请求转发给B,而且将B的返回结果返回给client;
  • 若是是对4的访问,在A处理,同时若是是对4的修改操做,会记录修改log,桶4迁移完成的时候,还要把log发送到B,在B上应用这些log,最终A B上对于桶4来讲,数据彻底一致才是真正的迁移完成;

 

 

 

 


 

Tair更多功能

客户端

tair 的server端是C++写的,由于server和客户端之间使用socket通讯,理论上只要能够实现socket操做的语言均可以直接实现成tair客户端。目前实际提供的客户端有java 和 C++, 客户端只须要知道config server的位置信息就能够享受tair集群提供的服务了。

 

 

plugin支持

Tair还内置了一个插件容器,能够支持热插拔插件。

插件由config server配置,config server会将插件配置同步给各个数据节点,数据节点会负责加载/卸载相应的插件。

插件分为request和response两类,能够分别在request和response时执行相应的操做,好比在put前检查用户的quota信息等。

插件容器也让Tair在功能方便具备更好的灵活性。

 

 

原子计数支持

Tair从服务器端支持原子的计数器操做,这使得Tair成为一个简单易用的分布式计数器。

 

 

item支持

Tair还支持将value视为一个item数组,对value中的部分item进行操做。好比有一个key的value为 [1,2,3,4,5],咱们能够只获取前两个item,返回[1,2],也能够删除第一个item。还支持将数据删除,并返回被删除的数据,经过这个接口能够实现一个原子的分布式FIFO的队列。

 

 


客户端 

目前淘宝开源的客户端有C++和Java两个版本,不过tair若是做为存储层,前端确定还需部署Nginx这样的web服务器,以Nginx为例,淘宝彷佛尚未开源其tair模块,春哥(agentzh)也没有公布tair的lua插件,若是想在Nginx里面访问tair,目前彷佛尚未什么办法了,除非本身去开发一个模块。

 

 

 

 

 

 

参考文档:

http://csrd.aliapp.com/?cat=8

http://code.taobao.org/p/tair/wiki/index/

相关文章
相关标签/搜索