KEY/VALUE-tair

Tair是什么

      tair 是淘宝自己开发的一个分布式 key/value 存储引擎. tair 分为持久化和非持久化两种使用方式.

     非持久化的 tair 可以看成是一个分布式缓存。

    持久化的 tair 将数据存放于磁盘中,tair 可以配置数据的备份数目, tair 自动将一份数据的不同备份放到不同的主机上, 当有主机发生异常, 无法正常提供服务的时候, 其于的备份会继续提供服务.

tair的总体架构

tair 作为一个分布式系统, 是由一个中心控制节点和一系列的服务节点组成。 我们称中心控制节点为config server. 服务节点是data server。

 config server 负责管理所有的data server, 维护data server的状态信息。config server是控制点, 而且是单点, 目前采用一主一备的形式来保证其可靠性。

 data server 对外提供各种数据服务, 并以心跳的形式将自身状况汇报给config server。所有的 data server 地位都是等价的.

 tair的基本概念

config server

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

从Tair的整体架构图上看,configserver很类似传统分布式集群中的中心节点。整个集群服务都依赖于configserver的正常工作。

但Tair的configserver却是一个轻量级的中心节点,在大部分时候,configserver不可用对集群的服务是不造成影响的。

Tair用户和configserver的交互主要是为了获取数据分布的对照表,当client获取到对照表后,会cache这张表,然后通过查这张表决定数据存储的节点,所以请求不需要和configserver交互,这使得Tair对外的服务不依赖configserver,所以它不是传统意义上的中心节点。

configserver维护的对照表有一个版本号,每次新生成表,该版本号都会增加。当有数据节点状态发生变化(比如新增节点或者有节点不可用了)时,configserver会根据当前可用的节点重新生成对照表,并通过数据节点的心跳,将新表同步给数据节点。

当客户端请求数据节点时,数据节点每次都会将自己的对照表的版本号放入response中返回给客户端,客户端接收到response后,会将数据节点返回的版本号和自己的版本号比较,如果不相同,则主动和configserver通信,请求新的对照表。

所以客户端也不需要和configserver保持心跳,以便及时地更新对照表。这使得在正常的情况下,客户端不需要和configserver通信,即使configserver不可用了,也不会对整个集群的服务造成大的影响。

仅有当configserver不可用,此时有客户端需要初始化,那么客户端将取不到对照表信息,这将使得客户端无法正常工作。

data server

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

invalid Server

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

client

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

table(对照表)

      对照表主要是解决负载均衡的,对照表的行数是一个固定值,这个固定值应该远大于一个集群的物理机器数,由于对照表是需要和每个使用Tair的客户端同步的,所以不能太大,不然同步将带来较大的开销。我们在生产环境中的行数一般为1023。

configId

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

namespace

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

tair的负载均衡

tair的分布采用的是一致性哈希算法, 对于所有的key, 分到Q个桶中, 桶是负载均衡和数据迁移的基本单位。

config server 根据一定的策略把每个桶指派到不同的data server上. 因为数据按照key做hash算法, 所以可以认为每个桶中的数据基本是平衡的。保证了桶分布的均衡性, 就保证了数据分布的均衡性.

 tair的数据一致性

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

 data server数据迁移过程

当发生迁移的时候,假设data server A 要把 桶 1,2,3 迁移给data server B。因为迁移完成前,client的路由表没有变化,因此对 1, 2, 3 的访问请求都会路由到A。现在假设1还没迁移,2 正在迁移中,3已经迁移完成,那么:

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

 dataserver宕机?

 当有某台data server故障不可用的时候, config server会发现这个情况, config server负责重新计算一张新的桶在data server上的分布表, 将原来由故障机器服务的桶的访问重新指派到其它的data server中。

如果是因为某data server宕机而引发的迁移, 客户端会收到一张中间临时状态的分配表. 这张表中, 把宕机的data server所负责的桶临时指派给有其备份data server来处理. 这个时候, 服务是可用的, 但是负载可能不均衡. 当迁移完成之后, 才能重新达到一个新的负载均衡的状态.

这个时候, 可能会发生数据的迁移. 比如原来由data server A负责的桶, 在新表中需要由 B负责. 而B上并没有该桶的数据, 那么就将数据迁移到B上来. 同时config server会发现哪些桶的备份数目减少了, 然后根据负载情况在负载较低的data server上增加这些桶的备份. 当系统增加data server的时候, config server根据负载, 协调data server将他们控制的部分桶迁移到新的data server上. 迁移完成后调整路由。

tair对存储数据的版本控制

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,然后在其基础上修改,也可以选择强行更新。

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

tair的存储引擎

mdb是Tair最早的一款缓存存储引擎,也是在应用最广泛的集中式缓存。特别适用容量小(一般在M级别,50G之内),读写QPS高(万级别)的应用场景。它有着和memcached类似的内存管理方式。mdb支持使用share memory,这使得我们在重启Tair数据节点的进程时不会导致数据的丢失,从而使升级对应用来说更平滑,不会导致命中率的较大波动。

rdb是基于redis开发另一款内存型产品,tair抽取了redis内部存储引擎部分,支持redis所有数据结构,故rdb不仅支持key对应一个value的结构,同时也支持key对应多个value的结构,结构可以是list/map/set/zset。使用树的方式根据数据key的hash值索引数据,加快查找速度。索引文件和数据文件分离,尽量保持索引文件在内存中,以便减小IO开销。使用空闲空间池管理被删除的空间。

ldb是google开源出来的,定位于高性能存储,并可选择内嵌mdb cache加速,这种情况下cache与持久化存储的数据一致性由tair进行维护。支持k/v,prefix等数据结构。

leveldb详解

构成LevelDb静态结构的包括六个主要部分:内存中的MemTable和Immutable MemTable以及磁盘上的几种主要文件:Current文件,Manifest文件,log文件以及SSTable文件。

ldb读写操作

当应用写入一条Key:Value记录的时候,LevelDb会先往log文件里追加写入,成功后将记录插进Memtable中,这样基本就算完成了写入操作,因为一次写入操作只涉及一次磁盘顺序写和一次内存写入,这是LevelDb写入速度极快的主要原因。Log文件在系统中的作用主要是用于系统崩溃恢复而不丢失数据,假如没有Log文件,因为写入的记录刚开始是保存在内存中的,此时如果系统崩溃,内存中的数据还没有来得及Dump到磁盘,所以会丢失数据。为了避免这种情况,LevelDb在写入内存前先将操作记录到Log文件中,然后再记入内存中,这样即使系统崩溃,也可以从Log文件中恢复内存中的Memtable,不会造成数据的丢失。

当Memtable插入的数据占用内存到了一个界限后,需要将内存的记录导出到外存文件中,LevleDb会生成新的Log文件和Memtable,原先的Memtable就成为Immutable Memtable,顾名思义,就是说这个文件的内容是不可更改的。新到来的数据被记入新的Log文件和Memtable,LevelDb后台调度会将Immutable Memtable的数据导出到磁盘,形成一个新的SSTable文件。SSTable就是由内存中的数据不断导出并进行Compaction操作后形成的,而且SSTable的所有文件是一种层级结构,第一层为Level 0,第二层为Level 1,依次类推,层级逐渐增高,这也是为何称为LevelDb的原因。

LevelDB首先会去查看内存中的Memtable

如果Memtable中包含key及其对应的value,则返回value值即可;

如果在Memtable没有读到key,则接下来到同样处于内存中的Immutable Memtable中去读取。

Memtable和Immutable Memtable中都没有,万般无奈下从磁盘中的大量SSTable文件中查找。

因为SSTable数量较多,而且分成多个Level,首先从属于level 0的文件中查找,如果找到则返回对应的value值,如果没有找到那么到level 1中的文件中去找,如此循环往复,直到在某层SSTable文件中找到这个key对应的value为止(或者查到最高level,查找失败,说明整个系统中不存在这个Key)。

Tair VS Redis

 
Redis
Tair
适用
  1. 需要使用复杂数据结构(map, set),map/set中元素很多(1000以上)
  2. 延迟敏感服务
  1. 不能容忍数据丢失
  2. 数据量大,内存放不下的服务
  3. 需要多语言客户端支持
不适用
  1. 数据量超过600GB(数据太多,全内存太浪费资源)
  2. 需要多语言客户端支持
  1. 使用复杂数据结构(map/set),map/set中元素很多(1000以上)

 

猜你喜欢

转载自dubbo2013.iteye.com/blog/2383808