注册
c使用dpi接口连接DM8主备,实现自动切换
技术分享/ 文章详情 /

c使用dpi接口连接DM8主备,实现自动切换

苏童 2024/12/13 524 0 0

提到c/c++ 开发与数据库相关的部分,我们总是会第一时间想到ODBC接口,但官方文档中还有c/c++接口并不只有ODBC,下边我们来将一下其中的ODBC/DPI如何使用以及它们的不同。

image20241208231715398.png

ODBC

DM ODBC 3.0 遵照 Microsoft ODBC 3.0 规范设计与开发,符合大家使用的使用习惯,也比较容易上手。以c++为例:

#include <iostream> #include <stdexcept> #include <sql.h> #include <sqltypes.h> #include <sqlext.h> using namespace std; /* 检测返回代码是否为成功标志,当为成功标志返回 true,否则返回 false */ #define RC_SUCCESSFUL(rc) ((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO) #define RC_NOTSUCCESSFUL(rc) (!(RC_SUCCESSFUL(rc))) void PrintODBCError(SQLSMALLINT handleType, SQLHANDLE handle) { SQLCHAR sqlState[6]; SQLCHAR message[SQL_MAX_MESSAGE_LENGTH]; SQLINTEGER nativeError; SQLSMALLINT textLength; SQLRETURN ret; SQLSMALLINT i = 1; while ((ret = SQLGetDiagRec(handleType, handle, i, sqlState, &nativeError, message, sizeof(message), &textLength)) != SQL_NO_DATA) { if (RC_SUCCESSFUL(ret)) { cerr << "SQLSTATE: " << sqlState << ", Native Error Code: " << nativeError << ", Message: " << message << endl; } i++; } } class ODBCConnection { public: ODBCConnection(const string& driverPath, const string& user, const string& pass) : henv(SQL_NULL_HENV), hdbc(SQL_NULL_HDBC), hstmt(SQL_NULL_HSTMT) { // 申请环境句柄 if (SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv) != SQL_SUCCESS) { throw runtime_error("Failed to allocate environment handle."); } // 设置 ODBC 版本 if (SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER) != SQL_SUCCESS) { throw runtime_error("Failed to set ODBC version."); } // 申请连接句柄 if (SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc) != SQL_SUCCESS) { throw runtime_error("Failed to allocate connection handle."); } // 动态加载驱动程序并连接到数据库 string connStr = "DRIVER={" + driverPath + "};" "SERVER=localhost;PORT=5236;" "UID=" + user + ";" "PWD=" + pass + ";"; SQLRETURN sret = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr.c_str(), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE); if (RC_NOTSUCCESSFUL(sret)) { PrintODBCError(SQL_HANDLE_DBC, hdbc); throw runtime_error("ODBC: Fail to connect to server!"); } cout << "ODBC: Connected to server successfully!" << endl; } ~ODBCConnection() { // 释放资源 if (hstmt != SQL_NULL_HSTMT) { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); } if (hdbc != SQL_NULL_HDBC) { SQLDisconnect(hdbc); SQLFreeHandle(SQL_HANDLE_DBC, hdbc); } if (henv != SQL_NULL_HENV) { SQLFreeHandle(SQL_HANDLE_ENV, henv); } } void executeQuery(const string& query) { // 申请一个语句句柄 if (SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) != SQL_SUCCESS) { throw runtime_error("Failed to allocate statement handle."); } // 执行 SQL 查询 SQLRETURN sret = SQLExecDirect(hstmt, (SQLCHAR*)query.c_str(), SQL_NTS); if (RC_NOTSUCCESSFUL(sret)) { PrintODBCError(SQL_HANDLE_STMT, hstmt); throw runtime_error("ODBC: Query execution failed."); } SQLFreeHandle(SQL_HANDLE_STMT, hstmt); } void fetchResults() { SQLLEN out_c1_ind = 0, out_c2_ind = 0; int out_c1 = 0; SQLCHAR out_c2[20] = { 0 }; // 申请一个语句句柄 if (SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) != SQL_SUCCESS) { throw runtime_error("Failed to allocate statement handle."); } // 执行 SELECT 查询 SQLRETURN sret = SQLExecDirect(hstmt, (SQLCHAR*)"select * from TEST.MY_TEST", SQL_NTS); if (RC_NOTSUCCESSFUL(sret)) { PrintODBCError(SQL_HANDLE_STMT, hstmt); throw runtime_error("ODBC: SELECT query failed."); } // 绑定列 SQLBindCol(hstmt, 1, SQL_C_SLONG, &out_c1, sizeof(out_c1), &out_c1_ind); SQLBindCol(hstmt, 2, SQL_C_CHAR, &out_c2, sizeof(out_c2), &out_c2_ind); cout << "odbc: select from table..." << endl; while (SQLFetch(hstmt) != SQL_NO_DATA) { cout << "c1 = " << out_c1 << ", c2 = " << out_c2 << endl; } cout << "odbc: select success" << endl; SQLFreeHandle(SQL_HANDLE_STMT, hstmt); } private: HENV henv; // 环境句柄 HDBC hdbc; // 连接句柄 HSTMT hstmt; // 语句句柄 }; int main() { try { // 创建连接对象并执行操作 ODBCConnection conn("/home/dmdba/dmdbms/bin/libdodbc.so", "SYSDBA", "SYSDBA"); // 执行插入、删除、更新操作 conn.executeQuery("delete from TEST.MY_TEST"); conn.executeQuery("insert into TEST.MY_TEST(C2) values('语文'), ('数学'), ('英语'), ('体育')"); conn.executeQuery("delete from TEST.MY_TEST where C2='数学'"); conn.executeQuery("update TEST.MY_TEST set C2 = '英语-新课标' where C2='英语'"); // 查询数据 conn.fetchResults(); } catch (const runtime_error &e) { cerr << "Error: " << e.what() << endl; } return 0; }

