天天看点

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

作者:LinkSLA智能运维管家

openGauss数据库客户端连接思考

openGauss数据库企业级数据库一般采用一主多从的高可用模式。主库可读写,从库提供可读能力。之前在里也探讨过openGauss高可用模式下客户端访问方式的选择。

客户端访问方式

openGauss数据库主从架构下,客户端如何连接到主数据库呢?又怎么做读写分离?这个时候就需要讨论下客户端连接数据库的几种方式:VIP、DNS和主机列表。因为openGauss数据库的jdbc驱动里支持设置多个主机,并提供参数设置只连主节点还是只连备节点,这也应对了读写分离的需求。

VIP

传统的VIP模式只能支持应用连接到主节点上,从节点的读请求是不太好设置的。增加读VIP的管理对于高可用软件来说就太复杂了。VIP管理还有一个限制是必须属于同一网段。对于同城双中心不能采用相同网段的情况下,VIP漂移就不能实现。

DNS

DNS也具备高可用性,但是只能检测到机器IP是否存在,不能检测openGauss的服务,所以采用DNS的高可用相对来说不太适合数据库来使用。

主机列表

例如下面这个例子就是只连主库的客户端设置:

url=jdbc:postgresql://:26000,:26000/?connectTimeout=5&targetServerType=master&tcpKeepAlive=true

我比较偏好这种采用主机列表的方式,通过配置客户端参数,能够实现自动主库发现,负载均衡,只读从库等各类应用场景需求。这种方式也避免了VIP,DNS在不同架构下的管理复杂性,相对更通用一些。因此我在openGauss的整个企业级部署里面,充分利用jdbc驱动的检测能力,将多IP列表的配置作为标准设置,期望openGauss主从切换的情况下,客户端数据源能自动连接到正确的节点。

数据源连接池测试

最常见的数据库访问方式是通过在中间件里定义连接数据库的数据源连接池,维持一定的长链接供应用访问,减少每次访问数据库需要创建连接的开销。

tomcat使用druid数据源配置

例如tomcat采用druid数据源连接数据库,设置如下:

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

为了保障数据源里连接的可用性,需要对空闲数据源进行周期性检测。参数testWhileIdle设置为true,validationQuery设置为"select 1",validationQueryTimeout设置为5秒。预期连接池里的连接在被使用前会通过validationQuery检测是否可用。

测试中遇到的问题

在一系列的数据源连接池和数据库的高可用测试中,遇到了一个奇怪的现象。当openGauss的主库(197.0.34.50)掉电后,备库(197.0.34.51)切换为新的主库(HA软件自动切换,测试中也可以人工操作切换),但是tomcat的连接一直没有连接到新的主库(197.0.34.51)上来。查看客户端上的连接长时间处于ESTABLISHED的状态,看起来不触发TCP_KEEPALIVE的机制是不会断掉的。而应用的访问全部都停滞了。

连接池检测机制分析

从这个问题的现象来看,显然validationQueryTimeout没有生效。抓取druid的运行trace发现时间不对:

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

isValidConnection中检测语句返回时间是931454.501349ms,远远超过5秒。进一步分析源码发现,druid的检测代码有下面的内容,是通过将数据源配置参数validationQueryTimeout转化为postgresql里的setQueryTimeout参数。

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

显然druid并没有设置自己的定时器来判断validationQuery是否超时,而是交给了openGauss的jdbc驱动。

openGauss jdbc相关源码解析

现在要做的事情就是分析openGauss驱动代码里面的setQueryTimeout为什么没有生效。为了验证这个问题,我单独改写了份验证的程序。

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

程序启动后,观察连接的状态。

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

这个时候直接给主机关机,客户端看连接还是ESTABLISHED的状态。

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

java程序一直hang,等待sql返回。二十分钟后报错:

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

报错内容是连接超时,而且时间也远远超过setQueryTimeout设置的20秒。分析代码发现源代码QueryExecutorBase.java里的sendQueryCancel函数抓到异常并不处理返回。

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

而调用者源码PgConnection.java中 cancelQuery() 也仅仅是发起了sendQueryCancel就结束了,有没处理可能出现的异常。

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

openGauss jdbc源码改进提交

现在解题思路就比较简单了,首先让sendQueryCancel能够返回异常,然后由cancelQuery函数catch这个异常并做处理,也就是关闭这个queryExecutor。我在被调用者sendQueryCancel中抛出超时异常,然后在调用者cancelQuery中抓取这个异常并关闭queryExecutor就可以了。

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

重新编译显得jar包后,测试下版本的驱动,在同样的情况下,错误变成了Socket closed。

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

trace日志里能看到新的代码成功执行了:

openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶
openGauss 数据库 JDBC 客户端配置探索与改进 | 运维进阶

从测试结果来看满足需求,在连接池情况下进一步验证也能切换了。当前我已经将这段代码修改提交到了社区,合并在master版本里面。

openGauss数据库连接总结

在数据库连接url里面,多个IP是顺序连接的,如果第一个ip不通,那么连接需要等待connectTimeout参数控制超时时间,才会连接下一个ip。这也是在配置过程里需要关注的点。最后总结下多ip主机列表的设置:

1.合理设置数据源连接池的大小。主要是initialSize,minIdle和maxActive这几个参数。建议不要设置过大的连接池。在生产中其实很少有活动会话很高的系统,但是确常见几千几万的空闲连接在数据库上。这些都是资源浪费,没有意义。

2.通过建立不同的数据源来实现读写分离和负载均衡。openGauss是单机数据库,利用好从库能够实现只读性能扩展,减少主库压力。

3.利用驱动能力实现高可用自动切换。这种方式也适合跨区域跨网段等VIP不能漂移的场景。

继续阅读