Tair(Taobao Pair)是淘宝开发的分布式Key-Value存储引擎,服务器端自动负载均衡,分为持久化和非持久化两种方式存储。
Tair采用可插拔存储引擎设计,以上这些存储引擎可以很方便的替换,还可以引入新的存储引擎比如:MySQL
分布式缓存
数据源存储
Data server负责数据的物理存储,并根据Configserver构建的对照表完成数据的复制和迁移工作。Data server具备抽象的存储引擎层,可以很方便地添加新存储引擎。Data server还有一个插件容器,可以动态地加载/卸载插件。
Tair的存储引擎有一个抽象层,只要满足存储引擎需要的接口,便可以很方便地替换Tair底层的存储引擎。比如你可以很方便地将bdb、tc甚至MySQL作为Tair的存储引擎,而同时使用Tair的分布方式、同步等特性。
Tair默认包含两个存储引擎:mdb和fdb。
mdb是一个高效的缓存存储引擎,它有着和memcached类似的内存管理方式。mdb支持使用sharememory(tmpfs),这使得我们在重启Tair数据节点的进程时不会导致数据的丢失,从而使升级对应用来说更平滑,不会导致命中率的较大波动。
fdb是一个简单高效的持久化存储引擎,使用树的方式根据数据key的hash值索引数据,加快查找速度。索引文件和数据文件分离,尽量保持索引文件在内存中,以便减小IO开销。使用空闲空间池管理被删除的空间。
GIt
- yum install git
- yum update -y nss curl libcurl
svn
yum install subversion
Libtool
yum install libtool
boost-devel
yum install boost-devel
zlib
yum install zlib-devel
C++
yum install gcc-c++
tbsys库和tbnet库下载(tair底层依赖tbsys库和tbnet库)
- #git源码下载
- git clone https://github.com/kayaklee/tb-common-util.git
- #设置环境变量
- mkdir /var/tblib
- export TBLIB_ROOT="/var/tblib"
- #因为tbnet和tbsys在两个不同的目录,但它们的源码文件里头文件的互相引用却没有加绝对或相对路径,将两个目录的源码加入到C++环境变量中即可。否则编译时会出现:“fatal error:tysys.h: No such file or directory”的错误。
- CPLUS_INCLUDE_PATH=/home/tair/tb-common-util/tbsys/src:/home/tair/tb-commonutil/tbnet/src
- export CPLUS_INCLUDE_PATH
- #修改tbsys代码
- cd ~/tb-common-util/trunk/tbsys/src
- 下载的代码有个错误:具体是tbsys/src/tblog.cpp中323行代码:需要将CLogger::CLogger&CLogger::getLogger()改为CLogger& CLogger::getLogger()
- #编译tbsys和tbnet
- cd tb-common-utils
- ./build.sh
- 注:安装成功后,TBLIB_ROOT所指示的目录下会有include和lib两个目录。
- #git源码下载
- git clone https://github.com/alibaba/tair.git
- #安装依赖
- yum install -y openssl-devel libcurl-devel
- #编译
- ./bootstrap.sh
- #检测和生成 Makefile
- ./configure
- #编译和安装到目标目录
- make -j
- make install
- 注:默认安装位置是 ~/tair_bin
下面以MDB引擎为例配置一个最小化的Tair集群(1 * ConfigServer + 1 * DataServer)
- #查看和设置系统tmpfs
- #MDB 引擎默认使用共享内存,所以需要查看并设置系统的tmpfs的大小
- # 这里根据实际机器内存情况配置,必须大于Tair使用内存的配置
- vim /etc/fstab
- tmpfs /dev/shm tmpfs defaults,size=1024M 0 0
- #生效
- mount -o remount /dev/shm
- #查看tmpfs
- cat /etc/fstab | grep /dev/shm
- 显示tmpfs /dev/shm tmpfs rw,size=1G 0 0
- # 定义配置文件 复制配置文件
- cp etc/configserver.conf.default etc/configserver.conf
- cp etc/group.conf.default etc/group.conf
- etc/dataserver.conf.default etc/dataserver.conf
- # configserver.conf
- [public]
- config_server=192.168.127.133:5198
- #config_server=192.168.1.2:5198
- #config_server=10.211.55.9:5198
- #config_server=192.168.1.2:5198
- dev_name=eno16777736
- #group.conf
- # data center A
- _server_list=192.168.127.133:5191
- #_server_list=192.168.1.2:5191
- #_server_list=192.168.1.3:5191
- #_server_list=192.168.1.4:5191
-
- # data center B
- #_server_list=192.168.2.1:5191
- #_server_list=192.168.2.2:5191
- #_server_list=192.168.2.3:5191
- #_server_list=192.168.2.4:5191
-
- #dataserver.conf
- [public]
- config_server=192.168.127.133:5198
- #config_server=192.168.1.2:5198
-
- dev_name=eno16777736
- mdb_inst_shift=0
- process_thread_num=4
- io_thread_num=4
- #这里 slab_mem_size控制MDB内存池的总大小,mdb_inst_shift 控制实例的个数,注意这里一个实例必须大于512MB且小于64GB。
- slab_mem_size=512
- #修改 tair.sh启动脚本
- #在CentOS 7下,安装目录下的 tair.sh 启动脚本有一行代码需要修改
- tmpfs_size=`df -m |grep /dev/shm | awk '{print $2}'`
- #启动Tair实例
- #启动dataserver
- tair_bin $ ./tair.sh start_ds
- #启动configserver
- tair_bin $ ./tair.sh start_cs
- #查看进程
- ps -ef |grep tair
- 注:执行后没有两行记录显示,说明启动失败,可查看logs中的log查看错误信息。
- #客户端读写测试
- tair_bin $ ./sbin/tairclient -c 192.168.127.133:5198 -g group_1
- TAIR> health
- TAIR> put key value
- TAIR> get key
- TAIR> remove key
- TAIR> get key
- #停止dataserver
- tair_bin $ ./tair.sh stop_ds
- #停止configserver
- tair_bin $ ./tair.sh stop_cs
比如:
- bucket data server
- 0 192.168.127.132 192.168.127.134
- 1 192.168.127.133 192.168.127.135
- 2 192.168.127.132 192.168.127.134
- 3 192.168.127.133 192.168.127.135
- 4 192.168.127.132 192.168.127.134
- 5 192.168.127.133 192.168.127.135
对照表的初始化
- config server会尽量的把桶均匀的分布到各个data server上
- 一个桶的备份数据不能在同一台主机上
- 一般我们通过控制 _pos_mask(Tair的一个配置项) 来使得不同的机房具有不同的位置信息
- 一个桶的备份数据不能都位于相同的一个位置(不在同一个机房)
这时需要做数据迁移,具体流程如下:
mdb
fdb
kdb
ldb
rdb
数据存储 | 持久化 | 数据格式 | 应用场景 | |
mdb | Memcached | 非 | K-V存储、prefix操作 | 缓存使用、Session分离 |
fdb | FireBird | 是 | SQL、BTree | 快速访问较小的数据 |
kdb | Kyoto Cabinet | 是 | Hash、B+Tree | 简单临时存储 |
ldb | LevelDB | 是 | kv、prefix、batch | 大数据量高频度的存储 |
rdb | Redis | 非 | List、Set、Hash.... | 复杂数据结构缓存 |
阿里使用:mdb、rdb、ldb
mdb的存储结构(Memcached)
mem_pool
mem_cache
cache_hash_map
mdb_area_stat
用于维护area(namespace)状态,记录了该area的数据链表和数据量限制(tablespace)
操作 | 说明 | |
get | get key [area] | 获得key的值 |
put | put key data [area] [expired] | 设置key和value |
incr | incr key [count] [initValue] [area] | 对key的值自增 |
decr | decr key [count] [area] | 对key的值自减 |
batcheGet | mget key1 ... keyn area | 批量获得 |
expire | expire key time | 设置key的过期时间 |
prefixPut | pput [area] pkey skey value | 根据前缀设置,按照prefix计算hash,同一个prefix会存储在同一个hash表,形成链表 |
prefixGet | pget [area] pkey skey | 根据前缀读取 |
Tair中的每个数据都包含版本号,版本号在每次更新后都会递增。这个特性有助于防止由于数据的并发更新导致的问题,类似于乐观锁
比如,系统有一个value为“a”,A和B同时get到这个value。
如果不控制,无论A和B谁先更新成功,它的更新都会被后到的更新覆盖。
使用version解决,第一次put version为1,get version为1,A修改成功后 version增加1,B修改时本身version为1,小于服务器版本2,所以B修改不成功,在put时,version传0 可强制修改。
- struct mdb_item{
- uint64_t item_id;
- .....
- uint16_t version
- }
Tair实现乐观锁
trylock(获取版本)–> transaction –> unlock(版本检查) — 正常—> version+1 commit— 异常—> rollback
version 初始化要大于1
Tair实现分布式锁
示例代码如下:
- //获得锁
- public boolean lock(String lockKey) {
- //10 :version expiretime : 过期时间 秒
- ResultCode code = defaultTairManager.put(lockKey, defaultVlaue, 5, expiretime);
- if (ResultCode.SUCCESS.equals(code))
- return true;
- else
- return false;
- }
- //释放锁
- public Boolean unlock(String lockKey){
- ResultCode code = defaultTairManager.delete(lockKey);
- return ResultCode.SUCCESS.equals(code);
- }
pom.xml
- <dependency>
- <groupId>com.taobao.tairgroupId>
- <artifactId>tair-clientartifactId>
- <version>2.3.5version>
- dependency>
demo
- import com.taobao.tair.DataEntry;
- import com.taobao.tair.Result;
- import com.taobao.tair.impl.DefaultTairManager;
-
- import java.util.ArrayList;
- import java.util.List;
-
- public class TairDemo {
- public static void main(String[] args) throws Exception {
- DefaultTairManager defaultTairManager = new DefaultTairManager();
- List
cs = new ArrayList(); - cs.add("192.168.127.132:5198");
- defaultTairManager.setConfigServerList(cs);
- defaultTairManager.setGroupName("group_1");
- defaultTairManager.init();
- defaultTairManager.put(0,"name:001","zhangfei");
- Result
value= defaultTairManager.get(0,"name:001"); - System.out.println(value);
- //设置版本0 强制更新 过期时间2秒
- defaultTairManager.put(0,"name:002","zhaoy",0,2);
- Result
value2= defaultTairManager.get(0,"name:002"); - System.out.println(value2);
- Thread.sleep(4000);
- Result
value3= defaultTairManager.get(0,"name:002"); - System.out.println(value3);
-
-
- }
- }