提前准备环境,搭建DW,建表

CREATE TABLE "TEST"."MY_TEST" ( "C1" INT, "C2" VARCHAR(40)) STORAGE(ON "MAIN", CLUSTERBTR) ADD LOGIC LOG ;

安装基础软件

[root@localhost ~]# yum install unixODBC-devel gcc-c++ [root@localhost ~]# g++ --version g++ (GCC) 7.3.0 Copyright © 2017 Free Software Foundation, Inc. 本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保; 包括没有适销性和某一专用目的下的适用性担保。

编译运行

[dmdba@localhost c++]$ g++ -std=c++17 -I/home/dmdba/dmdbms/include -L/home/dmdba/dmdbms/bin -ldodbc -o curd curd.cpp [dmdba@localhost c++]$ ./curd ODBC: Connected to server successfully! odbc: select from table... c1 = 0, c2 = 语文 c1 = 0, c2 = 英语-新课标 c1 = 0, c2 = 体育 odbc: select success

主备切换

#include <sql.h> #include <sqlext.h> #include <iostream> #include <unistd.h> // 定义成功标志的宏 #define RC_SUCCESSFUL(rc) ((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO) #define RC_NOTSUCCESSFUL(rc) (!(RC_SUCCESSFUL(rc))) // 连接数据库函数 SQLRETURN connect_to_db(SQLHDBC *hdbc, const char *connectionString) { SQLRETURN ret = SQLDriverConnect(*hdbc, NULL, (SQLCHAR *)connectionString, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE); return ret; } // 检查数据库连接状态的函数 bool check_connection(SQLHDBC hdbc) { SQLHSTMT hstmt; SQLRETURN ret; // 创建语句句柄 SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); // 执行简单查询检查连接 ret = SQLExecDirect(hstmt, (SQLCHAR *)"SELECT 1 FROM DUAL", SQL_NTS); // 释放语句句柄 SQLFreeHandle(SQL_HANDLE_STMT, hstmt); // 如果查询成功则认为连接有效 return (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO); } // 打印ODBC错误信息的函数 void PrintODBCError(SQLSMALLINT handleType, SQLHANDLE handle) { SQLCHAR sqlState[6]; SQLCHAR message[SQL_MAX_MESSAGE_LENGTH]; SQLINTEGER nativeError; SQLSMALLINT textLength; SQLRETURN ret; SQLSMALLINT i = 1; while ((ret = SQLGetDiagRec(handleType, handle, i, sqlState, &nativeError, message, sizeof(message), &textLength)) != SQL_NO_DATA) { if (RC_SUCCESSFUL(ret)) { std::cerr << "SQLSTATE: " << sqlState << ", Native Error Code: " << nativeError << ", Message: " << message << std::endl; } i++; } } // 查询 test 表的函数 void query_test_table(SQLHDBC hdbc) { SQLHSTMT hstmt; SQLRETURN ret; SQLCHAR query[] = "SELECT * FROM TEST.MY_TEST LIMIT 1"; // 查询 test 表的所有记录 SQLINTEGER c1; SQLCHAR c2[40]; // 创建语句句柄 SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); // 执行查询 ret = SQLExecDirect(hstmt, query, SQL_NTS); if (RC_SUCCESSFUL(ret)) { // 绑定列 SQLBindCol(hstmt, 1, SQL_C_LONG, &c1, sizeof(c1), NULL); SQLBindCol(hstmt, 2, SQL_C_CHAR, c2, sizeof(c2), NULL); // 逐行提取结果 while (SQLFetch(hstmt) == SQL_SUCCESS) { std::cout << "C1: " << c1 << ", C2: " << c2 << std::endl; } } else { std::cerr << "查询失败!" << std::endl; } // 释放语句句柄 SQLFreeHandle(SQL_HANDLE_STMT, hstmt); } int main() { SQLHENV henv; SQLHDBC hdbc; SQLRETURN ret; const char *dsn_primary = "DRIVER={/home/dmdba/dmdbms/bin/libdodbc.so};SERVER=192.168.131.152;PORT=5236;UID=SYSDBA;PWD=SYSDBA;"; const char *dsn_standby = "DRIVER={/home/dmdba/dmdbms/drivers/odbc/libdodbc.so};SERVER=192.168.131.153;PORT=5237;UID=SYSDBA;PWD=SYSDBA;"; std::string current_db_ip; // 申请环境句柄 SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv); SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER); // 申请连接句柄 SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); // 尝试连接主库 ret = connect_to_db(&hdbc, dsn_primary); if (RC_NOTSUCCESSFUL(ret)) { std::cout << "主库连接失败,尝试连接从库..." << std::endl; // 断开当前连接并释放资源 SQLDisconnect(hdbc); // 断开当前连接 SQLFreeHandle(SQL_HANDLE_DBC, hdbc); // 释放数据库连接句柄 // 重新申请连接句柄 SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); // 尝试连接从库 ret = connect_to_db(&hdbc, dsn_standby); if (RC_NOTSUCCESSFUL(ret)) { std::cerr << "无法连接到主库或从库。" << std::endl; PrintODBCError(SQL_HANDLE_DBC, hdbc); return 1; } else { current_db_ip = "192.168.131.153"; // 设置为从库的 IP std::cout << "连接到从库成功! 当前连接的数据库 IP: " << current_db_ip << std::endl; } } else { current_db_ip = "192.168.131.152"; // 设置为主库的 IP std::cout << "连接到主库成功! 当前连接的数据库 IP: " << current_db_ip << std::endl; } // 持续监控主库连接 while (true) { // 检查主库连接是否正常 if (!check_connection(hdbc)) { std::cout << "主库不可达,切换到从库..." << std::endl; // 断开当前连接并释放资源 SQLDisconnect(hdbc); // 断开当前连接 SQLFreeHandle(SQL_HANDLE_DBC, hdbc); // 释放数据库连接句柄 // 重新申请连接句柄 SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); // 主库不可用时,尝试连接到从库 ret = connect_to_db(&hdbc, dsn_standby); if (RC_SUCCESSFUL(ret)) { current_db_ip = "192.168.131.153"; // 设置为从库的 IP std::cout << "切换到从库成功! 当前连接的数据库 IP: " << current_db_ip << std::endl; } else { std::cerr << "无法连接到从库。" << std::endl; PrintODBCError(SQL_HANDLE_DBC, hdbc); } } // 持续查询 test 表,展示自动切换的过程 query_test_table(hdbc); // 休眠 5 秒再执行下一次查询 sleep(5); // 每5秒查询一次 } // 释放句柄 SQLFreeHandle(SQL_HANDLE_DBC, hdbc); SQLFreeHandle(SQL_HANDLE_ENV, henv); return 0; }

