更新要点
- 为
DatabaseClient
配置了一个负责重连的异步守护线程
- 完成了
DatabaseClient
和HTTPServer
的解耦合
- 为每一个数据库客户端接口的异常处理处加入了被动检测连接状态
- 异步线程循环中加入了计数器,能够定时自动触发连接状态检测
- 解决了查询订单列表时导致客户端崩溃的问题
为什么引入守护线程
原先的数据库连接更多的偏向于能用就行的实验性功能,这导致了一个很严重的问题:
数据库连接不稳定,以及只连接一次数据库,导致断连之后只能通过重启整个容器的方式重新连接,运维成本过高
因此我们引入守护线程,专门异步地负责数据库的重连。这样就能大大提高数据库连接的健壮性。
代码结构解耦合
原先嵌入在HTTPServer
内的initDB()
接口暴露了DatabaseClient
内的init()
接口参数,导致这两个类的耦合度过高,一旦修改init()
接口的参数,initDB
也要跟着修改,这已经是非常明显的因为耦合度过高所造成的问题了,为此要通过修改代码的方式实现二者的解耦合
这里我们采用最简单的方法解耦合:
- 将原先的
DatabaseClient db
改为智能指针类型std::shared_ptr<DatabaseClient> db
HTTPServer
不再负责初始化db
,而是改为提供接口,由外部传入db
指针
DatabaseClient
的实例化在main
函数中完成,然后把指针传给HTTPServer
单例
改变后的main()
函数如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main(int argc, char *argv[]) { google::ParseCommandLineFlags(&argc,&argv,true); btyGoose::init_logger(FLAGS_log_release_mode,FLAGS_log_logfile,FLAGS_log_release_level); LOG_DEBUG("全局日志器初始化成功"); auto db = std::make_shared<btyGoose::DatabaseClient>(); db->init(FLAGS_db_user,FLAGS_db_password,FLAGS_db_host,FLAGS_db_port,FLAGS_db_database, std::chrono::seconds(FLAGS_db_reconnect_interval)); btyGoose::HTTPServer::getInstance()->setDB(db); btyGoose::HTTPServer::getInstance()->init(FLAGS_host,FLAGS_port); btyGoose::HTTPServer::getInstance()->start(); return 0; }
|
添加异步守护线程
- 增加对应的成员变量,相应地命令行参数也要增加,这里不赘述了
1 2 3 4 5 6 7 8 9 10 11
| private: std::atomic<bool> connected_= false; std::chrono::seconds reconnect_interval_; std::condition_variable reconnect_cv_; std::mutex reconnect_mtx_; std::thread reconnect_thread_; bool stop_reconnect_ = false;
void reconnectLoop(); bool checkConnection();
|
- 实现相关的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
|
void DatabaseClient::reconnectLoop() { int cnt = 0; while(!stop_reconnect_) { std::unique_lock<std::mutex> lock(reconnect_mtx_); reconnect_cv_.wait_for(lock,reconnect_interval_); if(!connected_ && !stop_reconnect_) { LOG_INFO("开始尝试重连"); try{ std::unique_lock<std::mutex> db_lock(mtx);
delete stmt; delete con;
sql::mysql::MySQL_Driver* driver = sql::mysql::get_driver_instance(); con = driver->connect("tcp://"+host+":"+port,user,password); con->setSchema(database); stmt = con->createStatement(); connected_ = true;
LOG_INFO("数据库成功重连喵~ (≧∇≦)ノ");
} catch(const sql::SQLException e){ LOG_ERROR("重连失败: {} [错误码: {}]", e.what(), e.getErrorCode()); } } else { cnt++; if(cnt == 50) { checkConnection(); cnt = 0; } }
} }
bool DatabaseClient::checkConnection() { std::unique_lock<std::mutex> lock(mtx); if(connected_ == false) return false; try{ if(con && con->isValid() && !con->isClosed()) { std::unique_ptr<sql::Statement> test_stmt(con->createStatement()); std::unique_ptr<sql::ResultSet> res(test_stmt->executeQuery("SELECT 1")); return true; } }catch (const sql::SQLException&){}
LOG_WARN("当前数据库连接已丢失"); connected_ = false; return false; }
|
- 修改
start()
函数和析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void DatabaseClient::start() { try { sql::mysql::MySQL_Driver *driver = sql::mysql::get_mysql_driver_instance(); LOG_INFO("开始连接数据库 tcp://{}:{}\n\tuser:{}",host,port,user); con = driver->connect("tcp://"+host+":"+port, user, password); con->setSchema("BeautyGoose"); stmt = con->createStatement(); connected_ = true; reconnect_thread_ = std::thread(std::bind(&DatabaseClient::reconnectLoop,this)); LOG_INFO("守护线程已启动"); } catch (sql::SQLException &e) { LOG_FATAL( "数据库连接失败喵!\n\t错误码: {}\n\t信息:",e.getErrorCode(),e.what()); exit(-1); } }
DatabaseClient::~DatabaseClient() { stop_reconnect_ = true; reconnect_cv_.notify_all(); if(reconnect_thread_.joinable()) { reconnect_thread_.join(); }
delete stmt; delete con; }
|
采用被动检测连接策略
为了防止检测连接状态过于频繁,挤占了正常的业务通信,这里选用了被动检测的策略,即出现的异常
被catch
后,在里面额外添加对连接状态的检测,也就是调用checkConnection()
函数
附加计数器激发检测策略
除了被动检测,我们额外添加了一个计数器检测的策略,在保持连接状态一定时间后(状态不变的原因可能是这段时间没有服务请求),主动检测一次连接状态。