使用mysqlx操作MySQL8.0数据库时遭遇CDK Error: Connection refused (generic:111)错误

前言

游戏服务器一个配置存储服务,使用 mysql-connector-c++ 8.0 连接 MySQL 8.0 数据库,使用 MySQL X Protocol, 它是 MySQL 8.0 引入的一种新的通信协议,它是为了支持现代应用程序开发需求而设计的。除了关系型数据存储外,MySQL X Protocol 可以方便地处理文档存储,允许存储和检索 JSON 等文档类型的数据,使其更适合现代的 NoSQL 风格的开发模式。该协议支持异步操作,提高了性能,允许更高效的并发处理,特别是在处理大量请求时。支持更丰富的数据操作,包括 CRUD 操作、事务处理、SQL 语句执行,并且可以处理复杂的查询,例如对文档存储的查询。

mysqlx 是 MySQL 提供的用于与 MySQL 服务器进行交互的 C++ 客户端库,基于 MySQL X Protocol 开发。它提供了一组 C++ API,使开发人员可以在 C++ 程序中使用 MySQL 服务器的功能。而我就是用这套API是遇到了问题,本来这套功能在本地测试时很顺畅,没有发现问题,但是放到线上环境时就发现无法存储数据,调试线上环境还是有一些难度的。

因为线上程序编译为release版本,几乎没有可以调试的符号,起初还怀疑是release版本有问题,但是本地编译成release版本依旧正常,所以我就迅速用一个debug版本的so库替换了线上的release版本库,结果废了半天劲就找到了 CDK Error: Connection refused (generic:111) 这个错误,对于这个问题的处理还查了很多解决办法,但回过头来看,这个提示已经很明显了,就是不让连接呗,应该第一时间想到连接权限问题的,但是这有点马后炮,关于这个问题还是走了不少弯路,不过也学到了很多,总结一下做个备忘录。

问题现场

查找object时的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
mysqlx::Session sess = sqlClient->getSession();
mysqlx::DocResult result = sess
.getDefaultSchema()
.getCollection(collectionName)
.find(searchPattern)
.execute();

mysqlx::DbDoc doc;
while (doc = result.fetchOne())
{
// ...
}

添加新object时的实例代码:

1
2
3
4
5
6
7
8
mysqlx::Session sess = sqlClient->getSession();
mysqlx::Result result = sess
.getDefaultSchema()
.createCollection(collectionName, true)
.add(jsonString)
.execute();

return result.getAffectedItemsCount() > 0;

报错的函数主要集中在上面两段逻辑,实际上通过debug库断点,已经能定位到 mysqlx::Session sess = sqlClient->getSession(); 这一句就开始报异常了,异常类型 catch (const mysqlx::Error& err) { ... },打印信息也就是标题中看到的 CDK Error: Connection refused (generic:111)。

这里的Collection相当于关系数据库中的一个表,实际上可以通过关系数据库导出的方式,将Collection导出或者导出,仅导出表结构的命令

1
mysqldump -u username -p --no-data source_database table_name > table_structure.sql

导出的 table_structure.sql 内容示例

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
-- MySQL dump 10.13  Distrib 8.0.24, for Linux (x86_64)
--
-- Host: localhost Database: source_database
-- ------------------------------------------------------
-- Server version 8.0.24

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `GlobalConfig`
--

