1. Tair总述

1.1 系统架构

一个Tair集群主要包括3个必选模块:configserver、dataserver和client,一个可选模块:invalidserver。通常情况下,一个集群中包含2台configserver及多台dataServer。两台configserver互为主备并通过维护和dataserver之间的心跳获知集群中存活可用的dataserver,构建数据在集群中的分布信息(对照表)。dataserver负责数据的存储,并按照configserver的指示完成数据的复制和迁移工作。client在启动的时候,从configserver获取数据分布信息,根据数据分布信息和相应的dataserver交互完成用户的请求。invalidserver主要负责对等集群的删除和隐藏操作,保证对等集群的数据一致。

从架构上看,configserver的角色类似于传统应用系统的中心节点,整个集群服务依赖于configserver的正常工作。但实际上相对来说,tair的configserver是非常轻量级的,当正在工作的服务器宕机的时候另外一台会在秒级别时间内自动接管。而且,如果出现两台服务器同时宕机的最恶劣情况,只要应用服务器没有新的变化, tair依然服务正常。而有了configserver这个中心节点,带来的好处就是应用在使用的时候只需要配置configserver的地址(现在可以直接配置Diamond key),而不需要知道内部节点的情况。

1.1.1 ConfigServer的功能

1) 通过维护和dataserver心跳来获知集群中存活节点的信息
2) 根据存活节点的信息来构建数据在集群中的分布表。
3) 提供数据分布表的查询服务。
4) 调度dataserver之间的数据迁移、复制。

1.1.2 DataServer的功能

1) 提供存储引擎
2) 接受client的put/get/remove等操作
3) 执行数据迁移,复制等
4) 插件:在接受请求的时候处理一些自定义功能
5) 访问统计

1.1.3 InvalidServer的功能

1) 接收来自client的invalid/hide等请求后,对属于同一组的集群(双机房独立集群部署方式)做delete/hide操作,保证同一组集群的一致。
2) 集群断网之后的,脏数据清理。
3) 访问统计。

1.1.4 client的功能

1) 在应用端提供访问Tair集群的接口。
2) 更新并缓存数据分布表和invalidserver地址等。
3) LocalCache,避免过热数据访问影响tair集群服务。
4) 流控

1.2 存储引擎与应用场景

Tair经过这两年的发展演进,除了应用于cache缓存外,在存储(持久化)上支持的应用需求也越来越广泛。现在主要有mdb,rdb,ldb三种存储引擎。

1.2.1 mdb

定位于cache缓存,类似于memcache。
支持k/v存取和prefix操作

1.2.1.1 mdb的应用场景
在实际业务中,大部分当缓存用(后端有DB之类的数据源)。
也可用做大访问少量临时数据的存储(例如session登录,防攻击统计等)。
集团内绝对多数cache服务都是采用的tair mdb。

1.2.2 rdb

定位于cache缓存,采用了redis的内存存储结构。
支持k/v,list,hash,set,sortedset等数据结构。

1.2.2.1 rdb的应用场景
适用于需要高速访问某些数据结构的应用,例如SNS中常见的的粉丝存储就可以采用set等结构;或者存储一个商品的多个属性(hashmap);高效的消息队列(list)等。现在有30个左右的应用在使用rdb服务。

1.2.3 ldb

定位于高性能存储,并可选择内嵌mdb cache加速,这种情况下cache与持久化存储的数据一致性由tair进行维护。 支持k/v,prefix等数据结构。今后将支持list,hash,set,sortedset等redis支持的数据结构。

1.2.3.1 ldb的应用场景
存储,里面可以细分如下场景:
1) 持续大数据量的存入读取,类似淘宝交易快照。
2) 高频度的更新读取,例如计数器,库存等。
3) 离线大批量数据导入后做查询。参见fastdump
也可以用作cache:
数据量大,响应时间敏感度不高的cache需求可以采用。例如天猫实时推荐。

1.3 基本概念

1.3.1 configID

唯一标识一个tair集群,每个集群都有一个对应的configID,在当前的大部分应用情况下configID是存放在diamond中的,对应了该集群的configserver地址和groupname。业务在初始化tairclient的时候需要配置此ConfigID。

1.3.2 namespace

又称area, 是tair中分配给应用的一个内存或者持久化存储区域, 可以认为应用的数据存在自己的namespace中。 
同一集群(同一个configID)中namespace是唯一的。
通过引入namespace,我们可以支持不同的应用在同集群中使用相同的key来存放数据,也就是key相同,但内容不会冲突。一个namespace下是如果存放相同的key,那么内容会受到影响,在简单K/V形式下会被覆盖,rdb等带有数据结构的存储引擎内容会根据不同的接口发生不同的变化。

