• MySQL-JDBC Loadbalance深入解析


    背景说明

    公司的整个电商系统搭建在华为云上,根据老总的估计,上线3个月之后日订单量会达到百万级别,保守估计3个月之后总订单个数预计会有5千万。MySQL单表达到千万级别,就会出现明显的性能问题。根据如此规模的数据,当时考虑了2套解决方案:

    方案一:在业务上根据用户ID做拆分,将数据打散放在5台32U128G的华为云RDS上边

    方案二:直接使用华为云的分布式数据库中间件DDM

    方案一的好处是,分片算法全部在业务上实现,整个方案都在自己的控制下。后续问题定位,方案修改都会好很多;坏处是,整个方案需要业务代码支撑,访问到做了拆分的数据都需要做特殊处理,代价还是比较大的,而且对开发人员的能力要求很高。后续运维的工作也比较大。

    方案二的好处是,直接使用云服务后续不需要担心运维的事情,另外DDM从中间件层屏蔽了分库分表的具体实现,业务可以当做单库来操作,易用性以及对代码的要求、对开发人员的要求都会低了很多。缺点就是,使用了DDM之后,对华为云的粘性会大很多。

    综合考虑了两个方案的优缺点,最终选择了方案二,主要是基于对华为云技术能力和后续蓬勃发展的信心。

    对DDM做了一定的调研,的确是一个非常不错的分库分表服务。支持超大规模数据,10备于单机数据库的超强性能,百万并发,读写分离,支持平滑扩容等等。。。优点数不胜数~

    搭建到华为云之后,一直平稳运行,但是前阵子出了个奇怪的问题,在DDM技术专家的协助下,很快定位了出来,结果是MySQL-JDBC的一个bug导致。作为一个具有打破砂锅问到底、不破楼兰誓不还的程序员,决定对MySQL的相关参数做个详细的分析,免得从一个坑里边爬出来又进了另外一个。

    Loadbalance模式说明

    为了提供高性能,百万并发,DDM自身是以无状态的集群形式对外提供的。内部怎么做的我们不清楚,能看到的是,每个DDM提供了多个访问地址,每个库的访问url类似于:jdbc:mysql:loadbalance://192.168.0.35:5066,192.168.0.192:5066,192.168.0.175:5066,192.168.0.139:5066/orderdb?loadBalanceAutoCommitStatementThreshold=5

    从访问的url看,内部应该是多台DDM节点的,实际上从我们测试的情况看,访问任何一台的效果都是一样的。猜测,内部的交互应该是类似如下图的:

    跟DDM的技术专家求证,的确是如此的,心里有点小得意~~

    我们的代码全部是java的代码,连接池用的是druid,根据DDM的指导,将url配置好就能正常访问了。感觉关健的就在loadbalance这个,应该是告诉了驱动,通过负载均衡方式访问DDM。在网上查了下,这种方式是直接在驱动层面做的负载均衡,相比通过负载均衡器的方式,少了一次网络转发,怪不得效率会这么高。不过,APP到底是访问哪个DDM,内部机制是什么样子的?这些在网上查了下,都是语焉不详,没办法只好从MySQL JDBC的源码入手了。

    驱动的源码是托管在github上,我们当前用的是DDM推荐的5.1.44版本的:https://github.com/mysql/mysql-connector-j/tree/5.1.44

    核心的就是几个Loadbalance开头的类:

    代码比较多,其他的就不多说了,最关键的就是下边这块代码:

    LoadBalancedConnectionProxy.java类的pickNewConnection()函数

    这个函数在创建连接对象、一个事务提交或者回滚都会调用,作用就是轮换下一个DDM节点。这块代码的逻辑就是,根据一定的负载均衡策略挑选一个节点的连接,做个基本的连接有效性探测,然后将当前连接的状态同步到新连接(见 Table 2 MultiHostConnectionProxy.syncSessionState())。同步完毕,就把当前使用的连接设置为新挑选的连接。如果所有的连接都不可用,就把当前状态设置为了Closed状态。看着快代码,感觉MySQL的有些代码也不严谨,比如如果在获取新连接的时候,如果抛了SQLException出来,这个异常就直接被吃掉了,不会抛出去,也不会有任何信息记录下来,这个对后续的问题定位还是很不方便的,不知道是出于什么考虑的。

    Table 1 LoadBalancedConnectionProxy.pickNewConnection()
       synchronized void pickNewConnection() throws SQLException {
           if (this.isClosed && this.closedExplicitly) {
               return;
           }
           if (this.currentConnection == null) { // startup
               this.currentConnection = this.balancer.pickConnection(this, Collections.unmodifiableList(this.hostList),
                       Collections.unmodifiableMap(this.liveConnections), this.responseTimes.clone(), this.retriesAllDown);
               return;
           }
           if (this.currentConnection.isClosed()) {
               invalidateCurrentConnection();
           }
           int pingTimeout = this.currentConnection.getLoadBalancePingTimeout();
           boolean pingBeforeReturn = this.currentConnection.getLoadBalanceValidateConnectionOnSwapServer();
           for (int hostsTried = 0, hostsToTry = this.hostList.size(); hostsTried < hostsToTry; hostsTried++) {
               ConnectionImpl newConn = null;
               try {
                   newConn = this.balancer.pickConnection(this, Collections.unmodifiableList(this.hostList), Collections.unmodifiableMap(this.liveConnections), this.responseTimes.clone(), this.retriesAllDown);
                   if (this.currentConnection != null) {
                       if (pingBeforeReturn) {
                           if (pingTimeout == 0) {
                               newConn.ping();
                           } else {
                               newConn.pingInternal(true, pingTimeout);
                           }
                       }
                       syncSessionState(this.currentConnection, newConn);
    
                   }
                   this.currentConnection = newConn;
                   return;
               } catch (SQLException e) {
                   if (shouldExceptionTriggerConnectionSwitch(e) && newConn != null) {
                       // connection error, close up shop on current connection
                       invalidateConnection(newConn);
                   }
               }
           }
           // no hosts available to swap connection to, close up.
           this.isClosed = true;
           this.closedReason = "Connection closed after inability to pick valid new connection during load-balance.";
       }
    
    
    
    Table 2 MultiHostConnectionProxy.syncSessionState()
       static void syncSessionState(Connection source, Connection target, boolean readOnly) throws SQLException {
           if (target != null) {
               target.setReadOnly(readOnly);
           }
           if (source == null || target == null) {
               return;
           }
    target.setAutoCommit(source.getAutoCommit());
           target.setCatalog(source.getCatalog());
           target.setTransactionIsolation(source.getTransactionIsolation());
           target.setSessionMaxRows(source.getSessionMaxRows());
       }
    

      

    MySQL-JDBC Loadbalance参数说明

    明白了MySQL-JDBC的Loadbalance的相关机制,最重要的还是要对相关的参数有个详细的了解,并且设置有效的值,Loadbalance相关一共有十几个参数,几个比较关键的如下表所示:

    其他还有几个参数,一般用不到,也就不罗列出来了。大家感兴趣的话可以关注公众号:中间件小哥(zhongjianjianxiaoge)了解更多哟~

  • 相关阅读:
    DNNClassifier 深度神经网络 分类器
    浏览器对MP4视频 帧宽度 高度的兼容性
    UnicodeEncodeError:'latin-1' codec can't encode character
    文件夹下 文件计数
    the largest value you actually can transmit between the client and server is determined by the amount of available memory and the size of the communications buffers.
    the “identity” of an object
    广告特征 用户特征
    如果一个维度全覆盖,则有效维度应该对该维度全覆盖
    a high-level neural networks AP
    使用 LDA 挖掘的用户喜好主题
  • 原文地址:https://www.cnblogs.com/middleware/p/9444435.html
Copyright © 2020-2023  润新知