淘宝中间件——tair(下)

2021-01-14 15:36:28 浏览数 (1)

一致性和可靠性

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

version

Tair中的每个数据都包含版本号,版本号在每次更新后都会递增。这个特性可以帮助防止数据的并发更新导致的问题(CAS)。

如何获取到当前key的version?

get

get接口返回的是DataEntry对象,该对象中包含get到的数据的版本号,可以通过getVersion()接口获得该版本号。

put

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

很多情况下,更新数据是先get,然后修改get回来的数据,再put回系统。如果有多个客户端get到同一份数据,都对其修改并保存,那么先保存的修改就会被后到达的修改覆盖,从而导致数据一致性问题。

比如系统中有一个值”1”, 现在A和B客户端同时都取到了这个值。之后A和B客户端都想改动这个值,假设A要改成1a,B要改成1b,如果不加控制的话,无论A和B谁先更新成功,它的更新都会被后到的更新覆盖。Tair引入的version机制避免了这样的问题。刚刚的例子中,假设A和B同时取到数据,当时版本号是10,A先更新,更新成功后,值为1a,版本为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的队列。

0 人点赞