1.3.3 quota配额

对应了每个namespace储存区的大小限制,超过配额后数据将面临最近最少使用(LRU)的淘汰。
持久化引擎(ldb)本身没有配额,ldb由于自带了mdb cache,所以也可以设置cache的配额。超过配额后,在内置的mdb内部进行淘汰。

1.3.3.1 配额是怎样计算的
配额大小直接影响数据的命中率和资源利用效率,业务方需要给出一个合适的值,通常的计算方法是评估在保证一定命中率情况下所需要的记录条数,这样配额大小即为: 记录条数 * 平均单条记录大小。

1.3.3.2 管理员如何配置配额
单份数据情况下,业务提供的配额就是需要配置在Tair系统中的配额。但对于多备份,在系统中实际计算的配额为: 业务配额 * 备份数

1.3.4 expireTime:过期时间

expiredTime 是指数据的过期时间,当超过过期时间之后,数据将对应用不可见,这个设置同样影响到应用的命中率和资源利用率。不同的存储引擎有不同的策略清理掉过期的数据。调用接口时,expiredTime单位是秒,可以是相对时间(比如:30s),也可以是绝对时间(比如:当天23时,转换成距1970-1-1 00:00:00的秒数)。 小于0,不更改之前的过期时间
如果不传或者传入0,则表示数据永不过期;
大于0小于当前时间戳是相对时间过期;
大于当前时间戳是绝对时间过期;

1.3.5 version

Tair中存储的每个数据都有版本号,版本号在每次更新后都会递增,相应的,在Tair put接口中也有此version参数,这个参数是为了解决并发更新同一个数据而设置的,类似于乐观锁。
很多情况下,更新数据是先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,然后在其基础上修改,也可以选择强行更新。

1.3.5.1 如何获取到当前key的version
get接口返回的是DataEntry对象,该对象中包含get到的数据的版本号,可以通过getVersion()接口获得该版本号。在put时,将该版本号作为put的参数即可。 如果不考虑版本问题,则可设置version参数为0,系统将强行覆盖数据,即使版本不一致。

1.3.5.2 version是如何改变的
Version改变的逻辑如下:
1) 如果put新数据且没有设置版本号,会自动将版本设置成1。
2) 如果put是更新老数据且没有版本号,或者put传来的参数版本与当前版本一致,版本号自增1。
3) 如果put是更新老数据且传来的参数版本与当前版本不一致,更新失败,返回VersionError。
4) put时传入的version参数为0,则强制更新成功,版本号自增1。

1.3.5.3 version返回不一致的时候,该如何处理
如果更新所基于的version和系统中当前的版本不一致,则服务器会返回ResultCode.VERERROR。 这时你可以重新get数据,然后在新版本的数据上修改;或者设置version为0重新请求,以达到强制更新的效果,应用可以根据自身对数据一致性的要求在这两种策略间进行选择。

1.3.5.4 version具体使用案例
如果应用有10个client会对key进行并发put,那么操作过程如下: 
1) get key。如果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写入也会成功,这样造成了冲突

1.3.5.5 version分布式锁
Tair中存在该key,则认为该key所代表的锁已被lock;不存在该key,在未加锁。操作过程和上面相似。业务方可以在put的时候增加expire,已避免该锁被长期锁住。
当然业务方在选择这种策略的情况下需要考虑并处理Tair宕机带来的锁丢失的情况。

1.3.5.6 什么情况下需要使用version
业务对数据一致性有较高的要求,并且访问并发高,那么通过version可以避免数据的意外结果。
如果不关心并发,那么建议不传入version或者直接传0。

1.4 集群部署方式

Tair通过多种集群部署方式,来满足各类应用的容灾需求。
下面所述的双机房可以扩展到多机房,现阶段基本还是采用的双机房。
现总共有4种方式:
mdb存储引擎适用于双机房单集群单份,双机房独立集群,双机房单集群双份。
rdb存储引擎适用于双机房单集群单份。
ldb存储引擎适用于双机房主备集群,双机房单集群单份。

1.4.1 双机房单集群单份

双机房单集群单备份数是指,该Tair集群部署在两个机房中(也就是该Tair集群的机器分别在两个机房), 数据存储份数为1, 该类型集群部署示意图如下所示。数据服务器(Dataserver)分布在两个机房中,他们都属于同一集群。

