首页java › JBOSS连接池的初始化及关闭

JBOSS连接池的初始化及关闭

在启动完成之后,连接池紧接着会进行一个initialize的操作,这一节主要介绍这个initialize所完成的操作。具体涉及到如下几个参数:
< idle-timeout-minutes >:一个连接的最大空闲超时时间,即在连接被关闭之前,连接可以空闲的最长时间,超过这个时间连接就会被关闭。这个参数设置为0则禁用这个功能,文档上说默认值为15分钟,我看的jboss4.2.3的源代码中默认值为30分钟。
< background-validation >:在jboss4.0.5版本中,增加了一个后台连接验证的功能,用于减少RDBMS系统的负载。当使用这个功能的时候,jboss将使用一个独立的线程(ConnectionValidator)去验证当前池中的连接。这个参数必需在设置为true时才能生效,默认设置为false。
< background-validation-minutes >:ConnectionValidator线程被唤醒的定时间隔。默认设置为10分钟。注意:为谨慎起见,设置这个值稍大些,或者小于idle-timeout-minutes。
< background-validation-millis >:从jboss5.0版本开始,代替了background-validation-minutes参数。参数background-validation-minutes不再被支持。同时background-validation这个参数也被废弃。只要配置了background-validation-millis > 0,则启用后台验证。更多内容查看:https://jira.jboss.org/browse/JBAS-4088。

连接池的初始化方法如下:

	/**
  * Initialize the pool
  */
 protected void initialize()
 {
 	/*将一个连接池对象注册到IdleRemover线程中,表示这个连接池使用IdleRemover来进行管理。
 	IdleRemover线程是空闲连接清理线程,被唤醒的周期是poolParams.idleTimeout/2。
 	即配置的idle-timeout-minutes参数/2。
 	默认idle-timeout-minutes为30分钟,所以清理线程是15分钟运行一次。
 	*/
 if (poolParams.idleTimeout != 0)
    IdleRemover.registerPool(this, poolParams.idleTimeout);

	/*将一个连接池对象注册到ConnectionValidator线程中,表示这个连接池使用
	ConnectionValidator来进行管理。IdleRemover线程是一个验证连接池的线程,
 	被唤醒的周期是poolParams.backgroundValidation/2。
 	即配置的background-validation-millis 参数/2。
 	默认background-validation-millis为10分钟,所以清理线程是5分钟运行一次。
 	*/
  if (poolParams.backgroundValidation)
    {

       log.debug("Registering for background validation at interval "
       					+ poolParams.backgroundInterval);
       ConnectionValidator.registerPool(this, poolParams.backgroundInterval);

    }

 }

IdleRemover和 ConnectionValidator两个线程的处理方式是一致的。定时调度的处理方式也是完全一致的。
区别在于,定时任务的启动IdleRemover是15分钟清理一次空闲的连接,而ConnectionValidator是5分钟进行一次连接验证,后面会给出主要的两个方法。

因为两者调度方式一致,这里以IdleRemover类为例,来看看这两个线程的具体调度算法:

.......
private static final IdleRemover remover = new IdleRemover();

  //注册一个连接池清理对象,即将一个连接池清理对象加入到pools中,并传入一个清理的时间参数
  public static void  registerPool(InternalManagedConnectionPool mcp, long interval)
  {
     remover.internalRegisterPool(mcp, interval);
  }
  //反注册一个连接池清理对象,并且设置interval = Long.MAX_VALUE
  public static void unregisterPool(InternalManagedConnectionPool mcp)
  {
     remover.internalUnregisterPool(mcp);
  }
  .......

