简介tair 是淘宝自己开发的一个分布式 key/value 存储引擎. tair 分为持久化和非持久化两种使用方式. 非持久化的 tair tair 的总体结构tair 作为一个分布式系统, 是由一个中心控制节点和一系列的服务节点组成. 我们称中心控制节点为config server. tair 的负载均衡算法是什么tair 的分布采用的是一致性哈希算法, 对于所有的key, 分到Q个桶中, 桶是负载均衡和数据迁移的基本单位. config server 增加或者减少data server的时候会发生什么当有某台data server故障不可用的时候, config server会发现这个情况, config 发生迁移的时候data server如何对外提供服务当迁移发生的时候, 我们举个例子, 假设data server A 要把 桶 3,4,5 迁移给data server B. 因为迁移完成前, 桶在data server上分布时候的策略程序提供了两种生成分配表的策略, 一种叫做负载均衡优先, 一种叫做位置安全优先: 我们先看负载优先策略. 当采用负载优先策略的时候, tair 的一致性和可靠性问题分布式系统中的可靠性和一致性是无法同时保证的, 因为我们必须允许网络错误的发生. tair 采用复制技术来提高可靠性, tair提供的客户端tair 的server端是C++写的, 因为server和客户端之间使用socket通信, 主要的性能数据待补充 1. Tair总述1.1 系统架构一个Tair集群主要包括3个必选模块:configserver、dataserver和client,一个可选模块:invalidserver。通 从架构上看,configserver的角色类似于传统应用系统的中心节点,整个集群服务依赖于configserver的正常工作。但实际上相对来 1.1.1 ConfigServer的功能1) 通过维护和dataserver心跳来获知集群中存活节点的信息 1.1.2 DataServer的功能1) 提供存储引擎 1.1.3 InvalidServer的功能1) 接收来自client的invalid/hide等请求后,对属于同一组的集群(双机房独立集群部署方式)做delete/hide操作,保证同一组集群的一致。 1.1.4 client的功能1) 在应用端提供访问Tair集群的接口。 1.2 存储引擎与应用场景Tair经过这两年的发展演进,除了应用于cache缓存外,在存储(持久化)上支持的应用需求也越来越广泛。现在主要有mdb,rdb,ldb三种存储引擎。 1.2.1 mdb定位于cache缓存,类似于memcache。 1.2.1.1 mdb的应用场景 1.2.2 rdb定位于cache缓存,采用了redis的内存存储结构。 1.2.2.1 rdb的应用场景 1.2.3 ldb定位于高性能存储,并可选择内嵌mdb cache加速,这种情况下cache与持久化存储的数据一致性由tair进行维护。 1.2.3.1 ldb的应用场景 1.3 基本概念1.3.1 configID唯一标识一个tair集群,每个集群都有一个对应的configID,在当前的大部分应用情况下configID是存放在diamond中的,对应了该集 1.3.2 namespace又称area, 是tair中分配给应用的一个内存或者持久化存储区域, 可以认为应用的数据存在自己的namespace中。 1.3.3 quota配额对应了每个namespace储存区的大小限制,超过配额后数据将面临最近最少使用(LRU)的淘汰。 1.3.3.1 配额是怎样计算的 1.3.3.2 管理员如何配置配额 1.3.4 expireTime:过期时间expiredTime 1.3.5 versionTair中存储的每个数据都有版本号,版本号在每次更新后都会递增,相应的,在Tair put接口中也有此version参数,这个参数是为了解决并发更新同一个数据而设置的,类似于乐观锁。 1.3.5.1 如何获取到当前key的version 1.3.5.2 version是如何改变的 1.3.5.3 version返回不一致的时候,该如何处理 1.3.5.4 version具体使用案例 1.3.5.5 version分布式锁 1.3.5.6 什么情况下需要使用version 1.4 集群部署方式Tair通过多种集群部署方式,来满足各类应用的容灾需求。 1.4.1 双机房单集群单份双机房单集群单备份数是指,该Tair集群部署在两个机房中(也就是该Tair集群的机器分别在两个机房), 数据存储份数为1, 该类型集群部署示意图如下所示。数据服务器(Dataserver)分布在两个机房中,他们都属于同一集群。 使用场景: 1.4.2 双机房独立集群双机房独立集群是指,在两个机房中同时部署2个独立的Tair集群,这两个集群没有直接关系。下图是一个典型的双机房独立集部署示意图,可以看到,cm3 适用场景: 1.4.3 双机房单集群双份双机房单集群双份,是指一个Tair集群部署在2个机房中,数据保存2份,并且同一数据的2个备份不会放在同一个数据服务器上。根据数据分布策略的不同, 现只有tbsession集群使用了这种部署方式。 1.4.4 双机房主备集群这种部署方式中,存在一个主集群和一个备份集群,分别在两个机房中。如下图所示,不妨假设CM3中部署的是主集群,CM4中部署的是备份集群。那么,在正 适用场景:
取得源代码后, 先指定环境变量 TBLIB_ROOT 为需要安装的目录. 这个环境变量在后续 tair 的编译安装中仍旧会被使用到. 比如要安装到当前用户的lib目录下, 则指定 export TBLIB_ROOT="~/lib" 进入common文件夹, 执行build.sh进行安装.
进入 tair 目录 运行 bootstrap.sh 运行 configure. 注意, 在运行configue的时候, 可以使用 --with-boost=xxxx 来指定boost的目录. 使用--with-release=yes 来编译release版本. 运行 make 进行编译 运行 make install 进行安装 二 如何配置tair: tair的运行, 至少需要一个 config server 和一个 data server. 推荐使用两个 config server 多个data server的方式. 两个config server有主备之分. 源代码目录中 share 目录下有三个配置文件的样例, 下面会逐个解说. configserver.conf group.conf 这两个配置文件是config server所需要的. 先看这两个配置文件的配置
[public] config_server=x.x.x.x:5198 config_server=x.x.x.x:5198 [configserver] port=5198 log_file=logs/config.log pid_file=logs/config.pid log_level=warn group_file=etc/group.conf data_dir=data/data dev_name=eth0 public 下面配置的是两台config server的 ip 和端口. 其中排在前面的是主config server. 这一段信息会出现在每一个配置文件中. 请保持这一段信息的严格一致. configserver下面的内容是本config server的具体配置: port 端口号, 注意 config server会使用该端口做为服务端口, 而使用该端口+1 做为心跳端口 log_file 日志文件 pid_file pid文件, 文件中保存当前进程中的pid log_level 日志级别 group_file 本config server所管理的 group 的配置文件 data_dir 本config server自身数据的存放目录 dev_name 所使用的网络设备名 注意: 例子中, 所有的路径都配置的是相对路径. 这样实际上限制了程序启动时候的工作目录. 这里当然可以使用绝对路径. 注意: 程序本身可以把多个config server 或 data server跑在一台主机上, 只要配置不同的端口号就可以. 但是在配置文件的时候, 他们的数据目录必须分开, 程序不会对自己的数据目录加锁, 所以如果跑在同一主机上的服务, 数据目录配置相同, 程序自己不会发现, 却会发生很多莫名其妙的错误. 多个服务跑在同一台主机上, 一般只是在做功能测试的时候使用.
#group name [group_1] # data move is 1 means when some data serve down, the migrating will be start. # default value is 0 _data_move=1 #_min_data_server_count: when data servers left in a group less than this value, config server will stop serve for this group #default value is copy count. _min_data_server_count=4 _copy_count=3 _bucket_number=1023 _plugIns_list=libStaticPlugIn.so _build_strategy=1 #1 normal 2 rack _build_diff_ratio=0.6 #how much difference is allowd between different rack # diff_ratio = |data_sever_count_in_rack1 - data_server_count_in_rack2| / max (data_sever_count_in_rack1, data_server_count_in_rack2) # diff_ration must less than _build_diff_ratio _pos_mask=65535 # 65535 is 0xffff this will be used to gernerate rack info. 64 bit serverId & _pos_mask is the rack info, _server_list=x.x.x.x:5191 _server_list=x.x.x.x:5191 _server_list=x.x.x.x:5191 _server_list=x.x.x.x:5191 #quota info _areaCapacity_list=1,1124000; _areaCapacity_list=2,1124000; 每个group配置文件可以配置多个group, 这样一组config server就可以同时服务于多个 group 了. 不同的 group 用group name区分 _data_move 当这个配置为1的时候, 如果发生了某个data server宕机, 则系统会尽可能的通过冗余的备份对数据进行迁移. 注意, 如果 copy_count 为大于1的值, 则这个配置无效, 系统总是会发生迁移的. 只有copy_count为1的时候, 该配置才有作用. _min_data_server_count 这个是系统中需要存在的最少data server的个数. 当系统中可正常工作的data server的个数小于这个值的时候, 整个系统会停止服务, 等待人工介入 _copy_count 这个表示一条数据在系统中实际存储的份数. 如果tair被用作缓存, 这里一般配置1. 如果被用来做存储, 一般配置为3。 当系统中可工作的data server的数量少于这个值的时候, 系统也会停止工作. 比如 _copy_count 为3, 而系统中只有 2 台data server. 这个时候因为要求一条数据的各个备份必须写到不同的data server上, 所以系统无法完成写入操作, 系统也会停止工作的. _bucket_number 这个是hash桶的个数, 一般要 >> data server的数量(10倍以上). 数据的分布, 负载均衡, 数据的迁移都是以桶为单位的. _plugIns_list 需要加载的插件的动态库名 _accept_strategy 默认为0,ds重新连接上cs的时候,需要手动touch group.conf。如果设置成1,则当有ds重新连接会cs的时候,不需要手动touch group.conf。 cs会自动接入该ds。 _build_strategy 在分配各个桶到不同的data server上去的时候所采用的策略. 目前提供两种策略. 配置为1 则是负载均衡优先, 分配的时候尽量让各个 data server 的负载均衡. 配置为 2 的时候, 是位置安全优先, 会尽量将一份数据的不同备份分配到不同机架的机器上. 配置为3的时候,如果服务器分布在多个机器上,那么会优先使用位置安全优先,即策略2. 如果服务器只在一个机架上,那么退化成策略1,只按负载分布。 _build_diff_ratio 这个值只有当 _build_strategy 为2的时候才有意义. 实际上是用来表示不同的机架上机器差异大小的. 当位置安全优先的时候, 如果某个机架上的机器不断的停止服务, 必然会导致负载的极度不平衡. 当两个机架上机器数量差异达到一定程度的时候, 系统也不再继续工作, 等待人工介入. _pos_mask 机架信息掩码. 程序使用这个值和由ip以及端口生成的64为的id做与操作, 得到的值就认为是位置信息. 比如 当此值是65535的时候 是十六进制 0xffff. 因为ip地址的64位存储的时候采用的是网络字节序, 最前32位是端口号, 后32位是网络字节序的ip地址. 所以0xffff 这个配置, 将认为10.1.1.1 和 10.2.1.1 是不同的机架. _areaCapacity_list 这是每一个area的配额信息. 这里的单位是 byte. 需要注意的是, 该信息是某个 area 能够使用的所有空间的大小. 举个具体例子:当copy_count为3 共有5个data server的时候, 每个data server上, 该area实际能使用的空间是这个值/(3 * 5). 因为fdb使用mdb作为内部的缓存, 这个值的大小也决定了缓存的效率.
[public] config_server=172.23.16.225:5198 config_server=172.23.16.226:5198 [tairserver] storage_engine=mdb mdb_type=mdb_shm mdb_shm_path=/mdb_shm_path01 #tairserver listen port port=5191 heartbeat_port=6191 process_thread_num=16 slab_mem_size=22528 log_file=logs/server.log pid_file=logs/server.pid log_level=warn dev_name=bond0 ulog_dir=fdb/ulog ulog_file_number=3 ulog_file_size=64 check_expired_hour_range=2-4 check_slab_hour_range=5-7 [fdb] # in # MB index_mmap_size=30 cache_size=2048 bucket_size=10223 free_block_pool_size=8 data_dir=fdb/data fdb_name=tair_fdb 下面解释一下data server的配置文件: public 部分不再解说 storage_engine 这个可以配置成 fdb 或者 mdb. 分别表示是使用内存存储数据(mdb)还是使用磁盘(fdb). mdb_type 这个是兼容以前版本用的, 现在都配成mdb_shm就可以了 mdb_shm_path 这个是用作映射共享内存的文件. port data server的工作端口 heartbeat_port data server的心跳端口 process_thread_num 工作线程数. 实际上启动的线程会比这个数值多, 因为有一些后台线程. 真正处理请求的线程数量是这里配置的. slab_mem_size 所占用的内存数量. 这个值以M为单位, 如果是mdb, 则是mdb能存放的数据量, 如果是fdb, 此值无意义 ulog_dir 发生迁移的时候, 日志文件的文件目录 ulog_file_number 用来循环使用的log文件数目 ulog_file_size 每个日志文件的大小, 单位是M check_expired_hour_range 清理超时数据的时间段. 在这个时间段内, 会运行一个后台进程来清理mdb中的超时数据. 一般配置在系统较空闲的时候 check_slab_hour_range 对slap做平衡的时间段. 一般配置在系统较空闲的时候 index_mmap_size fdb中索引文件映射到内存的大小, 单位是M cache_size fdb中用作缓存的共享内存大小, 单位是M bucket_size fdb在存储数据的时候, 也是一个hash算法, 这儿就是hash桶的数目 free_block_pool_size 这个用来存放fdb中的空闲位置, 便于重用空间 data_dir fdb的数据文件目录 fdb_name fdb数据文件名 三 运行前的准备: 因为系统使用共享内存作为数据存储的空间(mdb)或者缓存空间(fdb), 所以需要先更改配置, 使得程序能够使用足够的共享内存. scripts 目录下有一个脚本 set_shm.sh 是用来做这些修改的, 这个脚本需要root权限来运行. 四 如何启动集群: 在完成安装配置之后, 可以启动集群了. 启动的时候需要先启动data server 然后启动cofnig server. 如果是为已有的集群添加dataserver则可以先启动dataserver进程然后再修改gruop.conf,如果你先修改group.conf 再启动进程,那么需要执行touch group.conf;在scripts目录下有一个脚本 tair.sh 可以用来帮助启动 tair.sh start_ds 用来启动data server. tair.sh start_cs 用来启动config server. 这个脚本比较简单, 它要求配置文件放在固定位置, 采用固定名称. 使用者可以通过执行安装目录下的bin下的 tair_server (data server) 和 tair_cfg_svr(config server) 来启动集群. Tair用户指南本文档介绍了Tair客户端的工作原理,以及Java和c++客户端的使用方法和接口介绍。 工作原理Tair是一个分布式的key/value存储系统,数据往往存储在多个数据节点上。客户端需要决定数据存储的具体节点,然后才能完成具体的操作。 Tair的客户端通过和configserver交互获取这部分信息。configserver会维护一张表,这张表包含hash值与存储其对应数据的节点的对照关系。客户端在启动时,需要先和configserver通信,获取这张对照表。 在获取到对照表后,客户端便可以开始提供服务。客户端会根据请求的key的hash值,查找对照表中负责该数据的数据节点,然后通过和数据节点通信完成用户的请求。 支持的客户端Tair当前支持Java http://baike.corp.taobao.com/index.php/GetClient和c++语言的客户端。 java客户端使用指南java版本兼容性Tair的java客户端需要JDK 1.5或与其兼容的环境。我们使用Sun公司的JDK 1.5在 Linux和Windows上测试过。 依赖Tair客户端使用mina( http://mina./ )通信框架与Tair server通信,所以使用Tair java客户端需要确保运行环境中包含mina的jar包以及其依赖的库,mina请使用1.1.x的版本。 配置java客户端支持的配置项
初始化Java客户端// 创建config server列表 List<String> confServers = new ArrayList<String>(); confServers.add("CONFIG_SERVER_ADDREEE:PORT"); confServers.add("CONFIG_SERVER_ADDREEE_2:PORT"); // 可选 // 创建客户端实例 DefaultTairManager tairManager = new DefaultTairManager(); tairManager.setConfigServerList(confServers); // 设置组名 tairManager.setGroupName("GROUP_NAME"); // 初始化客户端 tairManager.init(); 如果你的系统使用spring,那么可以使用类似下面的bean配置: <bean id="tairManager" class="com.taobao.tair.impl.DefaultTairManager" init-method="init"> <property name="configServerList"> <list> <value>CONFIG_SERVER_ADDREEE:PORT</value> <value>CONFIG_SERVER_ADDREEE_2:PORT</value> <!-- 可选 --> </list> </property> <property name="groupName"> <value>GROUP_NAME</value> </property> </bean> 接口介绍预备知识由于Tair中的value除了用户的数据外,好包括version等元信息。所以返回的用户数据将和元数据一起封装在DataEntry对象中。 Tair客户端的所有接口都不抛出异常,操作的结果状态使用ResultCode表示。如果接口会返回数据,则返回的数据和ResultCode一起封装在Result中。 Result和ResultCode都包含有isSuccess方法,如果该方法返回true,则表示请求成功(当get的数据不存在时,该方法也返回 true)。 基本接口
get接口用于获取单个数据,要获取的数据由namespace和key指定。 当数据存在时,返回成功,数据存放在DataEntry对象中; 当数据不存在时,返回成功,ResultCode为ResultCode.DATANOTEXSITS,value为null。 示例: Result<DataEntry> result = tairManager.get(namespace, key); if (result.isSuccess()) { DataEntry entry = result.getValue(); if(entry != null) { // 数据存在 } else { // 数据不存在 } } else { // 异常处理 }
mget接口用于批量获取同一个namespace中的多个key对应的数据集合。mget在客户端将key列表根据key所在的服务器分组,然后将分组后的key列表发送到对应的服务器上,发送到多个服务器这个步骤是异步的,所以需要的时间不是线性的。 当得到返回结果时
当有数据返回时,Result对象中的value是一个List<DataEntry>,这个List包含了所有取到的数据,每个 DataEntry都会包括请求的key,返回的value和version信息。
put接口有3个签名,分别为: ResultCode put(int namespace, Serializable key, Serializable value); // version为0,即不关心版本;expireTime为0,即不失效 ResultCode put(int namespace, Serializable key, Serializable value, int version); // expireTime为0,即不失效 ResultCode put(int namespace, Serializable key, Serializable value, int version, int expireTime); 示例: ResultCode rc = tairManager.put(namespace, key, value); if (rc.isSuccess()) { // put成功 } else if (ResultCode.VERERROR.equals(rc) { // 版本错误的处理代码 } else { // 其他失败的处理代码 } // version应该从get返回的DataEntry对象中获取 // 出给使用0强制更新,否则不推荐随意指定版本 rc = tairManager.put(namespace, key, value, version); // 使用全参数版本的put rc = tairManager.put(namespace, key, value, version, expireTime);
delete接口用于删除有namespac和key指定的value。如果请求删除的key不存在,tair也将返回成功。 示例: // 使用删除接口 ResultCode rc = tairManager.delete(namespace, key); if (rc.isSuccess()) { // 删除成功 } else { // 删除失败 }
mdelete接口用于批量删除数据,该接口只能删除同一个namespace中的多条数据。其工作原理和mget接口类似。 示例: // 使用批量删除接口 ResultCode rc = tairManager.mdelete(namespace, keys); if (rc.isSuccess()) { // 删除成功 } else { // 部分成功处理代码 }
incr和decr接口配合使用可以提供计数器的功能。使用get和put接口也能实现计数器的功能,但由于两个操作不是原子的,很多情况下这不能满足需求。所以我们提供了incr和decr接口,通过这两个接口提供原子的计数器操作。 incr接口的方法签名为: Result<Integer> incr(int namespace, Serializable key, int value, int defaultValue, int expireTime); 接口参数的含义为:
示例: Result<Integer> result = tairManager.incr(namespace, key, 1, 0); if (result.isSuccess()) { int rv = result.getValue(); // 这里是1 } else { // 错误处理 } // 将返回4 result = tairManager.incr(namespace, key, 3, 0); // 将返回2 result = tairManager.decr(namespace, key, 2, 0);
item接口是对原有key/value接口的扩充,item接口将value视为一个数组,配合服务器端的支持,可以完成以下操作:
Item接口内部使用json格式,只支持基本类型,包括:
同一个Value中的类型需要保持一致,否则将返回序列化错误。 每个item可以指定maxcount,当item的条数操作指定的maxcount是,服务器将自动删除最早插入的item。 List<Integer> intList = new ArrayList<Integer>(); intList.add(1); intList.add(2); // 添加新的itmes ResultCode rc = tairManager.addItems(1, key, intList, 50, 0, 0); // 获取item的总条数 Result<Integer> ic = tairManager.getItemCount(1, key); int totalCount = ic.getValue(); // 获取所有items Result<DataEntry> rets = tairManager.getItems(1, key, 0, totalCount); // 获取第一个item,并将其从系统中删除 rets = tairManager.getAndRemove(1, key, 0, 1); C++客户端使用指南数据操作接口put incr 初始化(连接到tair)startup 清理close 超时时间设置setTimeout 使用流程CTAIRClinetAPI *tairClient = new CTAIRClientAPI(); assert(tairClient != 0); if ( !tairClient->startup(configserver_master,configserver_slave,group_name)){ //没连上,处理错误 } tairClinet->put(...); tairClient->get(...); ...... delete tairClient; or CTAIRClientAPI tairClient; tairClient.startup(); //put //get //etc. tairClient.close(); //可选,析构时会自动清理 错误代码说明: 对几个常见的Tair错误码做一个简要的解释 0, "success" 1, -3998 "data not exist" 数据不存在 -3988, "data expired" 数据过期,由于tair的数据删采用的是lazy -1, "connection error or timeout" -3989, "timeout" 超时。 -3997, "version error" 版本错误,tair操作中的版本相当于一种乐观锁的机制,参见 -3994, "serialize error" -3984, "migrate busy" tair的迁移的过程要保证仍然对外提供服务,因此迁移过程会分成很多轮,当迁移过程中的数据增量小到一定的阈值后,会有很小的一段时间停止写服务,这是就会返回migrate busy,稍稍过几秒钟重试应该就可以成功。 -3986, "write not on master" -5, "key length error" -6, "value length error" key不能超过1K value不能超过1M -20008, "not support" TAIR命令说明
4. 接口4.1. 接口说明Result<DataEntry> get(int namespace, Serializable key) Result< List<DataEntry>> mget(int namespace, List<? extends Object> keys) 批量获取数据 参数: namespace - 数据所在的namespace keys - 要获取的数据的key列表 返回: 如果成功,返回的数据对象为一个Map ResultCode put(int namespace, Serializable key, Serializable value) 设置数据,如果数据已经存
|
|