代码为了演示,连接配置,表名等都是硬编码,生产中切忌这样做,可以作为参数传入,或使用配置文件。

从ODBC实现来看,数据库主备切换时,驱动中的功能明显不够用,需要我们自己去写一个连接池来实现。

做一个连接池的工作量是比较大的,需要考虑的事情比较多:

1. 基本功能
1.连接复用:维护一定数量的连接对象,减少频繁建立和关闭连接的开销。
2.最大连接数限制:防止连接池无限增长导致资源耗尽。
3.连接超时管理:处理长时间未使用或卡死的连接。
4.负载均衡:当后端是分布式数据库时,需要智能分配连接到不同的节点。
5.连接生命周期:检测连接的健康状态并在必要时重新建立。
2. 支持复杂架构
1.主备架构
主库读写分离:为写操作优先选择主库,为读操作选择备库。
主库故障切换:实时监控主库健康状况,自动切换到备库。
2.共享存储
确保每个实例对同一存储的一致性操作。
提供全局锁机制防止竞争条件。
3.分布式架构
节点发现机制:动态感知新增或失效的数据库节点。
数据分片:根据请求的分片键选择目标节点。
全局事务支持:需要集成分布式事务管理器(如两阶段提交或三阶段提交)。
3. 性能优化
1.连接池大小:根据并发量和数据库性能调整连接池的最大、最小大小。
2.并发控制:高效的线程安全机制,避免线程争用。
3.缓存机制:对频繁查询的结果进行缓存,减少数据库查询压力。
4.最小化网络延迟:通过优化协议和批量操作减少网络开销。
4. 异常处理
1.数据库连接失败的重试机制
2.连接泄漏检测:检查应用是否正确归还连接。
3.日志与监控:提供详细的运行日志和监控接口,便于调试和性能分析。

