最近看到有些研发写代码jdbc的配置文件是MGR多个地址。出于好奇它是如何选择连接的,在节点故障的时候,又是如何failover的。于是有了下文的探索与发现。
jdbc连接示例如下:
spring.datasource.url=jdbc:mysql://mgr-01:3306,mgr-02:3306,mgr-03:3306/sbtest?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai
遇事先查官方文档: https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-multi-host-connections.html
官方文档上提到了多hosts的几个连接方式,根据架构不同,写法也有些差异:
1、故障转移模式
代码语言:javascript复制jdbc:mysql://[primary host][:port],[secondary host 1][:port][,[secondary host 2][:port]]...[/[database]]»
[?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]
连接 URL 中的主机列表包含两种类型的主机,主要和次要主机。启动新连接时,驱动程序总是首先尝试连接到主要主机,
如果需要,当遇到通信问题时,依次故障转移到列表中的辅助主机。即使与主主机的初始连接失败并且驱动程序连接到辅助主机,
主主机也永远不会失去其特殊状态:例如,它可以配置为与辅助主机不同的访问模式,并且它在故障转移过程中选择主机时,
可以将其置于更高的优先级。
这种模式下,一般是主节点宕机了,程序就改为连接从节点,默认也是降级为只读访问方式。
2、负载均衡模式
代码语言:javascript复制jdbc:mysql:loadbalance://[host1][:port],[host2][:port][,[host3][:port]]...[/[database]] »
[?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]
Connector/J长期以来提供了一种有效的方法,可以跨多个MySQL服务器实例为集群或源复制部署分配读/写负载。
您可以动态配置负载平衡连接,而无需中断服务。进程内事务不会丢失,如果任何应用程序试图使用该特定服务器实例,
也不会生成任何应用程序异常。
3、主从模式
代码语言:javascript复制jdbc:mysql:replication://[source host][:port],[replica host 1][:port][,[replica host 2][:port]]...[/[database]] »
[?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]
用户可以指定属性 allowSourceDownConnections=true 以允许创建 Connection 对象,即使没有可访问的源主机。
此类 Connection 对象报告它们是只读的,并且 isSourceConnection() 为它们返回 false。 Connection 在调用
Connection.setReadOnly(false) 时测试可用的源主机,如果无法建立与源的连接,则抛出 SQLException,
如果主机可用,则切换到源连接。
Connector/J 支持复制感知连接。它可以根据 Connection.getReadOnly() 的状态自动将查询发送到读/写源主机,
或者故障转移或循环负载平衡的副本集。
应用程序通过调用 Connection.setReadOnly(true) 发出它希望事务为只读的信号。复制感知连接将使用其中一个副本连接,
该连接使用循环方案对每个副本主机进行负载平衡。在发出事务边界命令(提交或回滚)或从服务中删除副本之前,
给定的连接对副本是粘性的。调用 Connection.setReadOnly(true) 后,如果要在没有可用副本时允许连接到源,
请将属性 readFromSourceWhenNoReplicas 设置为“true”。请注意,在这些情况下,源主机将以只读状态使用,
就好像它是副本主机一样。另请注意,设置 readFromSourceWhenNoReplicas=true 可能会以透明方式导致源主机的额外负载。
如果你有一个写事务,或者你有一个时间敏感的读(记住,MySQL 中的复制是异步的),通过调用
Connection.setReadOnly(false) 将连接设置为非只读的,驱动程序将确保将进一步的调用发送到源 MySQL 服务器。
驱动程序负责在它用来完成此负载平衡功能的所有连接之间传播自动提交的当前状态、隔离级别和目录。
可以看到,对于MGR如果是多写的话,负载均衡模式是比较合适的。 我们这里对于MGR多写集群配置用的是故障转移模式。 使用起来也是可以的。但是默认参数是有坑的!下面实验会介绍。
开始实验阶段。
如果spring那一套你还不会,没关系,我们的dbeaver用的就是jdbc方式连接数据库的。直接用它即可模拟达到实验效果。
部署一套MGR多写集群,我这里是 192.168.3.11 192.168.3.12 192.168.3.14 , 用的是CRUD权限的普通账号。
dbeaver的服务器地址,我们把3个节点都写上。
step1、连接数据库,随便进行些写入、读取测试。可以去数据库上看,默认是连接的第一个节点,也就是 192.168.3.11。
step2、关闭192.168.3.11的上的mysql进程(模拟节点宕机的情况),重新连接dbeaver数据库,可以发现select操作是可以的,但是执行 delete insert之类修改数据的操作会提示正处于read-only transaction中,也就是说这时候你连接虽然切到了健康的MGR节点,但是仍然不能写入数据了!!!
写入操作报错如下图:
显然,这不是我们期望的效果。 查查官方文档吧
翻译下,大意如下:
与任何标准连接一样,与主主机的初始连接处于读/写模式。但是,如果驱动程序未能建立与主要主机的初始连接并自动切换到列表中的下一个主机,则访问模式现在取决于属性 failOverReadOnly 的值,默认情况下为“true”。如果驱动程序最初连接到主要主机,并且由于某些连接失败,它会故障转移到辅助主机,也会发生同样的情况。每次连接回退到主主机时,它的访问模式将是读/写,而不管主主机之前是否连接过。通过调用 Connection.setReadOnly(boolean) 方法可以在运行时随时更改连接访问模式,该方法部分覆盖了属性 failOverReadOnly。当 failOverReadOnly=false 并且访问模式显式设置为 true 或 false 时,它成为主机切换后每个连接的模式,无论连接到什么主机类型;但是,如果 failOverReadOnly=true,则只有在驱动程序连接到主主机时才能将访问模式更改为读/写;但是,即使当前连接的访问模式无法更改,驱动程序也会记住客户端的最后意图,并且在回退到主主机时,这就是将使用的模式。
放到这里,就是说MGR多写的模式的话, 使用默认这种配置方式,当第一个节点宕机后,程序会连接到第二个节点并且是只读模式的(因为默认failOverReadOnly=true),程序依然是不能写入新的数据。只有当设置failOverReadOnly=false的时候,MGR第一个节点宕机后,程序连接到第二个节点才能依旧具备读 写的能力。
修改下dbeaver的驱动属性,改动地方如下:
然后,断开数据库连接。修复下MGR集群状态。再次做故障测试模拟。可以发现,节点1宕机后,我们连接切到节点2 ,读写都是可以的。 这里就不上图了。
目标达成!
剩余的事情就是找研发交流下,找他们进行线下测试验证下,如果也存在这个情况,就要考虑修复掉了。