IdleRemover线程的启动:

 private IdleRemover ()
   {
      AccessController.doPrivileged(new PrivilegedAction()
      {
         public Object run()
         {
            Runnable runnable = new IdleRemoverRunnable();

            Thread removerThread = new Thread(runnable, "IdleRemover");
            removerThread.setDaemon(true);
            removerThread.start();
            return null;
         }
      });
   }

 线程的run方法如下:
  /**
  * Idle Remover background thread
  */
 private class IdleRemoverRunnable implements Runnable
 {
    public void run()
    {
  	  //更改上下文ClassLoader.
       setupContextClassLoader();

       //这是一个线程安全的操作。
       synchronized (pools)
       {
          while (true)
          {
             try
             {
          /*
           * interval在这个类中的初始值是一个无限大的值:interval = Long.MAX_VALUE。
           即线程开始运行时,即一直wait在这里。
           当执行internalRegisterPool方法,即第一次被唤醒时,
           interval即被设置为传入的interval/2,
           如果idle-time-out设置为30分钟,这个interval的值即被设为15分钟。
           即每次wait15分钟,表示15分钟线程被唤醒清理一次idle的连接。
           */
              pools.wait(interval);
              log.debug("run: IdleRemover notifying pools, interval: " + interval);
            /*
             * 这里的pools和第二节中讲到的pools一致,是一个临时队列,区别是这个临时队列
             不会进行清理,只有调用internalUnregisterPool方法才会从队列中清理出去。
             *遍历pool中的连接池对象,对所有的连接池对象,执行removeTimeout,后面会详细介绍。
            */
                for (Iterator i = pools.iterator(); i.hasNext(); )
                   ((InternalManagedConnectionPool)i.next()).removeTimedOut();
                //设置next值,这个next表示pools下一次需要清理的时间点。
                next = System.currentTimeMillis() + interval;
                if (next < 0)
                   next = Long.MAX_VALUE;      

             }
             catch (InterruptedException ie)
             {
                log.info("run: IdleRemover has been interrupted, returning");
                return;
             }
             catch (RuntimeException e)
             {
              log.warn("run: IdleRemover ignored unexpected runtime exception", e);
             }
             catch (Error e)
             {
                log.warn("run: IdleRemover ignored unexpected error", e);
             }
          }
       }
    }
 }