DROP TABLE IF EXISTS `GlobalConfig`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `GlobalConfig` (
`doc` json DEFAULT NULL,
`_id` varbinary(32) GENERATED ALWAYS AS (json_unquote(json_extract(`doc`,_utf8mb4'$._id'))) STORED NOT NULL,
`_json_schema` json GENERATED ALWAYS AS (_utf8mb4'{"type":"object"}') VIRTUAL,
PRIMARY KEY (`_id`),
CONSTRAINT `$val_strict_64BAB193F9193856B3219B21CF70823072B92EB1` CHECK (json_schema_valid(`_json_schema`,`doc`)) /*!80016 NOT ENFORCED */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2024-12-19 23:03:52

简单的导入命令

1
mysql -u username -p target_database < table_name.sql

解决问题

有经验之后可以从提示 CDK Error: Connection refused (generic:111) 推断出是连接权限问题,但要准确找到问题点还需要逐步排查

  1. MySQL绑定的IP地址是多少?允许哪些主机访问
  2. MySQL监听的端口是多少?允许通过哪些端口访问
  3. MySQL中定义了哪些用户?哪些用户可以访问
  4. MySQL中定义的用户绑定了哪些主机?已存在的用户可以通过哪些主机访问
  5. MySQL中绑定了主机的用户有哪些权限?可以访问的用户是否有指定的操作权限,比如增加、删除等

这么一看还真是挺麻烦的,不过不用担心,当处理的次数多了自然而然就理解了,先看前两个问题,MySQL绑定的IP和端口吧

MySQL绑定IP和端口

他们配置在文件 /etc/mysql/mysql.conf.d/mysqld.cnf 中,这里面文件名很相似,我的环境是在Ubuntu20.04,版本 目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# tree /etc/mysql
/etc/mysql
├── conf.d
│ ├── mysql.cnf
│ └── mysqldump.cnf
├── debian.cnf
├── debian-start
├── my.cnf -> /etc/alternatives/my.cnf
├── my.cnf.fallback
├── mysql.cnf
└── mysql.conf.d
├── mysql.cnf
└── mysqld.cnf

部分文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[mysqld]
#
# * Basic Settings
#
user = mysql
# pid-file = /var/run/mysqld/mysqld.pid
# socket = /var/run/mysqld/mysqld.sock
port = 3306
# datadir = /var/lib/mysql


# If MySQL is running as a replication slave, this should be
# changed. Ref https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_tmpdir
# tmpdir = /tmp
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
bind-address = 127.0.0.1
mysqlx-bind-address = 127.0.0.1

无论是传统的mysql协议还是新的mysqlx协议,都默认允许本机回环地址 127.0.0.1 连接,mysql协议默认端口3306,mysqlx默认端口33060,如果想修改在文件 mysqld.cnf 修改后重启MySQL服务 sudo systemctl restart mysql 既可以了,可以查询MySQL变量看看目前生效的配置值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> SHOW VARIABLES LIKE '%bind%';
+---------------------+-----------+
| Variable_name | Value |
+---------------------+-----------+
| bind_address | 127.0.0.1 |
| mysqlx_bind_address | 127.0.0.1 |
+---------------------+-----------+
2 rows in set (0.00 sec)

mysql> SHOW VARIABLES LIKE '%port%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| mysqlx_port | 33060 |
| port | 3306 |
+--------------------------+-------+

对于 MySQL 服务绑定地址 0.0.0.0192.168.1.100127.0.0.1localhost 有一些差异,其中192.168.1.100为本机内网地址,对比结果整理如下:

对比维度 0.0.0.0 192.168.1.100 127.0.0.1 localhost
含义 监听所有网络接口(本机、内网、外网)。 监听特定的本机网络接口,仅允许特定 IP 连接。 仅监听本机回环接口,仅限本机访问。 通常解析为 Unix Socket 或 127.0.0.1
访问范围 所有来源(包括本地和远程)。 仅允许通过指定的网络接口访问(如内网 IP)。 仅限本机访问,无法从外部访问。 仅限本机访问,无法从外部访问。
使用协议 强制使用 TCP/IP 协议。 强制使用 TCP/IP 协议。 强制使用 TCP/IP 协议。 优先使用 Unix Socket,无法使用时退回到 TCP/IP 协议。
性能 可能因监听所有接口稍有性能影响。 仅监听单个接口,性能略高于 0.0.0.0 受限于网络栈处理效率。 使用 Unix Socket 时性能最高;TCP/IP 时与 127.0.0.1 相同。
安全性 最不安全,需防火墙限制来源以防外网暴露。 较为安全,仅允许指定接口连接,适合内网使用。 高安全性,仅限本机访问,不暴露给外部网络。 高安全性,仅限本机访问,Unix Socket 更加安全。
适用场景 内网/外网连接,需严格配置防火墙规则。 局域网访问,仅需特定网络接口的连接。 单机应用或本地调试环境,仅限本机访问。 本地高频操作,优先使用更高效的 Unix Socket。
配置方法 bind-address = 0.0.0.0 bind-address = 192.168.1.100 bind-address = 127.0.0.1 bind-address = 127.0.0.1(间接实现)。
访问示例 所有主机(如 mysql -h <任意IP>)。 指定内网 IP(如 mysql -h 192.168.1.100)。 本机访问(如 mysql -h 127.0.0.1)。 本机访问(如 mysql -h localhost)。

MySQL用户和绑定的主机

在MySQL中不仅定义了用户和对应权限,还规定了用户绑定的主机,也就是说权限不仅仅分配给一个用户的,而是分配给通过指定机器访问的用户的,单单这么说对于没有操作的人来说可能有些懵,我们可以实际操作一下:

通过 SELECT DISTINCT CONCAT('User: ''',user,'''@''',host,''';') AS query FROM mysql.user; 可以查询用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> SELECT DISTINCT CONCAT('User: ''',user,'''@''',host,''';') AS query FROM mysql.user;
+---------------------------------------+
| query |
+---------------------------------------+
| User: 'webview'@'%'; |
| User: 'comm'@'192.168.1.100'; |
| User: 'comm'@'localhost'; |
| User: 'debian-sys-maint'@'localhost'; |
| User: 'admin'@'localhost'; |
| User: 'mysql.infoschema'@'localhost'; |
| User: 'mysql.session'@'localhost'; |
| User: 'mysql.sys'@'localhost'; |
| User: 'root'@'localhost'; |
| User: 'zone0001'@'localhost'; |
+---------------------------------------+
11 rows in set (0.00 sec)

用户名加上后面的主机名才是权限授予的主体,可以看到comm这个用户名可以通过localhost本机访问,也可以通过 192.168.1.100 这台机器访问,如果还需要通过 192.168.1.200,那么还需要创建一个 'comm'@'192.168.1.200' 的用户并授予权限才可以。

如果想要一个用户可以在任何主机上登录访问MySQL,主机部分用 % 代替,就比如 'webview'@'%' 这个用户,不过不建议这样做,安全性明显降低。

MySQL授予和回收权限

查询权限可以通过 show grants for 用户 查询,测试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql> show grants for 'comm'@'localhost';
+----------------------------------------------------------+
| Grants for comm@localhost |
+----------------------------------------------------------+
| GRANT USAGE ON *.* TO `comm`@`localhost` |
| GRANT ALL PRIVILEGES ON `mycomm`.* TO `comm`@`localhost` |
+----------------------------------------------------------+
2 rows in set (0.00 sec)

mysql> show grants for 'root'@'localhost';
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Grants for root@localhost |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE, CREATE ROLE, DROP ROLE ON *.* TO `root`@`localhost` WITH GRANT OPTION |
| GRANT APPLICATION_PASSWORD_ADMIN,AUDIT_ABORT_EXEMPT,AUDIT_ADMIN,AUTHENTICATION_POLICY_ADMIN,BACKUP_ADMIN,BINLOG_ADMIN,BINLOG_ENCRYPTION_ADMIN,CLONE_ADMIN,CONNECTION_ADMIN,ENCRYPTION_KEY_ADMIN,FIREWALL_EXEMPT,FLUSH_OPTIMIZER_COSTS,FLUSH_STATUS,FLUSH_TABLES,FLUSH_USER_RESOURCES,GROUP_REPLICATION_ADMIN,GROUP_REPLICATION_STREAM,INNODB_REDO_LOG_ARCHIVE,INNODB_REDO_LOG_ENABLE,PASSWORDLESS_USER_ADMIN,PERSIST_RO_VARIABLES_ADMIN,REPLICATION_APPLIER,REPLICATION_SLAVE_ADMIN,RESOURCE_GROUP_ADMIN,RESOURCE_GROUP_USER,ROLE_ADMIN,SENSITIVE_VARIABLES_OBSERVER,SERVICE_CONNECTION_ADMIN,SESSION_VARIABLES_ADMIN,SET_USER_ID,SHOW_ROUTINE,SYSTEM_USER,SYSTEM_VARIABLES_ADMIN,TABLE_ENCRYPTION_ADMIN,TELEMETRY_LOG_ADMIN,XA_RECOVER_ADMIN ON *.* TO `root`@`localhost` WITH GRANT OPTION |
| GRANT PROXY ON ``@`` TO `root`@`localhost` WITH GRANT OPTION |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

创建用户和授予权限的方法

1
2
3
CREATE USER 'comm'@'192.168.1.200' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON *.* TO 'comm'@'192.168.1.200';
FLUSH PRIVILEGES;

这样就创建了一个 'comm'@'192.168.1.200' 用户,并授予用户 comm 在 192.168.1.200 主机上对所有数据库和表的所有权限,如果允许用户将自己拥有的权限授予其他用户,可以加上 WITH GRANT OPTION 语句,完整命令如下:

1
GRANT ALL PRIVILEGES ON *.* TO 'comm'@'192.168.1.200' WITH GRANT OPTION;

回收权限的的命令如下:

1
2
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'comm'@'192.168.1.200';
FLUSH PRIVILEGES;

总结

  • CDK Error: Connection refused (generic:111) 表示目标数据库拒绝连接,查询MySQL配置和权限分配是否正确
  • 导出MySQL指定表结构 mysqldump -u username -p --no-data source_database table_name > table_structure.sql
  • 查询MySQL用户 SELECT DISTINCT CONCAT('User: ''',user,'''@''',host,''';') AS query FROM mysql.user;
  • 查询指定用户的权限 show grants for 'root'@'localhost';

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

程序是很单纯很单纯的,哪里能有什么坏心思,如果不能按照预期的情况表现,99.99%的情况下是使用方法不对,程序单纯到只会按照指令形式,如果表现不好,一定是指令不好~

2024-12-20 19:50:50

Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客