一个TCP连接池的自我修养-如mysql-client、http-client、redis-client

本文探讨了单服单天过亿请求下出现0.0001%的5xx错误的原因及解决方案,分析了MySQL连接问题、TCP连接池配置不当等问题,并提出了合理的连接池配置策略。

问题:单服单天过亿请求中,出现0.0001%的500怎么回事?

  • 日志中不定时出现 mysql gone awayconnect timeout, reset by beer等错,原因不明。
  • access-log同样不定时出现5xx错。
  • 感觉MySQL运行正常,没压力,没慢查,sql使用主键查询–不知道问题在哪?
  • 连接MySQL的代码使用keepalive。

这个时候,你是否怀疑过是连接库的问题?

一个TCP连接池的自我修养

  • keepalive特性,idle-connection可设定。
  • one retry 特性。
  • max-connection 设定。
  • read/write timeout 设定。

max-connection 特性是必需的

如果是mysql连接库,max-connection不设上限,要不就是造成后端的mysql连接过多,拖跨mysql。
如果是http连接库,就会造成服务应用大量并发请求在等待,不论阻塞、非阻塞同样会。即使像go语言,这样原生性非阻塞、轻并发语言。大量的等等请求,一样会有耗尽内存的风险。换是python、php一个线程就以M为单位内存计算的,并发更低。
所以必需带有合理的 setMaxConnection() 接口。

非常重要的容错

当连接池满载时,一个新线程请求一个新连接时,采用什么策略?
等待?
还是直接返回try-later?

read/write timeout 也是必需的

任何的TCP连接,都可能出现的读/写超时,对上层来说就是出错了,如果timeout很长,即直接阻塞了此线程或微线程。大量阻塞同样地引发内存风险,也不存在语言上的差别。所以,合理的timeout值,特别是read timeout,经常地出现一种,对方服务器处理慢,不能及时返回结果,而client端断开后,对方继续完成了工作,而client方以为没完成,而重发请求,如果没防重入,会造成数据不一致。 为什么重发?参考one-retry特性说明。

keepalive+ idle-connection 作为可选特性

追求高性能,TCP的keepalive,连接重用是必需的。
而idle-connection作为闲时保持最低的连接数量,当上层业务要使用时马上可用。
还有一种做法是 connection-keep-timeout, 即每个连接闲下来后,多长时间后自动关闭。
MySQL中就可以使用 set timeout=n; 显式设置每个连接的timeout值,否则使用服务端默认统一值。

one retry 特性,在keepalive打开时,必需加上

one-retry特性,并不是每个程序员都注意并知道的一个常规特性。
而此特性正是解决 我们项目中 0.0001% 5xx 问题 的关键。
一旦开启idle-connection特性后, TCP客端端会保持一定数量在连接在连接池中,上层要使用时返回一个连接。
而像MySQL的通信协议中是有Ping指令的,就是用于测试连接是否有效。所以当返回一个连接时,如果是MySQL,就可以用Ping测试一下,确保返回给上层的是可用连接。
原因是连接池中的连接不一定有效,即使你代码中监听了socket的close事件,但由于服务端可能使用了LVS这样的虚拟IP,很可能出现服务端单方面断开了TCP连接,客户端是没法感知(注:这里并不是说使用LVS就会有这种现象,只能用于举例说明,服务端可能使用了客户端无法感知的连接层)。除此以后,TCP断开时4次握手不完全,也是很正常的现象。
为了上层能获得一个可用连接池,没有ping协议的HTTP协议怎么办?
可以使用one-retry特性:
上层发送一个请求调用,由 连接池 执行, 如出现 reset by beermysql was gone away 情况的,可以新建一次连接,重发一次请求 。我把这种行为称为one-retry, 上层不必感知。若重试的请求,也出来同样的错误,就应该向上层返回或异常了–视语言而定。

在 PHP 中,可以使用 Swoole 扩展来实现 WebSocket 连接。Swoole 是 PHP 的一个异步、并行、高性能网络通信框架,支持 WebSocket、TCP/UDP 协议等。 下面是一个简单的 PHP WebSocket 连接 MySQLRedis 的示例: ```php <?php // 引入 Swoole 库 require_once 'path/to/swoole/autoload.php'; // 创建 WebSocket 服务器 $server = new Swoole\WebSocket\Server('0.0.0.0', 9501); // 监听 WebSocket 连接事件 $server->on('open', function (Swoole\WebSocket\Server $server, $request) { echo "new client connected, fd: {$request->fd}\n"; }); // 监听 WebSocket 消息事件 $server->on('message', function (Swoole\WebSocket\Server $server, $frame) { echo "received message: {$frame->data}\n"; // 连接 MySQL 数据库 $mysql = new mysqli('localhost', 'root', 'password', 'database'); if ($mysql->connect_errno) { echo "MySQL connect failed: {$mysql->connect_error}\n"; return; } // 执行查询操作 $sql = "SELECT * FROM table"; $result = $mysql->query($sql); if ($result === false) { echo "MySQL query failed: {$mysql->error}\n"; return; } // 获取查询结果 $data = []; while ($row = $result->fetch_assoc()) { $data[] = $row; } // 关闭 MySQL 连接 $mysql->close(); // 连接 Redis 数据库 $redis = new Redis(); if (!$redis->connect('127.0.0.1', 6379)) { echo "Redis connect failed\n"; return; } // 将查询结果存入 Redis $redis->set('data', json_encode($data)); // 关闭 Redis 连接 $redis->close(); // 发送消息给客户端 $server->push($frame->fd, json_encode($data)); }); // 启动 WebSocket 服务器 $server->start(); ``` 上面的代码中,我们在 WebSocket 消息事件中连接了 MySQLRedis 数据库,执行了一次查询操作,并将查询结果存入了 Redis 中。最后,将查询结果以 JSON 格式发送给了客户端。 需要注意的是,上面的代码仅供参考,实际应用中需要根据具体情况进行修改和完善。例如,需要考虑数据库连接池、错误处理、安全性等问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值