首页java › JBOSS连接池获取及返还和销毁

JBOSS连接池获取及返还和销毁

当应用需要进行业务处理时,首先会执行一个getConnection的操作,用于从连接池中获取连接,当业务处理完成后,需要把连接放回到连接池中,执行一个returnConnection的操作。
下面先看一下getConnection的源码:

  //getConnection方法返回的值是一个连接监听对象ConnectionListener
public ConnectionListener getConnection(Subject subject, ConnectionRequestInfo cri)
               throws ResourceException
   {

      subject = (subject == null) ? defaultSubject : subject;
      //获取连接信息
      cri = (cri == null) ? defaultCri : cri;
      //打印startWait,即当前时间,精确到毫秒
      long startWait = System.currentTimeMillis();
      try
      {
        /*等待最多xx毫秒获取一个信号量(permit),即permit操作。
         * permit操作用于获取当前可用的信号量,即是否有可以使用的信号量。
          因此,因为在创建连接池的时候,我们也创建了一个max值的信号集,
          所以,对于连接池中的连接数未达到max值的时候,肯定有可以使用的信号量。
           除非,所有的连接都已经在使用状态,并且连接数已经达到max值,
           这时,才有可能出现没有信号量,并出现超时的情况。
         */
         if (permits.attempt(poolParams.blockingTimeout))
         {
          //计算本次获取连接的阻塞时间=当前时间-开始获取信号量的时间
            long poolBlockTime =  System.currentTimeMillis() - startWait ;
            //累加阻塞时间。
            connectionCounter.updateBlockTime(poolBlockTime);
            //我们有一个权限去获取一个连接,判断是否在连接池中已经有一个可用的连接?
            ConnectionListener cl = null;
            do
            {
              //线程安全,即从连接池中获取连接是一个串行操作
               synchronized (cls)
               {
                 //判断连接池是否已经被shutdown,如果被shutdown,
                 //则抛出异常"The pool has been shutdown",并释放信号量
                  if (shutdown.get())
                  {
                     permits.release();
                     throw new ResourceException("The pool has been shutdown");
                  }
                  //如果可用的连接事件监听的arraylist大于0,则从arraylist的尾部取一个连接
                  if (cls.size() > 0)
                  {
                     cl = (ConnectionListener) cls.remove(cls.size() - 1);
                     //将arraylist中获取的连接加到到checkdout的一个hash结构中.
                     checkedOut.add(cl);
                     //计算已经使用的连接数
                     int size = (int) (maxSize - permits.permits());

                     //更新当前已经使用的最大连接数
                     if (size > maxUsedConnections)
                        maxUsedConnections = size;
                  }
               }
               //如果已经从连接事件监听数组中获取到了连接
               if (cl != null)
               {
                //我们从一个pool取一个ManagedConnection,并检查它是否符合要求?
                  try
                  {
                     Object matchedMC = mcf.matchManagedConnections
                     (Collections.singleton(cl.getManagedConnection())
                        ,subject, cri);
                     if (matchedMC != null)
                     {
                        if (trace)
                         log.trace("supplying ManagedConnection from pool: " + cl);
                        //通知connection listener是否它拥有权限
                        cl.grantPermit(true);
                        //返回连接,结束
                        return cl;
                     }

                     /*
                      * 匹配不成功,并且没有异常信息抛出。
                      * 在检查的时候,要么我们匹配错误,要么连接已经死亡
                       我们需要去辨别这些场景,但是现在,不管怎么样,我们都销毁连接。
                      */
                     log.warn("Destroying connection that could not be
                     successfully matched: " + cl);
                     synchronized (cls)
                     {
                      //从checkout的hashset中删除已经获取的连接。
                        checkedOut.remove(cl);
                     }
                     //销毁连接
                     doDestroy(cl);
                     cl = null;

                  }
                  //不管发生任何事,都销毁连接
                  catch (Throwable t)
                  {
                     log.warn("Throwable while trying to match ManagedConnection,
                           destroying connection: " + cl, t);
                     synchronized (cls)
                     {
                        checkedOut.remove(cl);
                     }
                     doDestroy(cl);
                     cl = null;

                  }
                  //如果发生意外,我们应该决定是否应该继续尝试去建立连接,
                  //由jboss配置文件中的的useFastFail参数来决定,默认为false。
                  //这个useFastFail设置为true,则立刻跳出get connection,并报错。
                  if(poolParams.useFastFail)
                  {
                     log.trace("Fast failing for connection attempt.
                    No more attempts will be made to acquire connection from pool
                     and a new connection will be created immeadiately");
                     break;
                  }

               }
            }
            //当连接监听队列>0,即还有可用的连接监听器
            while (cls.size() > 0);//end of do loop

            //OK, 我们不能够找到一个可以使用的连接,则新建一个
            try
            {
               //创建一个新的连接。这里不需要判断是否已经到达max连接数
              //因为前面已经获取了信号量,所以肯定可以创建连接。
               cl = createConnectionEventListener(subject, cri);
               synchronized (cls)
               {  //将创建的连接加入到checkout数组中。
                  checkedOut.add(cl);
                  int size = (int) (maxSize - permits.permits());
                //更新当前已经使用的最大连接数
                  if (size > maxUsedConnections)
                     maxUsedConnections = size;
               }

               //如果连接池还没有启动,则初始化连接池,并设置值started为true。
               //这里连接池可能被启用多次(因为非线程安全),但是这里没有危害。
               if (started == false)
               {

                  started = true;
                  if (poolParams.minSize > 0)
                     PoolFiller.fillPool(this);
               }
               if (trace)
                log.trace("supplying new ManagedConnection: " + cl);
               //通知connection listener是否它拥有权限
               cl.grantPermit(true);
               return cl;
            }
            catch (Throwable t)
            {
           log.warn("Throwable while attempting to get a new connection: " + cl, t);
               //return permit and rethrow
               synchronized (cls)
               {
                  checkedOut.remove(cl);
               }
               permits.release();
               JBossResourceException.rethrowAsResourceException(
             "Unexpected throwable while trying to create a connection: " + cl, t);
               throw new UnreachableStatementException();
            }
         }
         //这里的else操作,不能获取信号量,则抛出异常,报错连接池超时。
         else
         {
            // we timed out
            throw new ResourceException("No ManagedConnections available
              within configured blocking timeout ( "
                 + poolParams.blockingTimeout + " [ms] )");
         }

      }
      catch (InterruptedException ie)
      {
         long end = System.currentTimeMillis() - startWait;
       connectionCounter.updateBlockTime(end);
         throw new ResourceException("Interrupted while requesting permit!
         Waited " + end + " ms");
      }
   }

执行过程流程图如下:

关于getConnetion的几点说明
1.blockingTimeout是一个jboss的参数:< blocking-timeout-millis >5000< /blocking-timeout-millis >,它是一个获取信号量的超时时间,更确切的说,是从连接池中获取一个连接的超时时间。如果超过5000ms,不能够获取到信号量(连接),则jboss会抛出异常: “can not get connection,No ManagedConnections available within configured blocking timeout [xx] ms”。当然,只要当前正在使用的连接数没有到达MAX值,这个信号量一定能够被获取到。因为信号量一共有MAX值个,如果连接池中当前的连接不够用时,在获取信号量之后,新创建一个连接即可。建议可以设置成可以设成500ms左右,不需要设计过大,当应用中存在多个数据源时,可以防止因DB异常线程池带来的阻塞。如果网络环境不好的话,可以设置的更高一点。

2.连接池内部就是一个连接监听队列,每次都从队列的尾部获取连接。而IdleRemove线程,则是从这个队列头部开始进行清理。

3.连接池的获取,销毁,创建,这三个操作都是线程安全的操作。

4.业务在使用连接的过程中,会一直占有这个信号量,在returnConnection或者发生异常时释放信号量。能够获取信号量,则意味着肯定可以获取到连接。第二节中,我们讲到JBOSS在启动时会初始化一个信号量数组,长度为连接池的max参数。

当业务系统使用完连接后,需要把连接放回到连接池中它的主要见下图,源代码如下:

public void returnConnection(ConnectionListener cl, boolean kill) {
    synchronized (cls) {
      /*
			 * 判断连接是否已经被DESTROYED?
			 * 可能有其它的线程如background-validation及shuwdown
			 * 标记这个连接临听器为DESTORYED状态。
			 *
			 */
      if (cl.getState() == ConnectionListener.DESTROYED) {
        if (trace)
          log
          .trace("ManagedConnection is being returned after it was destroyed"
                  + cl);
        //释放信号量,并直接返回
        if (cl.hasPermit()) {
          // release semaphore
          cl.grantPermit(false);
          permits.release();
        }

        return;
      }
    }

    if (trace)
      log.trace("putting ManagedConnection back into pool kill=" + kill
          + " cl=" + cl);
    try {
      //前台应用强制清理连接。
      cl.getManagedConnection().cleanup();
    } catch (ResourceException re) {
      log.warn("ResourceException cleaning up ManagedConnection: " + cl,
          re);
      //清理失败,抛出异常,清理失败。
      kill = true;
    }

    synchronized (cls) {
      // 连接监听的状态为DESTROY或者DESTROYED,则设置kill为true
      if (cl.getState() == ConnectionListener.DESTROY
          || cl.getState() == ConnectionListener.DESTROYED)
        kill = true;
      //checkedOut队列中移除连接监听器。
      checkedOut.remove(cl);

      //如果kill==false,并且连接数>=最大连接max值,说明异常发生,再次设置kill=true
      if (kill == false && cls.size() >= poolParams.maxSize) {
        log
    .warn("Destroying returned connection, maximum pool size exceeded "
                + cl);
        kill = true;
      }

      //kill连接
      if (kill) {
        // Adrian Brock: A resource adapter can asynchronously notify us
        // that
        // a connection error occurred.
        // This could happen while the connection is not checked out.
        // e.g. JMS can do this via an ExceptionListener on the
        // connection.
        // I have twice had to reinstate this line of code, PLEASE DO
        // NOT REMOVE IT!
        cls.remove(cl);
      }
      //如果kill==false
      else {
        cl.used();
        //这个连接监听不属于连接监听队列,则加入。
        if (cls.contains(cl) == false)
          cls.add(cl);
        else
          log.warn("Attempt to return connection twice (ignored): "
              + cl, new Throwable("STACKTRACE"));
      }

      if (cl.hasPermit()) {
        //释放信号量
        cl.grantPermit(false);
        permits.release();
      }
    }

    if (kill) {
      if (trace)
        log.trace("Destroying returned connection " + cl);
      //销毁连接。
      doDestroy(cl);
    }
  }

执行过程流程图如下:

ReturnConnetion总结:
1.释放连接也是一个线程安全的操作。
2.在连接return时,有可能已经是destory的状态(前面第三节中讲到的SHUTDOWN操作,会对连接打上DESTORY的标记),这时,直接进行remove即可。
3.释放的连接若不属于连接监听队列(连接池),即加入到连接监听队列中(即连接池中)。
4.释放连接需要释放信号量。
5.在释放过程中,出现任何异常,则将连接从连接池中移除,并进行强制销毁。

 

JBOSS对于异常连接的处理:
默认情况下,JBOSS不会对无效的连接进行销毁。
如果我们需要对异常列表中的连接进行销毁,则需要在连接池的ds.xml中添加以下配置:

  
  < exception-sorter-class-name>
  	org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter
  	< /exception-sorter-class-name>

这个是ORACLE的异常列表,可以查看这个类里面定义了ORACLE的异常列表,当连接抛出这个列表中的错误时,即会进行销毁,如不能销毁,异常连接会一直存在连接池中。ORACLE异常列表如下:

 public boolean isExceptionFatal(final SQLException e)
    {
    		// I can't remember if the errors are negative or positive.
        final int error_code = Math.abs( e.getErrorCode() );  

        if( ( error_code == 28 )      //session has been killed
         || ( error_code == 600 )     //Internal oracle error
         || ( error_code == 1012 )    //not logged on
         || ( error_code == 1014 )    //Oracle shutdown in progress
         || ( error_code == 1033 )    //Oracle initialization or shutdown in progress
         || ( error_code == 1034 )    //Oracle not available
         || ( error_code == 1035 )    //ORACLE only available to users with RESTRICTED SESSION privilege
         || ( error_code == 1089 )    //immediate shutdown in progress - no operations are permitted
         || ( error_code == 1090 )    //shutdown in progress - connection is not permitted
         || ( error_code == 1092 )    //ORACLE instance terminated. Disconnection forced
         || ( error_code == 1094 )    //ALTER DATABASE CLOSE in progress. Connections not permitted
         || ( error_code == 2396 )    //exceeded maximum idle time, please connect again
         || ( error_code == 3106 )    //fatal two-task communication protocol error
         || ( error_code == 3111 )    //break received on communication channel
         || ( error_code == 3113 )    //end-of-file on communication channel
         || ( error_code == 3114 )    //not connected to ORACLE
         || ( error_code >= 12100 && error_code = 21000 ) &&
              (  (error_text.indexOf("SOCKET") > -1)     //for control socket error
              || (error_text.indexOf("CONNECTION HAS ALREADY BEEN CLOSED") > -1)
              || (error_text.indexOf("BROKEN PIPE") > -1) ) )
        {
            return true;
        }

        return false;
    }

类似的,我们也可以找到一个MYSQL的异常列表, org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter:

  public boolean isExceptionFatal(SQLException e)
   {
      if (e.getSQLState() != null)
      { // per Mark Matthews at MySQL
         if (e.getSQLState().startsWith("08"))
         {
            return true;
         }
      }
      switch (e.getErrorCode())
	  {
      	// Communications Errors
	  	case 1040: // ER_CON_COUNT_ERROR
      	case 1042: // ER_BAD_HOST_ERROR
      	case 1043: // ER_HANDSHAKE_ERROR
      	case 1047: // ER_UNKNOWN_COM_ERROR
      	case 1081: // ER_IPSOCK_ERROR
      	case 1129: // ER_HOST_IS_BLOCKED
      	case 1130: // ER_HOST_NOT_PRIVILEGED

        // Authentication Errors
      	case 1045: // ER_ACCESS_DENIED_ERROR

        // Resource errors
      	case 1004: // ER_CANT_CREATE_FILE
      	case 1005: // ER_CANT_CREATE_TABLE
      	case 1015: // ER_CANT_LOCK
      	case 1021: // ER_DISK_FULL
      	case 1041: // ER_OUT_OF_RESOURCES

        // Out-of-memory errors
      	case 1037: // ER_OUTOFMEMORY
      	case 1038: // ER_OUT_OF_SORTMEMORY

           return true;
      }

      return false;
   }

还有各种数据库的异常列表,都可以在自行配置,都定义在这个包下: org.jboss.resource.adapter.jdbc.vendor。

发表评论

注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>