Tair 是分布式、高性能、可扩展、搞可靠性的存储系统。目前,Tair已成为开源项目,代码在:http://code.taobao.org/p/tair/src/. 关于Tair的更多介绍,见http://code.taobao.org/p/tair/wiki/index/。
几个月以前,我就有计划写一个TAIR代码分析文档。由于个人精力和时间的限制,迟迟未动笔。在13年伊始,我真心觉得有必要对整个系统代码作一个深入的了解。我的突破口是从客户端开始入手。当线上遇到问题时候,希望能够快速定位问题原因,并排除之。于是乎,我正式动工了。闲话不说了。
1. 代码结构
首先,简要介绍Tair的代码结构,如下所示:
- .
- |-- AUTHORS
- |-- COPYING
- |-- ChangeLog
- |-- Makefile.am
- |-- NEWS
- |-- README
- |-- bootstrap.sh
- |-- configure.ac
- |-- contrib
- |-- doc
- |-- packages
- |-- rpm
- |-- scripts
- |-- share
- |-- src
- | |-- Makefile.am
- | |-- client
- | |-- common
- | |-- configserver
- | |-- dataserver
- | |-- invalserver
- | |-- packets
- | |-- plugin
- | |-- statserver
- | | |-- Makefile.am
- | | |-- include
- | | `-- storage
- | |-- storage
- | | |-- fdb
- | | |-- kdb
- | | |-- ldb
- | | |-- mdb
- | | `-- storage_manager.hpp
- | `-- tools
- |-- tcmalloc.m4
- `-- test
Tair系统还依赖底层网络编程库——tbnet,tbsys. 这两个底层库提供了网络通信,配置文件管理,等基础功能;这里就不一一介绍了。
2. 配置服务器
2.1 main
配置服务器(下文称为configserver)是tair系统的中心节点。该节点提供全局信息管理功能,通过主备机制实现中心节点的高可用性。在Tair系统中,configserver提供了哪些功能呢?configserver 具有对对照表(server table)的管理,维护主备配置服务器之间的心跳,维护配置服务器与数据服务器之间的心跳,对数据迁移的管理,以及其他的全局信息管理。下文以这些功能为线索,进行代码分析。
接下来就先分析该服务器的main函数吧,代码如下:
- main(int argc, char *argv[])
- {
- char *config_file = parse_cmd_line(argc, argv);
- if(TBSYS_CONFIG.load(config_file)) {
- return EXIT_FAILURE;
- }
- if(!tbsys::CFileUtil::mkdirs(dir_path)) {
- return EXIT_FAILURE;
- }
- if(tbsys::CProcess::startDaemon(sz_pid_file, sz_log_file) == 0) {
- tair_cfg_svr = new tair::config_server::tair_config_server();
- tair_cfg_svr->start();
- delete tair_cfg_svr;
- }
- return EXIT_SUCCESS;
- }
为了方便分析,上述代码经过精简的。可以看出,main函数在加载配置文件内容到内存,创建工作目录,将本线程设置为后台线程。接着,创建tair::config_server对象,调用start函数,configserver就在完成初始化后,就开始对外提供服务了。
2.2 configserver初始化
configserver的初始化工作主要有start中完成的。该函数首先启动线程池,该线程池用于处理各种请求;打开2个TCP端口,一个用于处理对外提供服务,另外一个用于主备configserver之间的心跳;再开一个称为my_server_conf_thread的线程。代码如下:
- void tair_config_server::start()
- {
- if(initialize()) {
- return;
- }
- task_queue_thread.start();
- if(heartbeat_transport.listen(spec, &packet_streamer, this) == NULL) {
- }
- if(packet_transport.listen(spec, &packet_streamer, this) == NULL) {
- }
- packet_transport.start();
- heartbeat_transport.start();
- my_server_conf_thread.start();
- task_queue_thread.wait();
- my_server_conf_thread.wait();
- packet_transport.wait();
- heartbeat_transport.wait();
- destroy();
- }
task_queue_thread.start, 在没有请求请求来的情况下,就会等待;packet_transport, heartbeat_transport.start 后,将接受到的请求,放入到线程池队列中,工作线程处理请求。这里主要分析my_server_conf_thread线程。该线程的run函数做了如下工作:
1) 读取配置文件,建立保存group信息的数据结构,这些信息包括该group的数据服务器列表等;
2) 如果主配置服务器存在并且alive, 备配置服务器从主配置服务器上读取group的配置信息,而不是从配置文件中读取;
3)建立循环,循环做如下事情:
a) 检查group的配置文件版本信息,确定是否要重新读取配置文件;
b) 检查group的对照表,确定是否要重新建立对照表,若需重建,设置重建标志;
c) 检查配置服务器的状态;
d) 检查数group的数据服务器的状态;
贴上该线程的run函数的主要代码,代码中添加了注释,以便理解:
- void server_conf_thread::run(tbsys::CThread * thread, void *arg)
- {
- //根据配置文件中配置的主备CS的IP和本机的IP,来确定当前cs是作为主配置服务器,还是作为备配置服务器;
- uint64_t sync_config_server_id = 0;
- uint64_t tmp_master_config_server_id = master_config_server_id;
- if(tmp_master_config_server_id == util::local_server_ip::ip) {
- tmp_master_config_server_id = get_slave_server_id();
- }
- if(tmp_master_config_server_id)
- //作为备配置服务器在运行
- sync_config_server_id =
- get_master_config_server(tmp_master_config_server_id, 1);
- if(sync_config_server_id) {
- log_info("syncConfigServer: %s",
- tbsys::CNetUtil::addrToString(sync_config_server_id).
- c_str());
- }
- // 读取group的配置文件名称,这里需要注意的是,在同一个Group中可以配置多个group;
- const char *group_file_name =
- TBSYS_CONFIG.getString(CONFSERVER_SECTION, TAIR_GROUP_FILE, NULL);
- if(group_file_name == NULL) {
- log_error("not found %s:%s ", CONFSERVER_SECTION, TAIR_GROUP_FILE);
- return;
- }
- //将group的配置文件的最后修改时间作为该文件的版本号;
- uint32_t config_version_from_file = get_file_time(group_file_name);
- //bool rebuild_group = true;
- bool rebuild_this_group = false;
- // 这里读取配置文件。对于备配置服务器而言,它需要从主配置服务器中读取配置文件,如果主配置服务器是alive的话;
- load_group_file(group_file_name, config_version_from_file,
- sync_config_server_id);
- //if (syncConfigServer != 0) {
- // rebuild_group = false;
- //}
- //当当前机器运行的cs是主配置服务器,或者主配置服务器down掉时,运行以下代码
- //set myself is master
- if(config_server_info_list.size() == 2U &&
- (config_server_info_list[0]->server_id == util::local_server_ip::ip
- || (sync_config_server_id == 0
- && config_server_info_list[1]->server_id ==
- util::local_server_ip::ip))) {
- //running as a master, or not find the master , only the slave.
- master_config_server_id = util::local_server_ip::ip;
- log_info("set MASTER_CONFIG: %s",
- tbsys::CNetUtil::addrToString(master_config_server_id).
- c_str());
- {
- //group info map数据结构在读取配置文件时候建立,具体在load_group_file中完成
- group_info_map::iterator it;
- group_info_rw_locker.rdlock();
- for(it = group_info_map_data.begin();
- it != group_info_map_data.end(); ++it) {
- if(rebuild_this_group) {
- it->second->set_force_rebuild();
- }
- else {
- //通过检查对照表中是否存在0的项,来确定是否需要重建对照表。对照表的建立是以group为单位的,各个group维护自己的对照表;
- bool need_build_it = false;
- const uint64_t *p_table = it->second->get_hash_table();
- log_debug("server_bucket_count: %d", it->second->get_server_bucket_count());
- for(uint32_t i = 0; i < it->second->get_server_bucket_count();
- ++i) {
- if(p_table[i] == 0) {
- need_build_it = true;
- break;
- }
- }
- if(need_build_it) {
- //这里将标记置为1,表示该Group的对照表需要重建
- it->second->set_force_rebuild();
- }
- else {
- it->second->set_table_builded();
- }
- }
- }
- group_info_rw_locker.unlock();
- }
- }
- // will start loop
- uint32_t loop_count = 0;
- tzset();
- int log_rotate_time = (time(NULL) - timezone) % 86400;
- struct timespec tm;
- clock_gettime(CLOCK_MONOTONIC, &tm);
- //记录心跳时间
- heartbeat_curr_time = tm.tv_sec;
- is_ready = true;
- //开始loop
- while(!_stop) {
- struct timespec tm;
- clock_gettime(CLOCK_MONOTONIC, &tm);
- heartbeat_curr_time = tm.tv_sec;
- //检查另外一个cs是否down掉
- for(size_t i = 0; i < config_server_info_list.size(); i++) {
- server_info *server_info_it = config_server_info_list[i];
- if(server_info_it->server_id == util::local_server_ip::ip)
- continue;
- if(server_info_it->status != server_info::ALIVE
- && (loop_count % 30) != 0) {
- down_slave_config_server = server_info_it->server_id;
- log_debug("local server: %s set down slave config server: %s",
- tbsys::CNetUtil::addrToString(util::local_server_ip::ip).c_str(),
- tbsys::CNetUtil::addrToString(down_slave_config_server).c_str());
- continue;
- }
- //向另外一个cs发送心跳包
- down_slave_config_server = 0;
- request_conf_heartbeart *new_packet = new request_conf_heartbeart();
- new_packet->server_id = util::local_server_ip::ip;
- new_packet->loop_count = loop_count;
- if(connmgr_heartbeat->
- sendPacket(server_info_it->server_id, new_packet) == false) {
- delete new_packet;
- }
- }
- loop_count++;
- if(loop_count <= 0)
- loop_count = TAIR_SERVER_DOWNTIME + 1;
- //读取配置文件的版本信息,检查是否需要重新读取配置文件,每次修改配置文件,总会触发CS重新读取配置文件
- uint32_t curver = get_file_time(group_file_name);
- if(curver > config_version_from_file) {
- log_info("groupFile: %s, curver:%d configVersion:%d",
- group_file_name, curver, config_version_from_file);
- //read config from file
- //读取配置文件,第3个参数为0,表示此次只从配置文件读取,不从另外一个CS上读取配置文件
- load_group_file(group_file_name, curver, 0);
- config_version_from_file = curver;
- //作为主CS,将新的配置文件发送到备CS
- send_group_file_packet();
- }
- //检查配置服务器状态
- check_config_server_status(loop_count);
- //检查数据服务器状态
- if(loop_count > TAIR_SERVER_DOWNTIME) {
- check_server_status(loop_count);
- }
- // 日志回滚
- log_rotate_time++;
- if(log_rotate_time % 86400 == 86340) {
- log_info("rotateLog End");
- TBSYS_LOGGER.rotateLog(NULL, "%d");
- log_info("rotateLog start");
- }
- if(log_rotate_time % 3600 == 3000) {
- log_rotate_time = (time(NULL) - timezone) % 86400;
- }
- //休息1s
- TAIR_SLEEP(_stop, 1);
- }
- is_ready = false;
- }
接下来分析load_group_file函数,该函数主要功能是获取配置文件,当配置文件版本号发生变化后,就会调用该函数,获取最新的配置信息;上代码,在代码中添加注释:
- void server_conf_thread::load_group_file(const char *file_name,
- uint32_t version,
- uint64_t sync_config_server_id)
- {
- if(file_name == NULL)
- return;
- tbsys::CConfig config;
- if(config.load((char *) file_name) == EXIT_FAILURE) {
- log_error("load config file %s error", file_name);
- return;
- }
- log_info("begin load group file, filename: %s, version: %d", file_name,
- version);
- //这里获取所有group的名称
- vector<string> section_list;
- config.getSectionName(section_list);
- // start up, get hash table from an other config server if exist one.
- //若sync_config_server_id不为0, 则表示从sync_config_server_id对应的CS上获取配置信息
- //若从另外一个CS上配置服务器上获取到了GROUP的配置信息后,就不再读取本地配置文件了
- if(sync_config_server_id
- && sync_config_server_id != util::local_server_ip::ip) {
- get_server_table(sync_config_server_id, NULL, GROUP_CONF);
- for(size_t i = 0; i < section_list.size(); i++) {
- get_server_table(sync_config_server_id,
- (char *) section_list[i].c_str(), GROUP_DATA);
- }
- // here we need reload group configfile, since the file has been synced
- if(config.load((char *) file_name) == EXIT_FAILURE) {
- log_error("reload config file error: %s", file_name);
- return;
- }
- }
- set<uint64_t> server_id_list;
- group_info_rw_locker.wrlock();
- server_info_rw_locker.wrlock();
- for(size_t i = 0; i < section_list.size(); i++) {
- group_info_map::iterator it =
- group_info_map_data.find(section_list[i].c_str());
- group_info *group_info_tmp = NULL;
- if(it == group_info_map_data.end()) {
- //根据每一个group name来创建group_info数据结构,该数据结构包含了一个group所需的
- group_info_tmp =
- new group_info((char *) section_list[i].c_str(),
- &data_server_info_map, connmgr);
- group_info_map_data[group_info_tmp->get_group_name()] =
- group_info_tmp;
- }
- else {
- grjup_info_tmp = it->second;
- }
- //group加载自身配置信息
- group_info_tmp->load_config(config, version, server_id_list);
- //获取有效的数据服务器
- group_info_tmp->find_available_server();
- }
- //由于配置文件变动,可能导致数据服务器列表中的内容也发生变化。若发送变化,就需要去掉过时的数据服务器信息
- set <uint64_t> map_id_list;
- set <uint64_t> should_del_id_list;
- server_info_map::iterator sit;
- for(sit = data_server_info_map.begin();
- sit != data_server_info_map.end(); ++sit) {
- map_id_list.insert(sit->second->server_id);
- }
- std::set_difference(map_id_list.begin(), map_id_list.end(),
- server_id_list.begin(), server_id_list.end(),
- inserter(should_del_id_list,
- should_del_id_list.begin()));
- for(set<uint64_t>::iterator it = should_del_id_list.begin();
- it != should_del_id_list.end(); ++it) {
- sit = data_server_info_map.find(*it);
- if(sit != data_server_info_map.end()) {
- sit->second->server_id = 0;
- data_server_info_map.erase(sit);
- }
- }
- for(group_info_map::iterator it = group_info_map_data.begin();
- it != group_info_map_data.end(); ++it) {
- it->second->correct_server_info(sync_config_server_id
- && sync_config_server_id !=
- util::local_server_ip::ip);
- }
- log_info("machine count: %d, group count: %d",
- data_server_info_map.size(), group_info_map_data.size());
- server_info_rw_locker.unlock();
- group_info_rw_locker.unlock();
- }
2.3 配置服务器状态检查
在my_server_conf_thread的run函数中,周期性检查配置服务器和数据服务器的状态,若状态发生变化,则做相应处理。若作为主配置服务器运行,该函数检查备配置服务器的状态,能检查到2种状态变化:
1) 备配置服务器down掉,不需发送主备配置服务器切换;
2) 备配置服务器up了,此时也不需要发生主备配置服务器切换;在这种情况下, 备份配置服务器需要和主配置服务同步数据;
若作为备配置服务器运行,则该函数将检查主配置服务器的状态,能检查到2种状态变化:
1)主配置服务器down了,发生主备配置服务器切换;
2)主配置服务器up,也发生主备配置服务器切换;若发生切换,作为主配置服务器运行的代码,会重新读取配配置文件;
本节分析,检查配置服务器状态的代码。上代码如下:
- void server_conf_thread::check_config_server_status(uint32_t loop_count)
- {
- //若只有一个配置服务器,则该函数无需做更多功能
- if(config_server_info_list.size() != 2U) {
- return;
- }
- bool master_status_changed = false;
- //每一个配置服务器记录其上一次收到对方心跳包的时机,根据这个时间,和一个全局的心跳时间,来检测另外一个配置服务器的状态
- uint32_t now = heartbeat_curr_time - TAIR_SERVER_DOWNTIME * 2;
- bool config_server_up = false;
- for(size_t i = 0; i < config_server_info_list.size(); i++) {
- server_info *server_info_tmp = config_server_info_list[i];
- //只检查另外一个配置服务器
- if(server_info_tmp->server_id == util::local_server_ip::ip) { // this is myself
- continue;
- }
- //如果超过TAIR_SERVER_DOWNTIME * 2时间内没有收到另外一个配置服务器的心跳包,该配置服务器如果其记录状态为alive,再次通过发送一个请求包到对端服务器,若对端服务器没有返回,则认为对端的配置服务器已经down了;
- if(server_info_tmp->last_time < now && server_info_tmp->status == server_info::ALIVE) { // downhost
- if(tbnet::ConnectionManager::isAlive(server_info_tmp->server_id) ==
- true)
- continue;
- server_info_tmp->status = server_info::DOWN;
- //若down掉的配置服务器,在配置文件中配置的为主配置服务器,则表示需要切换主配置服务器了
- if(i == 0)
- master_status_changed = true;
- log_warn("CONFIG HOST DOWN: %s",
- tbsys::CNetUtil::addrToString(server_info_tmp->server_id).
- c_str());
- }
- //若在TAIR_SERVER_DOWNTIME * 2内收到了对端配置服务器的心跳包,并且当前记录状态为down,则修改其为alive。这表示另外一个配置服务器已经活过来了(或许是人工重新启动的)
- else if(server_info_tmp->last_time > now && server_info_tmp->status == server_info::DOWN) { // uphost
- server_info_tmp->status = server_info::ALIVE;
- if(i == 0)
- master_status_changed = true;
- config_server_up = true;
- log_warn("CONFIG HOST UP: %s",
- tbsys::CNetUtil::addrToString(server_info_tmp->server_id).
- c_str());
- }
- }
- // 没有发生主配置服务器切换,并且备配置服务器起来了,需要将所有group的client version 和 server version都增加一个常量(这里是10)
- if(master_status_changed == false) {
- if(config_server_up
- && master_config_server_id == util::local_server_ip::ip) {
- uint64_t slave_id = get_slave_server_id();
- group_info_map::iterator it;
- group_info_rw_locker.rdlock();
- for(it = group_info_map_data.begin();
- it != group_info_map_data.end(); ++it) {
- // for we can't get peer's version from protocol, add 10 to client version and server
- it->second->inc_version(server_up_inc_step);
- it->second->send_server_table_packet(slave_id);
- log_warn("config server up and master not changed, version changed. "
- "group name: %s, client version: %u, server version: %u",
- it->second->get_group_name(), it->second->get_client_version(), it->second->get_server_version());
- }
- group_info_rw_locker.unlock();
- }
- //若没有发生主 备切换,这里将返回,这里需要注意的。
- return;
- }
- //代码执行到这里,表示发生主备切换
- server_info *server_info_master = config_server_info_list[0];
- if(server_info_master->status == server_info::ALIVE) {
- master_config_server_id = server_info_master->server_id;
- }
- else {
- master_config_server_id = util::local_server_ip::ip;
- }
- if(master_config_server_id == util::local_server_ip::ip) {
- //只有作为master的配置服务器才会执行下面代码,从本地配置文件中读取配置信息。这里为啥不将配置文件信息同步到备配置服务器上呢?
- const char *group_file_name =
- TBSYS_CONFIG.getString(CONFSERVER_SECTION, TAIR_GROUP_FILE, NULL);
- if(group_file_name == NULL) {
- log_error("not found %s:%s ", CONFSERVER_SECTION, TAIR_GROUP_FILE);
- return;
- }
- uint32_t curr_version = get_file_time(group_file_name);
- load_group_file(group_file_name, curr_version, 0);
- }
- log_warn("MASTER_CONFIG changed %s",
- tbsys::CNetUtil::addrToString(master_config_server_id).
- c_str());
- }
2.4 检查数据服务器状态
函数check_server_status函数完成数据服务器状态检查工作。上代码:
- void server_conf_thread::check_server_status(uint32_t loop_count)
- {
- set<group_info *>change_group_info_list;
- //收集需要重新建立对照表的group,在调用该函数之前,在检查配置服务器状态的函数过程中,还有在初始化的时候,group_info_map被首次填,有可能改变group_info_map数据结构;
- //当group的配置信息发生变化后,重建对照表标志就会被置位;
- group_info_map::iterator it;
- for(it = group_info_map_data.begin(); it != group_info_map_data.end();
- ++it) {
- if(it->second->is_need_rebuild()) {
- change_group_info_list.insert(it->second);
- }
- }
- server_info_map::iterator sit;
- for(sit = data_server_info_map.begin();
- sit != data_server_info_map.end(); ++sit) {
- //a bad one is alive again, we do not mark it ok unless some one tould us to.
- //administrator can touch group.conf to let these data serve alive again.
- uint32_t now; // this decide how many seconds since last heart beat, we will mark this data server as a bad one.
- if(sit->second->group_info_data) {
- now =
- heartbeat_curr_time -
- sit->second->group_info_data->get_server_down_time();
- }
- else {
- now = heartbeat_curr_time - TAIR_SERVER_DOWNTIME;
- }
- if(sit->second->status != server_info::DOWN) {
- //dataserver down了,也是通过上一次收到其心跳包的时间来判断的;
- if((sit->second->last_time < now
- && (sit->second->status == server_info::ALIVE
- && !tbnet::ConnectionManager::isAlive(sit->second->server_id)))
- || sit->second->status == server_info::FORCE_DOWN) { // downhost
- change_group_info_list.insert(sit->second->group_info_data);
- //修改dataserver的状态
- sit->second->status = server_info::DOWN;
- log_warn("HOST DOWN: %s lastTime is %u now is %u ",
- tbsys::CNetUtil::addrToString(sit->second->server_id).
- c_str(), sit->second->last_time, heartbeat_curr_time);
- // if (need add down server config) then set downserver in group.conf
- if (sit->second->group_info_data->get_pre_load_flag() == 1)
- {
- log_error("add down host: %s lastTime is %u now is %u ",
- tbsys::CNetUtil::addrToString(sit->second->server_id).
- c_str(), sit->second->last_time, heartbeat_curr_time);
- sit->second->group_info_data->add_down_server(sit->second->server_id);
- sit->second->group_info_data->set_force_send_table();
- }
- }
- }
- }
- // only master config server can update hashtable.
- if(master_config_server_id != util::local_server_ip::ip) {
- return;
- }
- //以下代码只会在主配置服务器中执行
- uint64_t slave_server_id = get_slave_server_id();
- group_info_rw_locker.rdlock();
- //需要检查是否数据迁移的相关状态,目前我还不明白hard check 和 check纠结各自目标是什么,这里后面慢慢分析
- if(loop_count % HARD_CHECK_MIG_COMPLETE == 0) {
- for(group_info_map::const_iterator it = group_info_map_data.begin();
- it != group_info_map_data.end(); it++) {
- it->second->hard_check_migrate_complete(slave_server_id);
- }
- }
- for(it = group_info_map_data.begin(); it != group_info_map_data.end();
- ++it) {
- it->second->check_migrate_complete(slave_server_id);
- // check if send server_table
- if (1 == it->second->get_send_server_table())
- {
- log_warn("group: %s need send server table", it->second->get_group_name());
- it->second->send_server_table_packet(slave_server_id);
- it->second->reset_force_send_table();
- }
- }
- if(change_group_info_list.size() == 0U) {
- group_info_rw_locker.unlock();
- return;
- }
- //收集需要重建对照表的group, 将group添加到builder_thread线程中
- set<group_info *>::iterator cit;
- tbsys::CThreadGuard guard(&mutex_grp_need_build);
- for(cit = change_group_info_list.begin();
- cit != change_group_info_list.end(); ++cit) {
- builder_thread.group_need_build.push_back(*cit);
- (*cit)->set_table_builded();
- }
- group_info_rw_locker.unlock();
- }