`
gao_xianglong
  • 浏览: 461744 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

分布式配置管理平台与优雅停机

阅读更多

《分布式配置管理平台与优雅停机》

 

分布式配置管理平台与优雅停机存在什么必然联系?假设配置在配置中心的数据信息发生改变后,客户端感知并订阅到后,必然需要reset来及时响应,最典型的场景就是服务降级(开关)。

 

一些根据配置信息生成的对象实例(比如JDBC对象实例、Jedis对象实例),则需要重新生成新的对象实例,而不是简单的对目标值做reset。针对这种对象,本文提供2种方案可供参考:

1、动态注册Bean实例方案;

2、优雅停机实现restart方案。

 

如果项目中采用了Spring,那么可以选择对IOC容器中的那些Bean实例进行“剔除”后重新注册,这其实就是不重启状态下的动态注册。但这种方案却存在一个较为致命的缺陷,生产环境中,尤其是用户流量较大的网站,假设配置中心的数据源信息发生了变更,客户端感知并订阅后需要立马释放之前的资源连接,也不管之前的连接上是否还有未执行完成的任务,那么这对业务肯定会产生较为严重的影响。

 

那么我们来看第2种方案,优雅停机后restart。在Linux上,相信大家都使用过kill命令,在生产环境中,笔者最长用的就是kill(即kill -15 )PID和kill -9 PID,无论执行哪一个命令,最终程序进程都会停止,但是仍然存在区别。我们可以先来看一下信号变量,执行命令“kill -l”,如下所示:

 

简单来说,假设执行的是kill -15命令,那么操作系统会发送一个SIGTERM的信号对应用程序,应用程序收到SIGTERM信号后,会对当前所占用的资源进行释放,然后才会相对安全优雅的结束进程。而kill -9命令则恰恰相反,强制结束进程,可能会造成资源无法释放等问题。

 

谈到了kill,那么接下来要谈的就是如何让程序实现优雅停机,也就是说,假设数据源信息发生变化,应用系统要首先拒绝新的任务请求,并且等待那些还未执行完成的线程处理完成后,再restart,相对于第1种方案,采用此方案会更加安全可靠。

 

假设我们采用dubbo实施服务化后,那么dubbo原生是支持优雅停机的,其实也就是采用JDKShudownHook来实现,当然仅限kill -15 PID。这里也顺带说一下dubbo优雅停机的原理,如下所示:

服务提供方

· 停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器。

· 然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。

服务消费方

· 停止时,不再发起新的调用请求,所有新的调用在客户端即报错。

· 然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭。

 

接下来,我们再来尝试通过ShudownHook模拟优雅停机,如下所示:

public class ShutdownHookDemo {
 public ThreadPoolExecutor tPool = new ThreadPoolExecutor(10, 100, 1, TimeUnit.SECONDS,
   new ArrayBlockingQueue<Runnable>(2), new CallerRunsPolicy());

 public ShutdownHookDemo() {
  init();
  int threadSize = tPool.getCorePoolSize();
  final CountDownLatch latch = new CountDownLatch(threadSize);
  for (int i = 0; i < threadSize; i++) {
   tPool.execute(new Thread() {
    public void run() {
     try {
      /* 休眠10s */
      TimeUnit.SECONDS.sleep(((int) (Math.random() * 15)));
     } catch (Exception e) {
      e.printStackTrace();
     } finally {
      latch.countDown();
     }
    }
   });
  }
  try {
   latch.await();
   System.out.println("所有任务执行结束");
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }

 public void init() {
  Runtime.getRuntime().addShutdownHook(new Thread() {
   public void run() {
    /* 尝试中断线程,且不再接收新请求 */
    tPool.shutdownNow();
    while (true) {
     int activeCount = tPool.getActiveCount();
     System.out.println("活跃线程-->" + activeCount);
     if (0 == activeCount)
      break;
     try {
      TimeUnit.SECONDS.sleep(1);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   }
  });
 }

 public static void main(String[] args) {
  new ShutdownHookDemo();
 }
}

 

上述程序示例中,总共会有10条线程分别“处理”任务,耗时都在<=15S内,那么假设使用kill -15 PID命令终止进程后,通过ShutdownHook注册的钩子就会发生作用,一般来说,开发人员可以通过ShutdownHook来完成一些释放资源的操作,这里笔者选择拒绝新任务,且等待当前线程处理完当前任务,如果线程池中的所有线程都非活跃的情况,则代表程序可以安全的结束进程。

 

未经许可,不可转载

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics