设为首页 收藏本站
查看: 797|回复: 0

[经验分享] Testing MySQL Replication Connection

[复制链接]

尚未签到

发表于 2016-9-20 10:53:45 | 显示全部楼层 |阅读模式
Testing MySQL Replication Connection
博客分类:
     
  • MySQL
MySQLC3P0replication driver 

  目前项目中使用了MySQL replication,并通过LVS对slaves进行负载均衡,数据库连接池使用的是c3p0。在使用过程中发现, LVS TCP timeout可能导致数据库连接被切断,从而应用程序中报数据库连接异常。
    ReplicationConnection内部保持了两个数据库连接,分别是masterConnection和slaveConnection。实际生效的连接取决于连接的readOnly属性,即readOnly ? currentConnection=slaveConnection : currentConnection=masterConnection。
    c3p0的提供了两种处理空闲连接的机制,对应的配置参数分别是:idleConnectionTestPeriod和maxIdleTime。但是这两种机制在默认情况下对ReplicationConnection不奏效。原因如下:

  • idleConnectionTestPeriod:c3p0在定期检查ReplicationConnection时只是检查了其内部的currentConnection,如果在某段时间内连接的readOnly属性为false,那么c3p0只会检查masterConnection,而不会检测slaveConnection是否仍然有效。
  • maxIdleTime:如果应用程序在一段内只以readOnly=false使用某个ReplicationConnection,那么c3p0不会认为该连接空闲,而实际上其内部的slaveConnection可能已经超时。
  默认情况下,c3p0使用DefaultConnectionTester(通过connectionTesterClassName配置)进行连接检查(基于Query),该类有以下两个比较重要的方法:

  • public int activeCheckConnection(Connection c, String query, Throwable[] rootCauseOutParamHolder):通常用于checkin,checkout和定期的连接检查。
  • public int statusOnException(Connection c, Throwable t, String query, Throwable[] rootCauseOutParamHolder):在连接抛异常的情况下,c3p0通过此方法确认连接本身是否可用(即是否需要销毁此连接)。
  需要注意的是,MySQL connector也提供了一个实现了c3p0的ConnectionTester接口的类:MysqlConnectionTester。该类使用com.mysql.jdbc.Connection的ping方法(相对于执行query,ping更轻量级)来确认连接是否正常。笔者认为该类仍然不能正确检测ReplicationConnection。
  以下是笔者实现的一个ConnectionTester,用于检查MySQL ReplicationConnection:
Java代码   DSC0000.png


  • import java.sql.Connection;  
  • import java.sql.ResultSet;  
  • import java.sql.SQLException;  
  • import java.sql.Statement;  
  •   
  • import org.slf4j.Logger;  
  • import org.slf4j.LoggerFactory;  
  •   
  • import com.mchange.v2.c3p0.AbstractConnectionTester;  
  • import com.mysql.jdbc.CommunicationsException;  
  •   
  •   
  • public final class MysqlReplicationConnectionTester extends AbstractConnectionTester {  
  •     //  
  •     private static final Logger LOGGER = LoggerFactory.getLogger(MysqlReplicationConnectionTester.class);  
  •       
  •     //  
  •     private static final long serialVersionUID = -7348778746126099053L;  
  •       
  •     //  
  •     private static final String DEFAULT_QUERY = "SELECT 1";  
  •       
  •     public boolean equals(Object o) {  
  •         return (o != null && o.getClass() == MysqlReplicationConnectionTester.class);  
  •     }  
  •   
  •     public int hashCode() {  
  •         return MysqlReplicationConnectionTester.class.getName().hashCode();  
  •     }  
  •           
  •     public int activeCheckConnection(Connection c, String query, Throwable[] outParamCause) {  
  •         //  
  •         boolean readOnly = false;  
  •         boolean needRestoreReadOnly = false;  
  •         try {  
  •             //  
  •             readOnly = c.isReadOnly();  
  •               
  •             //  
  •             int r = checkConnection(c, query, outParamCause, readOnly);  
  •             if(r == CONNECTION_IS_OKAY) {  
  •                 needRestoreReadOnly = true;  
  •                 r = checkConnection(c, query, outParamCause, !readOnly);  
  •             }  
  •             return r;  
  •         } catch(Exception e) {  
  •             //  
  •             LOGGER.warn("the connection: " + c + " was marked invalid", e);  
  •             if (outParamCause != null) {  
  •                 outParamCause[0] = e;  
  •             }  
  •             return CONNECTION_IS_INVALID;  
  •         } finally {  
  •             try {  
  •                 if(needRestoreReadOnly) {  
  •                     c.setReadOnly(readOnly);  
  •                 }  
  •             } catch (SQLException e) {  
  •                 LOGGER.error("failed to restore read only: " + readOnly + " on connection", e);  
  •             }  
  •         }  
  •     }  
  •   
  •     public int statusOnException(Connection c, Throwable t, String query, Throwable[] outParamCause) {  
  •         //  
  •         int r = checkConnectionOnException(c, t, query, outParamCause);  
  •           
  •         //  
  •         if(r != CONNECTION_IS_OKAY) {  
  •             if (outParamCause != null) {  
  •                 outParamCause[0] = t;  
  •             }  
  •         }  
  •         return r;  
  •     }  
  •   
  •     private int checkConnection(Connection c, String query, Throwable[] outParamCause, Boolean readOnly) {  
  •         //  
  •         if (query == null || query.equals("")) {  
  •             query = DEFAULT_QUERY;  
  •         }  
  •           
  •         //  
  •         ResultSet rs = null;  
  •         Statement stmt = null;  
  •         try {  
  •             //  
  •             boolean ro = c.isReadOnly();  
  •             if(readOnly != null && readOnly != ro) {  
  •                 c.setReadOnly(readOnly);  
  •             }  
  •               
  •             //  
  •             if(LOGGER.isInfoEnabled()) {  
  •                 LOGGER.info("testing connection: {} with query: {}, read only: {}"new Object[]{c, query, ro});  
  •             }  
  •               
  •             //  
  •             stmt = c.createStatement();  
  •             rs = stmt.executeQuery(query);  
  •             return CONNECTION_IS_OKAY;  
  •         } catch (SQLException e) {  
  •             LOGGER.warn("failed to test connection: " + c + " with query: " + query + ", state: " + e.getSQLState(), e);  
  •             if (outParamCause != null) {  
  •                 outParamCause[0] = e;  
  •             }  
  •             return CONNECTION_IS_INVALID;  
  •         } catch (Exception e) {  
  •             LOGGER.warn("failed to test connection: " + c + " with query: " + query, e);  
  •             if (outParamCause != null) {  
  •                 outParamCause[0] = e;  
  •             }  
  •             return CONNECTION_IS_INVALID;  
  •         } finally {  
  •             closeQuietly(rs);  
  •             closeQuietly(stmt);  
  •         }  
  •     }  
  •       
  •     private int checkConnectionOnException(Connection c, Throwable t, String query, Throwable[] outParamCause) {  
  •         //  
  •         if (t instanceof CommunicationsException) {  
  •             return CONNECTION_IS_INVALID;  
  •         }  
  •           
  •         //  
  •         if (t instanceof SQLException) {  
  •             final String sqlState = ((SQLException) t).getSQLState();  
  •             if (sqlState != null && sqlState.startsWith("08")) {  
  •                 return CONNECTION_IS_INVALID;  
  •             } else {  
  •                 return CONNECTION_IS_OKAY;  
  •             }  
  •         }  
  •           
  •         // Runtime/Unchecked?  
  •         return CONNECTION_IS_INVALID;  
  •     }  
  •       
  •     private void closeQuietly(ResultSet rs) {  
  •         //  
  •         if(rs == null) {  
  •             return;  
  •         }  
  •           
  •         //  
  •         try {  
  •             rs.close();  
  •         } catch (SQLException e) {  
  •             LOGGER.warn("failed to close result set", e);  
  •         }  
  •     }  
  •       
  •     private void closeQuietly(Statement stat) {  
  •         //  
  •         if(stat == null) {  
  •             return;  
  •         }  
  •           
  •         //  
  •         try {  
  •             stat.close();  
  •         } catch (SQLException e) {  
  •             LOGGER.warn("failed to close statement", e);  
  •         }  
  •         }  
  • }  

  需要注意的是,以上代码适用于MySQL 5.0。对于MySQL 5.1,需要修改checkConnectionOnException方法,如下:
Java代码  


  •  if (t instanceof CommunicationsException || "com.mysql.jdbc.exceptions.jdbc4.CommunicationsException".equals(throwable.getClass().getName())) {  
  •     return CONNECTION_IS_INVALID;  
  • }  

  
  此外, 由于Spring Dao会转译SQLException,因此在Spring环境中,不能使用sqlState判断连接是否正常,而是需要使用基于query的方式,如下:
Java代码  


  • public int statusOnException(Connection c, Throwable t, String query, Throwable[] outParamCause) {  
  •     return checkConnection(c, query, outParamCause, null);  
  • }  

  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-274899-1-1.html 上篇帖子: MySQL AUTO_INCREMENT 要点记录 下篇帖子: mysql 常用小操作
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表