最新消息: 关于Git&GitHub 版本控制你了解多少?
您现在的位置是:群英 > 服务器 > 建站服务器 >
避免大规模故障的微服务架构
51CTO发表于 2020-09-01 18:14 次浏览
微服务架构通过一种良好的服务边界划分,能够有效地进行故障隔离。但就像其他分布式系统一样,在网络、硬件或者应用级别上容易出现问题的机率会更高。服务的依赖关系,导致在任何组件暂时不可用的情况下,就它们的消费者而言都是可以接受的。为了能够降低部分服务中断所带来的影响,我们需要构建一个容错服务,来优雅地应对特定类型的服务中断。

问题分析

我们生产环境的微服务框架是通过Zookeeper进行服务注册发现的。如下图1所示:

图1:服务注册调用流程图

其主要调用逻辑为:

  • 生成容器,服务在容器内启动;
  • 注册到服务路由器(Zookeeper);
  • 服务调用者订阅服务路由器;
  • 服务路由器上发生注册变动,通知服务调用者重新获取新的注册列表;
  • 服务调用者根据获取到的服务端列表,进行服务调用。

但是当服务端实例停止后,由于服务端不会主动去更改服务路由器上的注册信息,客户端需要40秒(目前应用到Zookeeper的会话超时时间配置)才能剔除这个异常配置,在这40秒内应用还是会不断的尝试访问这个不存在的实例,导致产生大量业务报错。

这也与每次实例重启后,报错的持续时间相符。且由于微服务的特性,每笔实际业务请求,会根据业务需要多次调用同一个服务,这增加了每笔业务请求访问到异常实例的可能性,加剧了业务失败的概率。

所以这是一个由于服务实例暴力停止,并被微服务架构下单笔业务请求多次访问的倍数放大后,产生的问题。

解决方案

既然这是由于服务实例暴力停机导致的问题,所以我们开始研究基于微服务的优雅停机方式。

微服务架构中的应用优雅停机,主要是指应用实例有计划而平滑(即不产生需要处理的动作或不产生异常报错)的退出方式。主要有两种方式:

  • 方式一:通过微服务框架自带的检测能力来实现,如在Spring Cloud微服务框架中,提供了actuator组件的/health端点来实现。客户端需要实现一个自定义的HealthCheckHandler,它将应用的健康状态保存到内存中,只需在服务器上利用curl发送shutdown命令,状态一旦发生改变,就会重新向服务器进行注册。
  • 方式二:通过注册JDK的ShutdownHook(钩子)来实现,当系统接收到退出指令后,首先把自己从Zookeeper注册服务器上下线,不再接收新的消息,然后将积压的请求处理完,最后调用资源回收接口将资源销毁,最后各线程退出执行。

由于我们生产环境未采用开源通用型微服务架构,且应用都是基于JAVA开发,因此我们采用的是方式二:通过注册JDK的ShutdownHook(关闭钩子)来实现优雅停机方式。

关闭钩子本质是一个线程(也称为Hook线程),用来监听JVM的关闭。通过Runtime的addShutdownHook可以向JVM注册一个关闭钩子。Hook线程在JVM正常关闭才会执行,强制关闭时不会执行。

JVM正常关闭的场景主要包括如下几种:

  • Java程序正常运行完退出时会被调用;
  • 终端中通过ctrl-c终止命令时会被调用;
  • JVM发生OutOfMemory而退出时会被调用;
  • Java程序中执行System.exit()时会被调用;
  • 操作系统关闭时会被调用;
  • linux通过kill pid(或者kill -15 pid)结束进程时会被调用。

JDK中ShutdownHook相关的源码如下图2所示:

图2:添加删除实现

ShutdownHook如何被调用呢?使用java.lang.Runtime.addShutdownHook方法, 可以注册一个JVM关闭的钩子(线程),如下图3所示。我们想要程序在JVM退出时做的各种扫尾工作,比如:关闭资源、注销服务路由器上的注册信息、等待在途请求处理完成等都可以在该线程里添加实现。

图3:注册一个JVM关闭的钩子示例

当然优雅退出需要有超时控制机制,如果到达超时时间仍然没有完成退出前的资源回收等操作,则由停机脚本直接调用KILL -9 PID或调用强制关闭进程的代码方式进行强制退出,不然可能会等待很长时间,影响我们正常的启停操作。

其它注意事项

1、在以下几种场景下,会直接停止JVM进程,JVM完全没有机会执行关闭钩子线程中的扫尾工作,无法实现优雅停机:

  • kill -9(SIGKILL信号);
  • 调用了java.lang.runtime.halt()方法;
  • 主机直接crash;
  • 主机直接关机;
  • 主机内存(或者容器内存)不够,触发操作系统OOM-KILLER。

2、hook线程会延迟JVM的关闭时间,所以尽可能减少执行时间,并做好超时控制。

效果

在代码优化后,通过测试环境的验证和固定场景的实际生产演练,容器在正常销毁、重启时,均未出现“Can not get connection to server”的报错,也解决了业务感知问题。解决了该问题后,在大规模微服务架构的场景下,容器的自动化自愈、扩缩容等功能又可以大展身手了。

总结

微服务的优雅停机没有最优的解决方案,只要抓住核心思想进行设计即可。如果使用的框架中有此类解决方法,建议直接使用,其适配性肯定是最高的。在微服务架构中,我们可以遵守以下建议规则来设计微服务的优雅停机机制:

  • 所有微服务应用都应该支持优雅停机;
  • 优先注销注册中心注册的服务实例;
  • 待停机的服务应用的接入点标记拒绝服务;
  • 上游服务支持故障转移因优雅停机而拒绝的服务;
  • 根据具体业务也提供适当的停机接口。

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
相关信息推荐
2020-09-08 17:35:01 关键词:微服务架构
摘要:微服务架构中的应用优雅停机主要是指应用实例有计划而平滑(即不产生需要处理的事故)的退出。应用服务器的停机主要分为两类:主动停机和被动停机,而其中主动停机和大部分的..