使用场景:
1) 后端有无数据源都可。
2) 后端有数据源,且更新比例很高的场景。
优点:
1) 服务器存在于双机房,任一机房宕机保持可用。
2) 单份数据,无论应用在哪个机房,看到的都是同一个数据。
缺点:
1) 应用服务器会跨机房访问。如上图,并假设应用服务器在cm3和cm4,那么cm3的应用服务器也可能调用到cm4的tair机器,cm4的亦然。
2) 当一边机房出现故障时,tair中的数据会失效一半(一半这个数值是按两边机房tair机器数相同估计的,如果不相同,则按对应比例估算)
该部署方式,应用在删除数据时,只需要调用delete即可,无需调用invalid。当然,调用invalid也可,这种方式下会直接退化到delete。

1.4.2 双机房独立集群

双机房独立集群是指,在两个机房中同时部署2个独立的Tair集群,这两个集群没有直接关系。下图是一个典型的双机房独立集部署示意图,可以看到,cm3和cm4各有一个完整的tair集群(2个configserver+多个dataserver)。图中还多了一个invalidserver的角色, invalidserver接收客户端的invalid或者hide请求后,会对各机房内的集群进行delete或者hide操作,以此保障Tair中的数据和后端数据源保持一致的。

适用场景:
1) 后端必须要有数据源,否则则退化成单机房集群,Tair集群本身不做同步。
2) 读写比不能过小,不然可能会带来Tair命中率降低。例如某个key,在数据库中被频繁更新,那么此时应用必须调用invalid来确保Tair和DB的一致性。此时应用读Tair一直会不命中,导致整体命中率低,可能造成DB压力比较大。 如果依然有疑问的话,请联系 tair答疑。
优点:
1) 每个机房拥有独立Tair集群,应用在哪个机房就访问相同机房的Tair集群,不会出现跨机房调用和流量。
2) 单边机房故障,不会影响业务访问tair命中率。
缺点:
1) 后端必须要有数据源,也就是这种部署方式下,Tair必然是当作传统意义上的cache存在的。因为Tair mdb集群之间本身不会做数据同步,多集群间一致性保证依赖于后端数据源,如DB。
2) 当后端数据源数据发生更新后,业务不能直接把数据put到Tair,而是先需要调用invalid接口来失效这些对等集群中的数据(来保持各Tair集群的数据和后端数据源的一致性)。之后业务可以把数据put到当前Tair集群(注意:只会put到本机房的Tair集群,不会put到对端集群)或者在读Tair时发生not exist的时候从后端数据源取来放入Tair。

1.4.3 双机房单集群双份

双机房单集群双份,是指一个Tair集群部署在2个机房中,数据保存2份,并且同一数据的2个备份不会放在同一个数据服务器上。根据数据分布策略的不同,还可以将同一份数据的不同备份分布到不同的机房上。该类型的集群部署方式与双机房单集群单份数据的部署方式一样。其不同之处,数据保存份数不一样。该类型集群部署方式示意图如下图所示,数据服务器分别部署在两个不同的机房里,所有的数据服务器都被相同的配置服务器管理,在逻辑上,他们构成一个独立的集群。

现只有tbsession集群使用了这种部署方式。
适用场景:
后端无数据源,临时数据的存放,非cache。
cache类应用推荐使用双机房独立集群和双机房单集群单份部署方式。
优点:
1) 数据存放两份,数据安全性有一定保障。但由于存储引擎是mdb,数据存放在内存中,无法绝对保证数据不丢失。
2) 当一边机房故障时,另外一边机房依然可以服务,并且数据不丢失。
缺点:
1) 如果机房间网络出现异常情况,依然有较小几率丢失数据。

1.4.4 双机房主备集群

这种部署方式中,存在一个主集群和一个备份集群,分别在两个机房中。如下图所示,不妨假设CM3中部署的是主集群,CM4中部署的是备份集群。那么,在正常情况下,用户只使用主集群,读写数据都与主集群交互。主备集群会自动同步数据(不需要业务去更新两边),保证两个机房数据的最终一致性。当一个机房发生故障后,备集群会自动切换成主集群,提供服务,保证系统可用性。

适用场景:
该方式只在ldb存储引擎中存在,也就是业务将Tair当作最终存储使用。我们会在当前主集群存两份数据,并由Tair异步将数据更新到备集群,确保数据安全和服务可用。
优点:
1) 数据安全和服务可用性高。
2) 用户调用方便,无需考虑多集群间数据一致性的问题。