Consul注销实例时候的问题

标签: Spring Cloud Spring Boot Spring Cloud Consul | 发表时间:2016-12-29 15:48 | 作者:
出处:http://blog.didispace.com/

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

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

借鉴Spring Cloud Consul

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

     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     
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)来剔除

具体实现如下:

     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     
@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());实现。

     
1
2
3
4
5
6
     
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。所以,最终的修改结果如下:

     
1
2
3
4
5
6
7
8
9
10
11
     
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());
}
});
}

相关 [consul 实例 问题] 推荐:

Consul注销实例时候的问题

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

[转]consul VS zookeeper、etcd、doozerd

- - Xiao_Qiang_的专栏
  zookeeper、doozerd、etcd都有着相似的架构,这三者的服务节点都需要一个仲裁节点来操作,它们是强一致的,并提供各种操作原语. 应用程序可以通过客户端lib库来构建分布式的系统. 在一个单datacenter中,consul的server节点工作在一种简单的方式下,consul server需要一个仲裁操作,并提供强一致性.

服务发现:Zookeeper vs etcd vs Consul

- - 企业架构 - ITeye博客
服务发现:Zookeeper vs etcd vs Consul. 【编者的话】本文对比了Zookeeper、etcd和Consul三种服务发现工具,探讨了最佳的服务发现解决方案,仅供参考. 如果使用预定义的端口,服务越多,发生冲突的可能性越大,毕竟,不可能有两个服务监听同一个端口. 管理一个拥挤的比方说被几百个服务所使用的所有端口的列表,本身就是一个挑战,添加到该列表后,这些服务需要的数据库和数量会日益增多.

基于 Consul 的 Docker Swarm 服务发现

- - IT瘾-dev
基于 Consul 的 Docker Swarm 服务发现. 2017 年 1 月 10 日发布. Docker 是一种新型的虚拟化技术,它的目标在于实现轻量级操作系统的虚拟化. 相比传统的虚拟化方案,Docker. 虚拟化技术有一些很明显的优势:启动容器的速度明显快于传统虚拟化技术,同时创建一台虚拟机占用的资源也要远远小于传统的虚拟技术.

基于consul的Redis高可用方案

- -
这几天在研究如何做Redis的高可用容灾方案,查询了资料和咨询DBA同行,了解到Redis可以基于consul和sentinel实现读写分离以及HA高可用方案. 本文讲述基于consul的Redis高可用方案实践. 感谢邓亚运的提示和资料协助. Consul是HashiCorp公司基于go语言研发用于服务发现和配置共享开的分布式高可用的系统.

基于Consul的数据库高可用架构 - yayun - 博客园

- -
      几个月没有更新博客了,已经长草了,特意来除草. 本次主要分享如何利用consul来实现redis以及mysql的高可用. 以前的公司mysql是单机单实例,高可用MHA加vip就能搞定,新公司mysql是单机多实例,那么显然这个方案不适用,后来也实现了故障切换调用dns api来修改域名记录,但是还是没有利用consul来实现高可用方便,后面会说明优势.

使用Consul做服务发现的若干姿势

- - 程序猿DD
从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后来逐步应用于生产环境,并总结了少许使用经验. 最开始使用Consul的人不多,为了方便交流创建了一个QQ群(群号在最后),这两年微服务越来越火,使用Consul的人也越来越多,目前群里已有400多人,经常有人问一些问题,比如:. 服务注册到节点后,其他节点为什么没有同步.

注册中心 Consul 使用详解 - 纯洁的微笑博客

- -
在上个月我们知道 Eureka 2.X 遇到困难停止开发了,但其实对国内的用户影响甚小,一方面国内大都使用的是 Eureka 1.X 系列,另一方面 Spring Cloud 支持很多服务发现的软件,Eureka 只是其中之一,下面是 Spring Cloud 支持的服务发现软件以及特性对比:. (弱)长连接,keepalive.

基于Nginx和Consul构建高可用及自动发现的Docker服务架构 - DockOne.io

- -
如果你在大量接触或使用微服务的话,你可能会碰到一个问题:当你创建的服务数量越来越多时,这些服务之间的通信便越难管理,而且维护代价会越来越高. 针对这个问题,Consul给出了一份完美的答卷. Consul是一套开源的分布式服务发现和配置管理系统,支持多数据中心分布式高可用. Consul是HashiCorp(Vagrant的创建者)开发的一个服务发现与配置项目,用Go语言开发,基于 Mozilla Public License 2.0 的协议开源.

使用JProfiler解决Java应用程序内存溢出问题实例

- - Java - 编程语言 - ITeye博客
    前段时间基于OpenJms部署了一个消息中间件服务器,通过主题订阅模式在各个消息节点之间传递信息,但是某个类型的消息节点长时间运行后出现了内存溢出问题,最后使用JProfiler的基本线程监测功能找到问题所在,并且进行解决. Java 版本 java version "1.7.0_40". JProfiler 版本 v8.0.7.