簡介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) 設置數據,如果數據已經存
|
|