特别c/c++这类基础语言要做的事情就更多了:

1. 系统资源管理
1.内存管理
连接对象的内存分配和释放需要严格控制,避免内存泄漏。
使用智能指针(如malloc/free配对或C库工具)管理动态内存。
2.文件描述符管理
数据库连接通常会占用大量文件描述符,需要避免句柄泄漏。
定期检查和关闭闲置的连接句柄。
2. 网络通信
1.高效的Socket管理
支持非阻塞I/O,避免阻塞线程。
使用高效的事件驱动模型(如epollkqueueselect)。
2.通信协议实现
如果使用特定数据库协议,需要自行实现其解析逻辑。
考虑协议压缩和安全性(如TLS)。
3. 多线程编程
1.线程安全
使用线程安全的数据结构(如队列、哈希表)。
利用同步原语(如互斥锁、条件变量)保护共享资源。
2.无锁编程
在性能敏感场景下,可以尝试无锁队列或原子操作优化多线程访问。
4. 数据一致性
1.事务支持
在连接池中维护事务上下文,确保多线程环境下事务的独立性。
2.故障恢复
当数据库连接中断时,需要重新建立连接并恢复状态。
5. 依赖库与工具
1.使用成熟的C库
避免从零开发,除非有特殊需求。选择依赖库是尽量选择成熟通用的。
2.调试工具
使用valgrind检测内存泄漏。
通过gdb调试并发问题。
6. 性能优化
1.内存池
为频繁创建和销毁的对象(如连接结构体)建立内存池。
2.CPU缓存优化
数据结构设计要考虑缓存友好性(如避免过多的指针跳转)。
3.批量操作
合并小型数据库操作,减少请求数量。
7. 跨平台兼容性
1.系统API差异
注意不同操作系统的线程、网络I/O、定时器API差异。
2.编译环境
建议使用跨平台工具生成编译配置。

