Consul注销实例时候的问题

当我们在Spring Cloud应用中使用Consul来实现服务治理时,由于Consul不会自动将不可用的服务实例注销掉(deregister),这使得在实际使用过程中,可能因为一些操作失误、环境变更等原因让Consul中存在一些无效实例信息,而这些实例在Consul中会长期存在,并处于断开状态。它们虽然不会影响到正常的服务消费过程,但是它们会干扰我们的监控,所以我们可以实现一个清理接口,在确认故障实例可以清理的时候进行调用来将这些无效信息清理掉。

开始以为只要简单的调用注销接口就能轻松完成,但是实际实践的发现并非如此。因此,分享一下整个实现过程以及中间遇到的一些坑。

借鉴Spring Cloud Consul

在实现之初,先参考了Spring Cloud Consul在关闭程序时候实现的注销方法,具体如下:

public class ConsulLifecycle extends AbstractDiscoveryLifecycle {
    ...

    private void deregister(String serviceId) {
        if (!this.properties.isRegister()) {
            return;
        }
        if (ttlScheduler != null) {
            ttlScheduler.remove(serviceId);
        }
        log.info("Deregistering service with consul: {}", serviceId);
        client.agentServiceDeregister(serviceId);
    }

    ...
}

我们可以看到,当应用关闭时候的注销操作是通过调用client.agentServiceDeregister(serviceId)来实现的。其中client是consul-api的com.ecwid.consul.v1.ConsulClient实例。而agentServiceDeregister方法则是对/v1/agent/service/deregister/<serviceID> 接口的实现,该接口主要用来从Consul Agent中根据serviceId来注销实例。

以此实现为范例,于是开始的思路是这样的:

  • 先通过consulClient.getHealthServices(serviceId, false, null)根据serviceId来获取服务实例清单
  • 遍历实例清单中有不是PASSING状态的实例,就调用client.agentServiceDeregister(serviceId)来剔除

具体实现如下:

@RestController
public class ApiController {

    @Autowired
    private ConsulClient consulClient;

    @RequestMapping(value = "/unregister/{id}", method = RequestMethod.POST)
    public String unregisterServiceAll(@PathVariable String id) {
        List<HealthService> response = consulClient.getHealthServices(id, false, null).getValue();
        for(HealthService service : response) {
            service.getChecks().forEach(check -> {
                if(!check.getStatus().name().equals(Check.CheckStatus.PASSING.name())) {
                    logger.info("unregister : {}", check.getServiceId());
                    consulClient.agentServiceDeregister(check.getServiceId());
                }
            });
        }
        return null;
    }
}

但是,在测试后发现该方法只能剔除同一个agent上的非PASSING实例。

Catalog误区

继续搜索了一下Consul的文档,发现了这个接口:/v1/catalog/deregister : Deregisters a node, service, or check。于是,尝试了用该接口来替换之前的consulClient.agentServiceDeregister(check.getServiceId());实现。

CatalogDeregistration catalogDeregistration = new CatalogDeregistration();
catalogDeregistration.setDatacenter("dc1");
catalogDeregistration.setNode(check.getNode());
catalogDeregistration.setServiceId(check.getServiceId());
catalogDeregistration.setCheckId(check.getCheckId());
consulClient.catalogDeregister(catalogDeregistration);

经过测试,该方法可以实现短暂的剔除,但是过一段时间之后这些被剔除的实例又都恢复回来了……也就是说这个接口完全没有什么卵用!

那么为什么会出现这种情况呢?我们可以在Github中找到这个维持了一年多的问题讨论:https://github.com/hashicorp/consul/issues/1188 。整个讨论过程非常曲折,虽然当前该问题还依然在open状态,但是一些回复也基本够我们去理解它的原因了。比如下面这条评论:

You cannot deregister a service from the agent on a different node, service only exists on the agent you have registered with. It also exists in the catalog on all nodes, but that is not related to the agent itself. And to be honest I don't understand why there is a catalog/deregister endpoint at all, in my opinion catalog should be a read-only service list.

从该评论中,我们可以知道一个重要信息:服务实例只能在注册的Agent上进行注销!另外,对于/v1/catalog/deregister接口,目前还是有不少争议的,因为根本没啥用。

最终实现

既然服务实例只能在注册的Agent上进行注销,那么我们的实现完全可以按照该思路来实现,方法很简单,只需要对一开始实现的内容做一些调整,依然使用client.agentServiceDeregister(serviceId)方法,只是我们需要调整client连接的agent必须是serviceId注册的agent。所以,最终的修改结果如下:

List<HealthService> response = consulClient.getHealthServices(id, false, null).getValue();
for(HealthService service : response) {
    // 创建一个用来剔除无效实例的ConsulClient,连接到无效实例注册的agent
    ConsulClient clearClient = new ConsulClient(service.getNode().getAddress(), 8500);
    service.getChecks().forEach(check -> {
        if(check.getStatus() != Check.CheckStatus.PASSING) {
            logger.info("unregister : {}", check.getServiceId());
            clearClient.agentServiceDeregister(check.getServiceId());
        }
    });
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,678评论 19 139
  • [转] 1. 什么是consul? 是一个服务管理软件。支持多数据中心下,分布式高可用的,服务发现和配置共享。co...
    baboon阅读 3,134评论 0 7
  • 在互联网应用领域,服务的动态性需求十分常见,这就对服务的自动发现和可动态扩展提出了很高的要求。 微服务系统动辄上万...
    Liberalman阅读 8,304评论 23 80
  • consul是golang开发的一个高可用的分布式服务注册系统,有service discovery和key/va...
    auguszou阅读 2,415评论 0 6
  • 时光如梭,过的飞快,3.0转眼间过了60天,而这60天带来的变化是巨大的。 读书确实是件有福气的事情,以前不觉得,...
    易效能张倩阅读 263评论 9 11

友情链接更多精彩内容