分布式配置管理平台与优雅停机
《分布式配置管理平台与优雅停机》
分布式配置管理平台与优雅停机存在什么必然联系?假设配置在配置中心的数据信息发生改变后,客户端感知并订阅到后,必然需要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原生是支持优雅停机的,其实也就是采用JDK的ShudownHook来实现,当然仅限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() { 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来完成一些释放资源的操作,这里笔者选择等待当前线程处理完当前任务,如果线程池中的所有线程都非活跃的情况,则代表程序可以安全的结束进程。
未经许可,不可转载
已有 0 人发表留言,猛击->> 这里<<-参与讨论
ITeye推荐