注册连接池的方法如下:

 private void internalRegisterPool(InternalManagedConnectionPool mcp, long interval)
   {
      log.debug("internalRegisterPool: registering pool with interval "
      			+ interval + " old interval: " + this.interval);
      synchronized (pools)
      {
    	   //往pool里面增加需要清理的连接池对象,即注册连接池对象。
         pools.add(mcp);
         /*这里有两个条件:
          * 1.interval>1,即idle-time-out设置至少为2分钟,pools才会执行nofity()。
          * 2.interval/2 < LONG.MAX_VALUE,防止idle-time-out设置过大。
          */
         if (interval > 1 && interval/2 < this.interval)
           {
             //设置为interval为interval/2,即清理线程被唤醒的间隔时间。
             this.interval = interval/2;
             //本连接池下一次可能清理的时间。
             long maybeNext = System.currentTimeMillis() + this.interval;
             /*如果next即wait线程的下一次唤醒的时间>maybeNext的时间。
            即立即唤醒清理线程,进行第一次清理。
            这样的目的是为了让这个连接池的注册能够立即生效,而不被旧的interval影响。
            */
            if (next > maybeNext && maybeNext > 0)
            {
               next = maybeNext;
               log.debug("internalRegisterPool: about to notify thread:
               	old next: " + next + ", new next: " + maybeNext);
               pools.notify();
            }
         }
      }
   }

反注册方法,即从pools队列中去除注册的连接池,表示这个连接池不需要进行清理。

  private void internalUnregisterPool(InternalManagedConnectionPool mcp)
   {
      synchronized (pools)
      {
         pools.remove(mcp);
         if (pools.size() == 0)
         {
            log.debug("internalUnregisterPool: setting interval to Long.MAX_VALUE");
            interval = Long.MAX_VALUE;
         }
      }
   }

以上是IdleRemover.registerPool和 ConnectionValidator.registerPool.registerPool两个线程的调度方式,下面来看一下这两个方法执行的具体操作。

IdleRemover对空闲连接的清理:

   public void removeTimedOut()
   {
	   //合建一个清理队列
      ArrayList destroy = null;
      long timeout = System.currentTimeMillis() - poolParams.idleTimeout;
      while (true)
      {
         synchronized (cls)
         {
            // 如果连接池中没有连接,则直接返回。
            if (cls.size() == 0)
               break;

            /*
             * 获取cls中的第一个连接,即头部的连接。
             * 后面一节中会讲到,在getconnection时,都是从cls的尾部获取。
             * 所以,cls头部的连接,肯定是最近最少被使用的。
             */
            ConnectionListener cl = (ConnectionListener) cls.get(0);
            //判断是否超时,return lastUse < timeout
            if (cl.isTimedOut(timeout))
            {
               //销毁连接计数
               connectionCounter.incTimedOut();
               // 销毁这个连接,并加入到销毁队列
               cls.remove(0);
               if (destroy == null)
                  destroy = new ArrayList();
               destroy.add(cl);
            }
            else
            {
               //它们是按照时间顺序插入的, 如果这个连接没有超时,肯定没有其它的连接超时。
               break;
            }
         }
      }

      // 有需要销毁的连接,将这些连接进行销毁,并对销毁的连接数进行计数。
      if (destroy != null)
      {
         for (int i = 0; i < destroy.size(); ++i)
          {
             ConnectionListener cl = (ConnectionListener) destroy.get(i);
             if (trace)
                log.trace("Destroying timedout connection " + cl);
                doDestroy(cl);
          }
          // 销毁完空闲的连接后,判断连接池没有shutdown,并且最小值大于0。
          //进行将连接池填充到最小值的操作。
          if (shutdown.get() == false && poolParams.minSize > 0)
            PoolFiller.fillPool(this);
      }
   }

关于fillPool方法可以参考第二节:http://www.dbafree.net/?p=300
关于IdleRemover线程的具体执行过程如下:

ConnectionValidator线程对于连接的验证:

 public void validateConnections() throws Exception
   {

      if (trace)
         log.trace("Attempting to  validate connections for pool " + this);
      //获取信号量,若不能获取,表时当前的连接都在被使用。直接结束validate。
      if (permits.attempt(poolParams.blockingTimeout))
      {

         boolean destroyed = false;

         try
         {

            while (true)
            {

               ConnectionListener cl = null;

               synchronized (cls)
               {
                  if (cls.size() == 0)
                  {
                     break;
                  }
               //对连接池中的每个连接进行check,将removeForFrequencyCheck方法见下面。
                  cl = removeForFrequencyCheck();

               }

               if (cl == null)
               {
                  break;
               }

               try
               {

                Set candidateSet = Collections.singleton(cl.getManagedConnection());
                //当前时间-上一次check时间>=后台的validate时间的连接,进行validating操作。

                  if (mcf instanceof ValidatingManagedConnectionFactory)
                  {
                     ValidatingManagedConnectionFactory vcf =
                     			(ValidatingManagedConnectionFactory) mcf;
                     candidateSet = vcf.getInvalidConnections(candidateSet);

                     if (candidateSet != null && candidateSet.size() > 0)
                     {

                        if (cl.getState() != ConnectionListener.DESTROY)
                        {
                           doDestroy(cl);
                           destroyed = true;
                        }
                     }

                  }
                  else
                  {
                     log.warn("warning: background validation was specified with
                     a non compliant ManagedConnectionFactory interface.");
                  }

               }
               finally
               {
                  if(!destroyed)
                  {
                     synchronized (cls)
                     {
                        returnForFrequencyCheck(cl);
                     }
                  }

               }

            }

         }
         finally
         {
            permits.release();
            //destory之后,也进行一个fillPool的操作,这个操作可以参考第二节的内容
            if (destroyed && shutdown.get() == false && poolParams.minSize > 0)
            {
               PoolFiller.fillPool(this);
            }

         }

      }

   }

这个validate操作,由前台的参数控制,可以传入自定义的SQL来验证。也可以直接调用jdbc的ping database操作,如调用ping database方法,通过jdbc驱动中可以找到,oracle执行的是select * from dual来验证,不一一列举。见下面的截图vaildate的方法:

removeForFrequencyCheck方法如下:

   private ConnectionListener removeForFrequencyCheck()
   {

      log.debug("Checking for connection within frequency");

      ConnectionListener cl = null;

      for (Iterator iter = cls.iterator(); iter.hasNext();)
      {

         cl = (ConnectionListener) iter.next();
         long lastCheck = cl.getLastValidatedTime();
         //返回 当前时间 - 上一次check时间 >= 设置的validate时间的第一个连接。
         //即表示这个连接没有在backgroundInterval区间内进行check。
         if ((System.currentTimeMillis() - lastCheck)
         				>= poolParams.backgroundInterval)
         {
            cls.remove(cl);
            break;

         }
         else
         {
            cl = null;
         }

      }

      return cl;
   }

连接池的shutdown

	public void shutdown() {
		//设置shutdown标志位为true
		shutdown.set(true);
		//清除IdleRemover线程和ConnectionValidator线程的初始化
		IdleRemover.unregisterPool(this);
		ConnectionValidator.unRegisterPool(this);
		//destroy所有checkout队列和连接临听队列中的连接
		flush();
	}

flush()函数清理所有的连接:包括checkd out队列(已经被使用)中的连接及空闲的队列的连接。这里将空闲的队列的连接监听进行直接销毁,而checkd out队列的连接设置为DESTROY状态,并没有进行销毁。这是一个安全的操作,我们在下一节的returnConnection方法中可以看到对于DESTROY状态连接的清理。

	public void flush() {
		//生成一个destroy队列
		ArrayList destroy = null;
		synchronized (cls) {
			if (trace)
				log.trace("Flushing pool checkedOut=" + checkedOut + " inPool="
						+ cls);

			// 标记checkd out的连接为清理的状态。
			for (Iterator i = checkedOut.iterator(); i.hasNext();) {
				ConnectionListener cl = (ConnectionListener) i.next();
				if (trace)
					log
		.trace("Flush marking checked out connection for destruction "
									+ cl);
				cl.setState(ConnectionListener.DESTROY);
			}
			// 销毁空闲的,需要清理的连接监听器,并加入到destroy队列中。
			while (cls.size() > 0) {
				ConnectionListener cl = (ConnectionListener) cls.remove(0);
				if (destroy == null)
					destroy = new ArrayList();
				destroy.add(cl);
			}
		}

		//销毁destory队列中的所有连接临听。
		if (destroy != null) {
			for (int i = 0; i < destroy.size(); ++i) {
 				ConnectionListener cl = (ConnectionListener) destroy.get(i);
 				if (trace)
 					log.trace("Destroying flushed connection " + cl);
 				doDestroy(cl);
 			}
 			// 如果shutdown==false,即连接池没有shutdown,则再执行一个fillPool的操作
 			//将连接数填充到min值。
 			if (shutdown.get() == false && poolParams.minSize > 0)
				PoolFiller.fillPool(this);
		}

	}

关于shutdown操作,流程图如下:

总结:
idle-timeout-minutes参数:后台定时清理过度空闲的连接,从而节省数据库的连接资源,相应线程的wait时间为idle-timeout-minutes/2。
background-validation-millis参数:后台定时验证连接是否有效,对于oracle,内部执行一个select * from dual;的方法来进行验证,相应线程的wait时间为background-validation-millis/2,JBOSS 4的版本中,默认不启用这个验证。

JBOSS会各自启动一个线程来为这两个参数工作,线程内部的调度机制完全一致。IdleRemover线程被唤醒(即每隔多少时间执行)的区间是idle-timeout-minutes参数/2,ConnectionValidator线程被唤醒(即每隔多少时间执行)的区间是background-validation-millis/2。

在销毁空闲的连接(IdleRemover)和无效的连接(ConnectionValidator)后,都会执行一个prefill的操作,将连接池中的连接数填充到min值,所以,对于连接池min需要合理的进行设置,如果min设置过大,JBOSS会将连接不断的进行销毁->创建->销毁->创建…(idle线程对空闲连接销毁,销毁后小于min值,然后马上又创建,新创建的连接处于空闲状态,于是又被销毁…)

总之,我们需要合理的设置连接池min值,对于某些系统来说,数据库的连接资源是很昂贵的。
前段日子在公司的核心系统上优化的一个连接池,主要也是对于min值的优化:一个生产库的JBOSS连接池调整优化及分析。对于连接数的调优,后面会专门整理一篇文章。

1 条评论。[ 发表评论 ]

发表评论

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