那为了实现可以故障自动切换,有没有更好的办法呢?答案:DPI。

DPI

上文使用了c++做演示,这里就使用c来做,先来展示如何做最基础的dml操作

#include <stdio.h> #include <stdlib.h> #include <string.h> #include "DPI.h" #include "DPIext.h" #include "DPItypes.h" #define DM_SVR "192.168.131.153:5237" #define DM_USER "SYSDBA" #define DM_PWD "SYSDBA" dhenv henv; /* 环境句柄 */ dhcon hcon; /* 连接句柄 */ dhstmt hstmt; /* 语句句柄 */ dhdesc hdesc; /* 描述符句柄 */ DPIRETURN rt; /* 函数返回值 */ /****************************************************** Notes: 获取错误信息 *******************************************************/ void dpi_err_msg_print(sdint2 hndl_type, dhandle hndl) { sdint4 err_code; sdint2 msg_len; sdbyte err_msg[SDBYTE_MAX]; /* 获取错误信息集合 */ dpi_get_diag_rec(hndl_type, hndl, 1, &err_code, err_msg, sizeof(err_msg), &msg_len); printf("err_msg = %s, err_code = %d\n", err_msg, err_code); } /* 入口函数 */ int main(int argc, char *argv[]) { sdint4 out_c1 = 0; sdbyte out_c2[20]= { 0 }; slength out_c1_ind = 0; slength out_c2_ind = 0; ulength row_num; //连接数据库 rt = dpi_alloc_env(&henv); rt = dpi_alloc_con(henv, &hcon); rt = dpi_login(hcon, (sdbyte *)DM_SVR, (sdbyte *)DM_USER, (sdbyte *)DM_PWD); if(!DSQL_SUCCEEDED(rt)) { dpi_err_msg_print(DSQL_HANDLE_DBC, hcon); return rt; } rt = dpi_alloc_stmt(hcon, &hstmt); //清空表,初始化测试环境 rt = dpi_exec_direct(hstmt, (sdbyte*) "delete from TEST.MY_TEST"); //插入数据 rt = dpi_exec_direct(hstmt, (sdbyte*) "insert into TEST.MY_TEST(C2) values('语文'), ('数学'), ('英语'), ('体育') "); if(!DSQL_SUCCEEDED(rt)) { dpi_err_msg_print(DSQL_HANDLE_STMT, hstmt); return rt; } printf("dpi: insert success\n"); //删除数据 rt = dpi_exec_direct(hstmt, (sdbyte*) "delete from TEST.MY_TEST where C2='数学' "); if(!DSQL_SUCCEEDED(rt)) { dpi_err_msg_print(DSQL_HANDLE_STMT, hstmt); return rt; } printf("dpi: delete success\n"); //更新数据 rt = dpi_exec_direct(hstmt, (sdbyte*) "update TEST.MY_TEST set C2 = '英语-新课标' where C2='英语' "); if(!DSQL_SUCCEEDED(rt)) { dpi_err_msg_print(DSQL_HANDLE_STMT, hstmt); return rt; } printf("dpi: update success\n"); //查询数据 dpi_exec_direct(hstmt, (sdbyte*) "select * from TEST.MY_TEST"); dpi_bind_col(hstmt, 1, DSQL_C_SLONG, &out_c1, sizeof(out_c1), &out_c1_ind); dpi_bind_col(hstmt, 2, DSQL_C_NCHAR, &out_c2, sizeof(out_c2), &out_c2_ind); printf("dpi: select from table...\n"); while(dpi_fetch(hstmt, &row_num) != DSQL_NO_DATA) { printf("c1 = %d, c2 = %s ,\n", out_c1, out_c2); } printf("dpi: select success\n"); //断开数据库连接 rt = dpi_logout(hcon); if(!DSQL_SUCCEEDED(rt)) { dpi_err_msg_print(DSQL_HANDLE_DBC, hcon); return rt; } rt = dpi_free_con(hcon); rt = dpi_free_env(henv); return rt; }

