Mysql ConnectorJ 源码分析(Replication)

    科技2022-08-25  115

    文章目录

    前言名词定义一、Replication的使用场景二、Replication的逻辑结构三、异常处理机制3.1 构造阶段小结 3.2 使用阶段小结 四、实用性分析五、调用链路总结

    前言

    本文讨论Connector/J 的Replication模块。该模块整合Loadbalance模式而形成新的模式。看官若想了解Loadbalance模式,可参阅《Mysql Connector/J 源码分析(LoadBalance)》。

    在本文,我们会观察整个模块的大概逻辑结构。然后在代码层面分析对于异常的控制,这里会有两个“区分”:1)区分构造连接过程和使用连接过程;2)区分通讯异常和数据异常。最后分析此模式的实用性。

    本次分析的版本为5.1.46。若通过maven下载,可添加以下依赖:

    <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency>

    名词定义

    Mysql:Mysql数据库管理软件Mysql服务器:安装了Mysql数据库管理软件的服务器Source服务器:负责写数据的Mysql服务器Replica服务器:负责读数据的Mysql服务器底层连接:普通的ConnectionImplLB写代理:与Source服务器对接的Loadbalance动态代理LB读代理:与Replica服务器对接的Loadbalance动态代理动态代理:通过动态代理的方式构造的连接调用方:使用动态连接连接一方

    一、Replication的使用场景

    Replication的使用场景与数据库的部署模式相关,当下很流行的读写分离,将读数据请求引向一部分服务器,将写数据请求引向另一部分的服务器。由于负责写的服务器会将更新的数据同步到负责读的服务器,这样就能够减轻服务的负担并且提高了请求的影响时间。 Connector/J 的Replication模块引入了这种思路,它会建立两组连接,一组负责读操作(即:LB读代理),一组负责写为主操作(即:LB写代理)。在使用过程中,通过调用动态代理连接的setReadOnly方法,控制发送SQL命令时的走向。

    二、Replication的逻辑结构

    Replication模块同样利用动态代理技术,并且基于LoadBalance模式实现,他们的结构如下: Replication动态代理有三个重要的属性:

    masterConnection,保存LB写代理slavesConnection,保存LB读代理currentConnection,指向当前在用LB代理

    当调用者调用Replication动态代理的setReadOnly(true)方法时,currentConnection指向LB读代理,接下来只能进行读操作;当调用者调用Replication动态代理的setReadOnly(false)方法时,currentConnection指向LB写代理,接下来可以进行写操作(也可以读操作)。

    有些调皮的看官会提问,能否调用Replication动态代理的setReadOnly(true),然后进行数据更新操作呢? 答案是不行的。当调用动态代理的setReadOnly(true)时,底层连接会发送set session transaction read only 到Mysql,对于Mysql来说,这个会话只进行数据读取操作,其他操作都将拒绝。所以如果后续在应用端再发送更新数据的操作就会收到Mysql返回的错误码,从而产生数据异常。

    同理,当调用动态代理的setReadOnly(false)时,底层连接会发送set session transaction read write 到Mysql,对于Mysql来说,这个会话可进行数据读取和更新操作。所以如果后续在应用端再发送更新数据的操作不会收到Mysql返回的错误码。

    三、异常处理机制

    3.1 构造阶段

    在我们的例子里,url以jdbc:mysql:replication作为前缀,当我们使用DriverManager#getConnection命令获取连接的时候,跟踪调用链会一直来到NonRegisteringDriver#connect方法。而该方法分析参数url后,知道这是使用replication模块,因此就会进入NonRegisteringDriver#connectReplicationConnection方法。该方法将调用ReplicationConnectionProxy#createProxyInstance方法获取连接。

    #mermaid-svg-RFazR3PlugFjASmB .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-RFazR3PlugFjASmB .label text{fill:#333}#mermaid-svg-RFazR3PlugFjASmB .node rect,#mermaid-svg-RFazR3PlugFjASmB .node circle,#mermaid-svg-RFazR3PlugFjASmB .node ellipse,#mermaid-svg-RFazR3PlugFjASmB .node polygon,#mermaid-svg-RFazR3PlugFjASmB .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-RFazR3PlugFjASmB .node .label{text-align:center;fill:#333}#mermaid-svg-RFazR3PlugFjASmB .node.clickable{cursor:pointer}#mermaid-svg-RFazR3PlugFjASmB .arrowheadPath{fill:#333}#mermaid-svg-RFazR3PlugFjASmB .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-RFazR3PlugFjASmB .flowchart-link{stroke:#333;fill:none}#mermaid-svg-RFazR3PlugFjASmB .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-RFazR3PlugFjASmB .edgeLabel rect{opacity:0.9}#mermaid-svg-RFazR3PlugFjASmB .edgeLabel span{color:#333}#mermaid-svg-RFazR3PlugFjASmB .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-RFazR3PlugFjASmB .cluster text{fill:#333}#mermaid-svg-RFazR3PlugFjASmB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-RFazR3PlugFjASmB .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-RFazR3PlugFjASmB text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-RFazR3PlugFjASmB .actor-line{stroke:grey}#mermaid-svg-RFazR3PlugFjASmB .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-RFazR3PlugFjASmB .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-RFazR3PlugFjASmB #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-RFazR3PlugFjASmB .sequenceNumber{fill:#fff}#mermaid-svg-RFazR3PlugFjASmB #sequencenumber{fill:#333}#mermaid-svg-RFazR3PlugFjASmB #crosshead path{fill:#333;stroke:#333}#mermaid-svg-RFazR3PlugFjASmB .messageText{fill:#333;stroke:#333}#mermaid-svg-RFazR3PlugFjASmB .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-RFazR3PlugFjASmB .labelText,#mermaid-svg-RFazR3PlugFjASmB .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-RFazR3PlugFjASmB .loopText,#mermaid-svg-RFazR3PlugFjASmB .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-RFazR3PlugFjASmB .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-RFazR3PlugFjASmB .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-RFazR3PlugFjASmB .noteText,#mermaid-svg-RFazR3PlugFjASmB .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-RFazR3PlugFjASmB .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-RFazR3PlugFjASmB .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-RFazR3PlugFjASmB .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-RFazR3PlugFjASmB .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB .section{stroke:none;opacity:0.2}#mermaid-svg-RFazR3PlugFjASmB .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-RFazR3PlugFjASmB .section2{fill:#fff400}#mermaid-svg-RFazR3PlugFjASmB .section1,#mermaid-svg-RFazR3PlugFjASmB .section3{fill:#fff;opacity:0.2}#mermaid-svg-RFazR3PlugFjASmB .sectionTitle0{fill:#333}#mermaid-svg-RFazR3PlugFjASmB .sectionTitle1{fill:#333}#mermaid-svg-RFazR3PlugFjASmB .sectionTitle2{fill:#333}#mermaid-svg-RFazR3PlugFjASmB .sectionTitle3{fill:#333}#mermaid-svg-RFazR3PlugFjASmB .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-RFazR3PlugFjASmB .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB .grid path{stroke-width:0}#mermaid-svg-RFazR3PlugFjASmB .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-RFazR3PlugFjASmB .task{stroke-width:2}#mermaid-svg-RFazR3PlugFjASmB .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB .taskText:not([font-size]){font-size:11px}#mermaid-svg-RFazR3PlugFjASmB .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-RFazR3PlugFjASmB .task.clickable{cursor:pointer}#mermaid-svg-RFazR3PlugFjASmB .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-RFazR3PlugFjASmB .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-RFazR3PlugFjASmB .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-RFazR3PlugFjASmB .taskText0,#mermaid-svg-RFazR3PlugFjASmB .taskText1,#mermaid-svg-RFazR3PlugFjASmB .taskText2,#mermaid-svg-RFazR3PlugFjASmB .taskText3{fill:#fff}#mermaid-svg-RFazR3PlugFjASmB .task0,#mermaid-svg-RFazR3PlugFjASmB .task1,#mermaid-svg-RFazR3PlugFjASmB .task2,#mermaid-svg-RFazR3PlugFjASmB .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-RFazR3PlugFjASmB .taskTextOutside0,#mermaid-svg-RFazR3PlugFjASmB .taskTextOutside2{fill:#000}#mermaid-svg-RFazR3PlugFjASmB .taskTextOutside1,#mermaid-svg-RFazR3PlugFjASmB .taskTextOutside3{fill:#000}#mermaid-svg-RFazR3PlugFjASmB .active0,#mermaid-svg-RFazR3PlugFjASmB .active1,#mermaid-svg-RFazR3PlugFjASmB .active2,#mermaid-svg-RFazR3PlugFjASmB .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-RFazR3PlugFjASmB .activeText0,#mermaid-svg-RFazR3PlugFjASmB .activeText1,#mermaid-svg-RFazR3PlugFjASmB .activeText2,#mermaid-svg-RFazR3PlugFjASmB .activeText3{fill:#000 !important}#mermaid-svg-RFazR3PlugFjASmB .done0,#mermaid-svg-RFazR3PlugFjASmB .done1,#mermaid-svg-RFazR3PlugFjASmB .done2,#mermaid-svg-RFazR3PlugFjASmB .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-RFazR3PlugFjASmB .doneText0,#mermaid-svg-RFazR3PlugFjASmB .doneText1,#mermaid-svg-RFazR3PlugFjASmB .doneText2,#mermaid-svg-RFazR3PlugFjASmB .doneText3{fill:#000 !important}#mermaid-svg-RFazR3PlugFjASmB .crit0,#mermaid-svg-RFazR3PlugFjASmB .crit1,#mermaid-svg-RFazR3PlugFjASmB .crit2,#mermaid-svg-RFazR3PlugFjASmB .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-RFazR3PlugFjASmB .activeCrit0,#mermaid-svg-RFazR3PlugFjASmB .activeCrit1,#mermaid-svg-RFazR3PlugFjASmB .activeCrit2,#mermaid-svg-RFazR3PlugFjASmB .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-RFazR3PlugFjASmB .doneCrit0,#mermaid-svg-RFazR3PlugFjASmB .doneCrit1,#mermaid-svg-RFazR3PlugFjASmB .doneCrit2,#mermaid-svg-RFazR3PlugFjASmB .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-RFazR3PlugFjASmB .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-RFazR3PlugFjASmB .milestoneText{font-style:italic}#mermaid-svg-RFazR3PlugFjASmB .doneCritText0,#mermaid-svg-RFazR3PlugFjASmB .doneCritText1,#mermaid-svg-RFazR3PlugFjASmB .doneCritText2,#mermaid-svg-RFazR3PlugFjASmB .doneCritText3{fill:#000 !important}#mermaid-svg-RFazR3PlugFjASmB .activeCritText0,#mermaid-svg-RFazR3PlugFjASmB .activeCritText1,#mermaid-svg-RFazR3PlugFjASmB .activeCritText2,#mermaid-svg-RFazR3PlugFjASmB .activeCritText3{fill:#000 !important}#mermaid-svg-RFazR3PlugFjASmB .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-RFazR3PlugFjASmB g.classGroup text .title{font-weight:bolder}#mermaid-svg-RFazR3PlugFjASmB g.clickable{cursor:pointer}#mermaid-svg-RFazR3PlugFjASmB g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-RFazR3PlugFjASmB g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-RFazR3PlugFjASmB .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-RFazR3PlugFjASmB .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-RFazR3PlugFjASmB .dashed-line{stroke-dasharray:3}#mermaid-svg-RFazR3PlugFjASmB #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB .commit-id,#mermaid-svg-RFazR3PlugFjASmB .commit-msg,#mermaid-svg-RFazR3PlugFjASmB .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-RFazR3PlugFjASmB g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-RFazR3PlugFjASmB g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-RFazR3PlugFjASmB g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-RFazR3PlugFjASmB g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-RFazR3PlugFjASmB .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-RFazR3PlugFjASmB .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-RFazR3PlugFjASmB .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-RFazR3PlugFjASmB .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-RFazR3PlugFjASmB .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-RFazR3PlugFjASmB .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-RFazR3PlugFjASmB .edgeLabel text{fill:#333}#mermaid-svg-RFazR3PlugFjASmB .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-RFazR3PlugFjASmB .node circle.state-start{fill:black;stroke:black}#mermaid-svg-RFazR3PlugFjASmB .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-RFazR3PlugFjASmB #statediagram-barbEnd{fill:#9370db}#mermaid-svg-RFazR3PlugFjASmB .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-RFazR3PlugFjASmB .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-RFazR3PlugFjASmB .statediagram-state .divider{stroke:#9370db}#mermaid-svg-RFazR3PlugFjASmB .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-RFazR3PlugFjASmB .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-RFazR3PlugFjASmB .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-RFazR3PlugFjASmB .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-RFazR3PlugFjASmB .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-RFazR3PlugFjASmB .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-RFazR3PlugFjASmB .note-edge{stroke-dasharray:5}#mermaid-svg-RFazR3PlugFjASmB .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-RFazR3PlugFjASmB .error-icon{fill:#522}#mermaid-svg-RFazR3PlugFjASmB .error-text{fill:#522;stroke:#522}#mermaid-svg-RFazR3PlugFjASmB .edge-thickness-normal{stroke-width:2px}#mermaid-svg-RFazR3PlugFjASmB .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-RFazR3PlugFjASmB .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-RFazR3PlugFjASmB .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-RFazR3PlugFjASmB .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-RFazR3PlugFjASmB .marker{fill:#333}#mermaid-svg-RFazR3PlugFjASmB .marker.cross{stroke:#333} :root { --mermaid-font-family: "trebuchet ms", verdana, arial;} #mermaid-svg-RFazR3PlugFjASmB { color: rgba(0, 0, 0, 0.75); font: ; } 调用方 DriverManager NonRegisteringDriver ReplicationConnectionProxy getConnection connect 分析url,发现要使用replication connectReplicationConnection createProxyInstance 调用方 DriverManager NonRegisteringDriver ReplicationConnectionProxy

    在上述调用链的过程中,并没有捕获异常的行为。因此,如果ReplicationConnectionProxy#createProxyInstance方法的底层抛出的任何异常都会直接抛向调用方。所以捕获SQLException异常的动作通常由调用方做。

    我们观察下ReplicationConnectionProxy#createProxyInstance方法:

    public static ReplicationConnection createProxyInstance(List<String> masterHostList, Properties masterProperties, List<String> slaveHostList, Properties slaveProperties) throws SQLException { ReplicationConnectionProxy connProxy = new ReplicationConnectionProxy(masterHostList, masterProperties, slaveHostList, slaveProperties); return (ReplicationConnection) java.lang.reflect.Proxy.newProxyInstance(ReplicationConnection.class.getClassLoader(), INTERFACES_TO_PROXY, connProxy); }

    这里只有两行命令,第一行构造ReplicationConnectionProxy对象,根据命令的声明,它有可能抛出SQLException;第二句是创建动态代理对象,根据命令的声明,它有可能抛出IllegalArgumentException,这异常是RuntimeException的子类。因此我们只需要着重观察ReplicationConnectionProxy对象的构造过程。

    private ReplicationConnectionProxy(List<String> masterHostList, Properties masterProperties, List<String> slaveHostList, Properties slaveProperties) throws SQLException { .... try { initializeSlavesConnection(); } catch (SQLException e) { if (!this.allowSlaveDownConnections) { if (this.connectionGroup != null) { this.connectionGroup.handleCloseConnection(this.thisAsReplicationConnection); } throw e; } // Else swallow this exception. } SQLException exCaught = null; try { this.currentConnection = initializeMasterConnection(); } catch (SQLException e) { exCaught = e; } if (this.currentConnection == null) { if (this.allowMasterDownConnections && this.slavesConnection != null) { // Set read-only and fail over to the slaves connection. this.readOnly = true; this.currentConnection = this.slavesConnection; } else { .... if (exCaught != null) { throw exCaught; } throw SQLError.createSQLException(Messages.getString("ReplicationConnectionProxy.initializationWithEmptyHostsLists"), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); } } }

    在ReplicationConnectionProxy的构造函数里,省略号部分会调用抛异常的命令,然而异常已经被捕获并且不再往上抛,所以这部分可以不用分析。

    initializeSlavesConnection故名思义,它用于构造LB读代理,此处会捕获该异常。如果用户的url设置了allowSlaveDownConnections=false或者没有添加该选项,就会将异常往上层抛。其语义就是,不能成功建立读的服务器连接,就不要构造Replication动态代理连接了。如果用户的url设置了allowSlaveDownConnections=true,不会将异常往上抛,其语义就是,不能成功建立读的服务器连接也没关系,就先这样吧。

    initializeMasterConnection方法用于构造LB写代理。它的底层也有可能抛出异常,如果allowMasterDownConnections为true并且连接读的服务器的动态连接已经成功构造的话,就不会抛出异常,其他情况一律抛出异常。其语言是,不能成功建立写的服务器连接也没有关系,就先这样吧。

    小结

    在构造ReplicationConnectionProxy时,会构造LB读代理和LB写代理。这两代理在构造的时候遇到通讯异常以及数据异常都会往上抛。ReplicationConnectionProxy捕获到异常后会根据allowSlaveDownConnections和allowMasterDownConnections两属性值选择吞掉异常还是继续往上抛。也就是说调用者能够感知到ReplicationConnectionProxy构造时出现的异常。如果调用者觉得异常可以忽略,可通过在url添加allowSlaveDownConnections=true或者allowMasterDownConnections=true选项加以控制。

    3.2 使用阶段

    @Override Object invokeMore(Object proxy, Method method, Object[] args) throws Throwable { checkConnectionCapabilityForMethod(method); boolean invokeAgain = false; while (true) { try { Object result = method.invoke(this.thisAsConnection, args); if (result != null && result instanceof Statement) { ((Statement) result).setPingTarget(this); } return result; } catch (InvocationTargetException e) { if (invokeAgain) { invokeAgain = false; } else if (e.getCause() != null && e.getCause() instanceof SQLException && ((SQLException) e.getCause()).getSQLState() == SQLError.SQL_STATE_INVALID_TRANSACTION_STATE && ((SQLException) e.getCause()).getErrorCode() == MysqlErrorNumbers.ERROR_CODE_NULL_LOAD_BALANCED_CONNECTION) { try { // Try to re-establish the connection with the last known read-only state. setReadOnly(this.readOnly); invokeAgain = true; } catch (SQLException sqlEx) { // Still not good. Swallow this exception. } } if (!invokeAgain) { throw e; } } } }

    方法里基乎被while(true)循环占用了,意味着有些情况下会多次执行Mehod#invoke方法。另外,对于捕获的异常会进行判定,如果符合判定会以当前的readOnly值来更新,然后再次执行Mehod#invoke方法,如果仍产生同样的异常就向上抛。对于不符合判定的异常一律向上抛。

    那么,Mehod#invoke方法执行时,是否会有异常抛出来呢?答案是肯定的。打比方要执行Replication动态代理的setAutoCommit方法,随后会调用LB代理的setAutoCommit方法。最终调用的是ConnectionImpl#setAutoCommit方法,该方法往Mysql服务器发送SET autocommit=1的命令。所以这期间有可能会遇到通讯异常。

    在《Mysql Connector/J 源码分析(LoadBalance)》已经分析到,LB代理在执行过程中如果遇到异常都会往外抛,所以一旦遇到异常情况,ReplicationConnectionProxy#invokeMore方法就会捕获到异常。

    小结

    所以经过综合分析,Replication动态代理在执行时如果底层的LB代理遇到异常时,它会将异常继续往上抛,所以调用方是能够感知到异常的发生。如果该异常属于通讯异常,底层的LB代理会更换更底层的连接。

    四、实用性分析

    官网对于写数据和读数据的切换如下所述:

    An application signals that it wants a transaction to be read-only by calling Connection.setReadOnly(true). The replication-aware connection will use one of the replica connections, which are load-balanced per replica host using a round-robin scheme.

    If you have a write transaction, or if you have a read that is time-sensitive (remember, replication in MySQL is asynchronous), set the connection to be not read-only, by calling Connection.setReadOnly(false) and the driver will ensure that further calls are sent to the source MySQL server.

    从字面上看,切换动作是由调用者发起。我们从上面分析invokeMore时,确实也没看到程序会自动判断当前要执行的是查询操作还是更新数据操作。所以当调用者需要查询数据时,必须先调用动态连接的setReadOnly(true),这样才会使用对应着读数据的LB读代理,当调用者需要更新数据时,必须先调用动态连接的setReadOnly(false),这样才会使用对应着写数据的LB写代理。如果调用者忘记调用setReadOnly方法或者搞反了方法的参数,会发生无法写数据,或者从Source服务器读取数据而不是Replica服务器读取。

    所以,要发挥好Mysql的读-写分离的模式带来的特性,需要编程人员在发送SQL命令前正确地调用setReadOnly方法。因此,无论是升级项目或者开发新的项目使用此模式,都会加极大地增加程序员的工作量。

    现在有一些数据库中间件,它们能根据SQL命令判断出是要进行读操作还是写操作,然后它们就会将命令发送到Replica服务器或者Source服务器。但这种方式也有一个缺点,就是要占用资源去部署,所以在资源特别紧张或者预算不多的情况下,可以考虑Replication模式。对于复杂的查询提前调用setReadOnly(false)吧。

    五、调用链路

    无论是Replication模式还是LoadBalance模式,发送的命令最终要通过ConnectionImpl发送,这三者关系是一个比一个高层次。现在以执行Replication动态代理的setAutoCommit方法为例,简单梳理出调用过程。

    总结

    Replication模式其实是整合了Loadbalance模式而形成新的模式,用匹配Mysql的Source/Replica部署模式。

    本文一如既往以两个区分的方式,探讨动态连接的构造阶段和运行阶段异常的处理方式。在Replication模式,构造阶段抛出的异常能够被调用者感知到,如果调用者觉得通讯异常可以忽略,可通过在url添加allowSlaveDownConnections=true或者allowMasterDownConnections=true选项屏蔽,具体使用方式请查阅官网。在使用阶段,调用者可以感知到底层抛出的异常,因此Replication模式具有使用价值。

    本文也探讨了Replication模式的实用性。开发者需要明确SQL命令要发送到Source服务器还是Replica服务器,在发送SQL命令前,需要调用Replication动态代理的setReadOnly方法并传入正确布尔值,所以可以理解为此模式具有入侵式的特点。在数据库中间件骚动的今天,此模式可能不会太普遍地被使用。

    最后,以顺序图剖析命令是如何传达到最底层的连接。

    至此我们基本了解了Replication模式的结构以及使用价值。大家觉得哪些地方描述不清,敬请提意见。

    Processed: 0.023, SQL: 9