0
点赞
收藏
分享

微信扫一扫

SpringCloud微服务项目-无感知动态调整服务实例数量

承蒙不弃 2021-09-25 阅读 49
spring cloud

随着时间的流逝,java技术也在一次次的革新,现在大家基本都已经在使用微服务开发了,spring-cloud就是java技术栈下最流行的微服务解决方案,在微服务领域,对服务数量的大小调整已成为一个非常常规的一个操作,那么在某个服务扩容和削减服务数量的时候,怎么能不影响线上的服务使用呢

一、关于扩容

对于扩容其实比较简单,启动新的服务实例,注册到eurake上即可,其他调用服务的client端(包括zuul)每隔一段时间会从eureka server上拉取新的服务列表,当启动完成的服务实例注册完成,各个client拉取到新的服务列表,便可以正常访问新增的服务实例了,全程无感知。

二、关于下线削减服务数量

下线微服务实例,使用kill -9直接强制杀掉进程,肯定是不行的,因为各个客户端都还在调用该实例时,突然被杀掉进程,肯定会报错(这里我们先不考虑,重试机制,这是另外的一个问题,粗暴的重试也会导致所有接口都做幂等性处理),如果调用springboot提供的优雅关闭服务实例的方法,可能会导致关闭失败,进程无法关闭,而且关闭后,因为各个客户端会缓存eureka上的服务列表(如果都使用默认配置,缓存最长还会持续90秒的时间),这样客户端调用已经下线的服务还是会报错,那么下面进入本文主题,介绍一种可以,完全不报错,无感知的下线服务实例方式:

eureka提供了一个方法eurekaClient.shutdown(),调用后会将该服务实例从eureka上摘除,不会再进行注册,但是服务实例并不会关闭,我们从eureka摘除服务以后,先不要杀掉服务进程,这个时候因为各个客户端的缓存的服务列表没有更新,还会继续调用该服务,所以就要等待,等90秒后所有的客户端都从eureka上拉去到最新的服务列表,不再调用该服务时,将进程杀掉,这样客户端就不会感知到该服务的下线。这种方式就是会耗费一些等待缓存过期的时间,这个我们可以通过修改缓存时长,来缩短这个时间,当然也可以同时下线多个服务

三、代码实例

/**
 * @author anjl
 * 2018-7-12
 */
@SuppressWarnings("unused,unchecked")
@Api(tags = "eureka操作接口")
@ApiResponses(value = {@ApiResponse(code = 400, response = GlobalExceptionHandler.class, message = "数据校验失败"), @ApiResponse(code = 500, response = GlobalExceptionHandler.class, message = "内部错误")})
@Slf4j
@RestController
@RequestMapping("/eureka")
public class EurekaClientController {

    @ApiOperation(value = "注销eureka client", notes = "注销eureka client", position = 10)
    @GetMapping("/shutdown")
    public BusinessResult shutdown() throws Exception {
        InstanceInfo info = eurekaClient.getApplicationInfoManager().getInfo();
        log.info("服务下线:instanceId = {}", info.getInstanceId());
        singleThreadPool.execute(this::shutdownClient);
        return BusinessResult.createSuccessInstance(null);
    }

    @ApiOperation(value = "用于检测中间是否有停机", position = 10)
    @GetMapping("/test")
    public BusinessResult test() throws Exception {
        return BusinessResult.createSuccessInstance(null);
    }

    private void shutdownClient() {
        eurekaClient.shutdown();
        log.info("服务下线成功!!!");
    }

    @Autowired
    private EurekaClient eurekaClient;

    private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("app-eureka-shutdown-pool-%d").build();
    private ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 10L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

}

四、后续的一些说明

(1)从eureka server上也可以下线服务,但是因为微服务实例的心跳并没有停止,所以还是会重新注册到eureka server上
(2)下线服务实例,采用异步处理主要是为了,快速返回结果,在服务实例访问量比较大的情况下,下线服务处理时间较长,所以采用异步处理的方式,先返回结果,同时异步去处理下线操作。
(3)如果我们项目资源比较紧张,暂时只有一个服务实例,也可以通过这种方式实现不停机升级服务,我们只需要在额外的端口启动一个备份服务,然后通过上述方式将旧的服务下线,然后更新代码后启动,再用上述方式下线备份服务即可(这样的方案需要机器提供可以启动一个备份服务的内存)。在启动备份服务阶段还可以检查一些低级错误,比如配置文件错误或者编译错误导致备份服务无法启动成功,就可以不再向下执行。
(4)此方案好处:在联调阶段,不影响前端使用的情况下,升级服务。
在测试阶段不影响测试的情况下,修复bug。
在线上环境不影响使用的情况下,服务升级。
总之大大提高了服务的可用性。
(ps:以前小编公司,测试或者联调阶段,每次要更新代码,都要在群里大喊”服务重启“,重启完,再大喊”完成“,喊的次数少了,更新不及时,喊的次数多了,又怕影响其他人工作,着实令小编为难!!
从此小编再也不用担心重启次数多的问题了,想怎么更新就怎么更新!!)

举报

相关推荐

0 条评论