编译运行

[dmdba@localhost c]$ gcc -I/home/dmdba/dmdbms/include -L/home/dmdba/dmdbms/bin -ldmdpi -o dpiDml dpiDml.c [dmdba@localhost c]$ ./dpiDml dpi: insert success dpi: delete success dpi: update success dpi: select from table... c1 = 0, c2 = 语文 , c1 = 0, c2 = 英语-新课标 , c1 = 0, c2 = 体育 , dpi: select success

自动切换

接下来演示使用dm_svc.conf连接主备。

export DM_SVC_PATH=/etc

配置dm_svc.conf文件

DMDW=(192.168.131.152:5236,192.168.131.153:5237) 

[DMDW] 
LOGIN_MODE=(1)

SWITCH_TIMES=(60)  
SWITCH_INTERVAL=(1000) 
AUTO_RECONNECT=(1)

image20241209092712429.png

AUTO_RECONNECT

连接发生异常或一些特殊场景下连接处理策略。0:关闭连接;1:当连接发生异常时自动切换到其他库,无论切换成功还是失败都会抛一个 SQLException,用于通知上层应用进行事务执行失败时的相关处理;2:配合 EP_SELECTOR>=1 使用,如果服务名列表前面的节点恢复了,将当前连接切换到前面的节点上;4:保持各节点会话动态均衡,通过后台线程检测节点及会话数变化,并切换连接使之保持均衡。也可以将 AUTO_RECONNECT 置为上述几个值的组合值,表示同时进行多项配置,如置为 3 表示同时配置 1 和 2。缺省值为 0。

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "DPI.h" #include "DPIext.h" #include "DPItypes.h" #define DM_SVR "DMDW" #define DM_USER "SYSDBA" #define DM_PWD "SYSDBA" dhenv henv; /* 环境句柄 */ dhcon hcon; /* 连接句柄 */ DPIRETURN rt; /* 函数返回值 */ /****************************************************** Notes: 获取错误信息 *******************************************************/ void dpi_err_msg_print(sdint2 hndl_type, dhandle hndl) { sdint4 err_code; sdint2 msg_len; sdbyte err_msg[SDBYTE_MAX]; /* 获取错误信息集合 */ dpi_get_diag_rec(hndl_type, hndl, 1, &err_code, err_msg, sizeof(err_msg), &msg_len); printf("err_msg = %s, err_code = %d\n", err_msg, err_code); } /* 入口函数 */ int main(int argc, char *argv[]) { sdbyte sess_id[20] = {0}; sdbyte user_name[50] = {0}; sdbyte instance_name[50] = {0}; sdbyte clnt_type[20] = {0}; sdbyte clnt_ip[50] = {0}; sdbyte last_recv_time[50] = {0}; slength sess_id_ind = 0, user_name_ind = 0, instance_name_ind = 0, clnt_type_ind = 0; slength clnt_ip_ind = 0, last_recv_time_ind = 0; //连接数据库 /* 申请环境句柄 */ rt = dpi_alloc_env(&henv); if (!DSQL_SUCCEEDED(rt)) { printf("Failed to allocate environment handle\n"); return rt; } /* 申请连接句柄 */ rt = dpi_alloc_con(henv, &hcon); if (!DSQL_SUCCEEDED(rt)) { printf("Failed to allocate connection handle\n"); dpi_free_env(henv); return rt; } /* 连接数据库服务器 */ rt = dpi_login(hcon, (sdbyte *)DM_SVR, (sdbyte *)DM_USER, (sdbyte *)DM_PWD); if (!DSQL_SUCCEEDED(rt)) { dpi_err_msg_print(DSQL_HANDLE_DBC, hcon); dpi_free_con(hcon); dpi_free_env(henv); return rt; } printf("dpi: connect to server success!\n"); /* 构造循环查询 */ while (1) { dhstmt hstmt; /* 循环内申请的语句句柄 */ /* 申请语句句柄 */ rt = dpi_alloc_stmt(hcon, &hstmt); if (!DSQL_SUCCEEDED(rt)) { printf("Failed to allocate statement handle\n"); dpi_err_msg_print(DSQL_HANDLE_DBC, hcon); sleep(1); continue; } // 执行查询 rt = dpi_exec_direct(hstmt, (sdbyte *)"select a.SESS_ID, a.USER_NAME, b.instance_name, a.CLNT_TYPE, " "a.CLNT_IP, a.LAST_RECV_TIME " "from v$sessions a, v$instance b " "where SESS_ID = SYS_CONTEXT('USERENV', 'sessionid')"); if (!DSQL_SUCCEEDED(rt)) { // 打印错误信息,不退出程序 printf("Query failed, continuing to next iteration...\n"); dpi_err_msg_print(DSQL_HANDLE_STMT, hstmt); dpi_free_stmt(hstmt); // 释放语句句柄 sleep(1); // 延迟1秒,防止频繁查询 continue; } // 绑定列 dpi_bind_col(hstmt, 1, DSQL_C_NCHAR, sess_id, sizeof(sess_id), &sess_id_ind); dpi_bind_col(hstmt, 2, DSQL_C_NCHAR, user_name, sizeof(user_name), &user_name_ind); dpi_bind_col(hstmt, 3, DSQL_C_NCHAR, instance_name, sizeof(instance_name), &instance_name_ind); dpi_bind_col(hstmt, 4, DSQL_C_NCHAR, clnt_type, sizeof(clnt_type), &clnt_type_ind); dpi_bind_col(hstmt, 5, DSQL_C_NCHAR, clnt_ip, sizeof(clnt_ip), &clnt_ip_ind); dpi_bind_col(hstmt, 6, DSQL_C_NCHAR, last_recv_time, sizeof(last_recv_time), &last_recv_time_ind); printf("Querying session information...\n"); // 获取结果 while (dpi_fetch(hstmt, NULL) != DSQL_NO_DATA) { printf("SESS_ID: %s, USER_NAME: %s, INSTANCE_NAME: %s, CLNT_TYPE: %s, " "CLNT_IP: %s, LAST_RECV_TIME: %s\n", sess_id, user_name, instance_name, clnt_type, clnt_ip, last_recv_time); } // 释放游标以准备下次查询 dpi_close_cursor(hstmt); // 释放语句句柄 dpi_free_stmt(hstmt); // 延迟1秒 sleep(1); } //断开数据库连接 rt = dpi_logout(hcon); if (!DSQL_SUCCEEDED(rt)) { dpi_err_msg_print(DSQL_HANDLE_DBC, hcon); } printf("dpi: disconnect from server success!\n"); /* 释放连接句柄和环境句柄 */ rt = dpi_free_con(hcon); rt = dpi_free_env(henv); return rt; }
gcc -I/home/dmdba/dmdbms/include -L/home/dmdba/dmdbms/bin -ldmdpi -o dpiConn dpiConn.c ./dpiConn

运行期间关闭当前连接的数据库,我这里首先连接的DMSTANDBY,所以我直接关闭了192.168.131.153虚拟机。也可以关掉数据库。

systemctl stop DmServiceDMSERVICE.service

image20241209112628399.png

image20241209112229368.png
可见运行过程中在原主库挂掉时,在短暂等待后,程序自动切换了数据库。而代码中并没有做重连的操作,这是DPI帮我们实现的。大家在开发过程中要注意申请语句句柄的时机。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服