mybatisのメモ-データベース接続プールの実装


簡単な実装データベース接続プールの実装といえば、よく知られていないかもしれませんが、多かれ少なかれ実装されているはずです.では、まず簡単なデータベース接続プールの実装についてお話しします.
接続プールである以上、まず接続が必要で、それからプール(くだらない話)があり、接続はjdkのConnectionを使用し、プールはListを使用すればよい.接続が必要な場合はリストから取得し、リストにない場合は新しいnewをリストに追加すればよい.使用が完了したらlistに接続を戻せば、最も簡単な接続プールが実現します.(PS:関連インタフェースは必ずスレッドが安全である)
上では簡単なスレッドプールを実現し、ほとんどの使用シーンを満たしていますが、プログラムの性能、同時性、拡張性、メンテナンス性、使いやすさなどの面からまだ不足しています.以下では、上の簡単な接続プールの不足点を挙げ、これらの不足点について一歩一歩改善します.
  • 最大接続数無節制に新しい接続を作成できないように設定するには、最大接続数を制限する必要があります.制限された接続数を超えたり、古い接続から新しい接続を復元できない場合は、呼び出し元に一定のフィールドを投げ出します.呼び出し者は、例外に基づいて、要求をキャッシュして、新しい利用可能な接続があるまで処理することを決定することができる.
  • アイドル接続を解放ある時点で同時性が高い場合、接続プール内の接続の数はプログラム起動以来最大の同時数に等しいが、同時性が小さい場合、[1]接続プール内の接続は使用されていないが、メモリ領域を占有し続け、リソースの浪費である.[2]現在のコンカレント度が非常に小さくても、コア数の接続を保持する必要があり、突然爆発するコンカレントに対応するには、poolMaximumIdleConnections(最大アイドル接続数)を設定する必要があることをまとめると、
  • 接続を取得するタイムアウト設定接続取得タイムアウト等値を設定することで、無限の待ち時間を防止できます.......より良いパフォーマンスを得るためには、最適化が必要なものがたくさんあります.幸いなことに、業界にも対応するオープンソースの実装がたくさんあります.優れたオープンソースの実装を分析することで、優れた接続プールを設計すれば理解することができます.

  • mybatisの接続プールの実現上で簡単な接続プールの実現を述べ、以下mybatisのデータベース接続プールの実現に対してソースコードレベルの分析を行い、ソースコードに深く入り込んでこそAPIを正しく呼び出すことができる.
    実はmybatisの中の接続プールの実現は上の簡単な接続プールの実現と同じで、ただ上述のあれらの最適化する必要がある点を実現しただけで、mybatisの中でデータベースの接続プールの実現は主にクラスPooledDataSourceの中で、直接このクラスのgetConnectionの方法を探し当てて見ることができて、この方法は1つのpopConnectionの方法を呼び出して、この方法は、接続プールから接続を取得するプロセスを本当に処理します.次に、この方法を詳しく分析します.
    private PooledConnection popConnection(String username, String password) throws SQLException {
        boolean countedWait = false;
        PooledConnection conn = null;
        long t = System.currentTimeMillis();
        int localBadConnectionCount = 0;
    
        while (conn == null) {
          //      ,         
          synchronized (state) {
            // 1、            
            if (!state.idleConnections.isEmpty()) {
              // Pool has available connection
              conn = state.idleConnections.remove(0);
              if (log.isDebugEnabled()) {
                log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
              }
            } else {
              // Pool does not have available connection
              // 2、                  
              if (state.activeConnections.size() < poolMaximumActiveConnections) {
                // Can create new connection
                //             ,       
                conn = new PooledConnection(dataSource.getConnection(), this);
                if (log.isDebugEnabled()) {
                  log.debug("Created connection " + conn.getRealHashCode() + ".");
                }
              } else {
                // Cannot create new connection
                //             ,        
                PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                //                   ,             
                //        ,            
                if (longestCheckoutTime > poolMaximumCheckoutTime) {
                  // Can claim overdue connection
                  state.claimedOverdueConnectionCount++;
                  state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                  state.accumulatedCheckoutTime += longestCheckoutTime;
                  state.activeConnections.remove(oldestActiveConnection);
                  if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                    try {
                      //           
                      oldestActiveConnection.getRealConnection().rollback();
                    } catch (SQLException e) {
                      /*
                         Just log a message for debug and continue to execute the following
                         statement like nothing happend.
                         Wrap the bad connection with a new PooledConnection, this will help
                         to not intterupt current executing thread and give current thread a
                         chance to join the next competion for another valid/good database
                         connection. At the end of this loop, bad {@link @conn} will be set as null.
                       */
                      log.debug("Bad connection. Could not roll back");
                    }  
                  }
                  //     ,             
                  //               ,        Connection close  
                  //        (            )
                  conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                  conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                  conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
                  oldestActiveConnection.invalidate();
                  if (log.isDebugEnabled()) {
                    log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                  }
                } else {
                  // Must wait
                  //                 ,          ,        
                  //        ,          ,       conn=null
                  try {
                    if (!countedWait) {
                      state.hadToWaitCount++;
                      countedWait = true;
                    }
                    if (log.isDebugEnabled()) {
                      log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                    }
                    long wt = System.currentTimeMillis();
                    state.wait(poolTimeToWait);
                    state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                  } catch (InterruptedException e) {
                    break;
                  }
                }
              }
            }
            if (conn != null) {
              // ping to server and check the connection is valid or not
              //                            
              //         conn    ,           ,           ;
              //           ,      。
              if (conn.isValid()) {
                if (!conn.getRealConnection().getAutoCommit()) {
                  conn.getRealConnection().rollback();
                }
                conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
                conn.setCheckoutTimestamp(System.currentTimeMillis());
                conn.setLastUsedTimestamp(System.currentTimeMillis());
                state.activeConnections.add(conn);
                state.requestCount++;
                state.accumulatedRequestTime += System.currentTimeMillis() - t;
              } else {
                if (log.isDebugEnabled()) {
                  log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                }
                state.badConnectionCount++;
                localBadConnectionCount++;
                conn = null;
                if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
                  if (log.isDebugEnabled()) {
                    log.debug("PooledDataSource: Could not get a good connection to the database.");
                  }
                  throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                }
              }
            }
          }
    
        }
    
        //         ,            
        if (conn == null) {
          if (log.isDebugEnabled()) {
            log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
          }
          throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        }
    
        return conn;
      }

    以上の解析から,この方法のAPIは正常なデータベース接続を返すか,異常情報を投げ出すか,呼び出し者は情報をキャプチャし,今回のデータベース操作要求をタイムリーにキャッシュする必要があることが分かった.
    上記ではConnectionに戻るときにエージェントのカプセル化を行いましたが、このエージェントはPooledConnectionで、このクラスはjdkのConnectionのエージェントです.以下、主にそのエージェントのinvokeメソッドを見て、そのエージェントクラスの主な役割を説明します.
    private static final String CLOSE = "close";
    private final Connection realConnection;
    ......
    /*
       * Required for InvocationHandler implementation.
       *
       * @param proxy  - not used
       * @param method - the method to be executed
       * @param args   - the parameters to be passed to the method
       * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
       */
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //       
        String methodName = method.getName();
        //      close  
        if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
          //    close  ,                ,         
          dataSource.pushConnection(this);
          return null;
        } else {
          //     close  ,              Connection  
          // (      PooledConnection      ,       jdk Connection)
          try {
            if (!Object.class.equals(method.getDeclaringClass())) {
              // issue #579 toString() should never fail
              // throw an SQLException instead of a Runtime
              checkConnection();
            }
            return method.invoke(realConnection, args);
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }

    この設計では、クライアントのsocket接続プール、例えばjschのsshのセッション接続プールなど、任意の接続を使用して、消費の大きいシーンを確立することができる.
    学習フレームワークのソースコードにも方法があります!