最近使用consul注册实例时发现一个问题,困扰了两天,终于解决记录下,同时也希望能对其他学习Spring Cloud有此类问题的“学友”有所帮助。
例如两台服务器同一个应用config,端口8080,注册到consul上,就会出现后注册覆盖前一个注册的情况,产生这个问题的原因是consul在注册时的instanceid采用的是“服务名+端口”的方式,默认情况下是"spring.application.name-server.port"。
那么如何解决这个问题呢,网上搜索了好多资料,其中有一篇“Spring Cloud Finchley版中Consul多实例注册的问题处理”这片文章帮助还是挺大的,下面结合这篇文章谈谈如何实现的。
By default a consul instance is registered with an ID that is equal to its Spring Application Context ID. By default, the Spring Application Context ID is ${spring.application.name}:comma,separated,profiles: ${server.port}. For most cases, this will allow multiple instances of one service to run on one machine. If further uniqueness is required, Using Spring Cloud you can override this by providing a unique identifier in spring.cloud.consul.discovery.instanceId. For example:
application.yml
spring:
cloud:
consul:
discovery:
instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
意思是默认情况下consul注册的实例ID采用的是Spring Context ID,也就是spring application name-server.port,若进一步确定唯一性可以使用唯一标识覆盖spring.cloud.consul.discovery.instanceId。
具体实现:
spring.cloud.consul.discovery.instance-id = ${spring.application.name}-${server.port}-${random.int[1,999]}
这样的设置,每次服务重启注册都不一样,查看consul会有很多旧的实例不可用,个人感觉这种方式不太好。
/**
* @author : Erick
* @version : 1.0
* @Description :
* @time :2018-11-26
*/
public class GatewayRegister extends ConsulServiceRegistry {
public GatewayRegister(ConsulClient client, ConsulDiscoveryProperties properties, TtlScheduler ttlScheduler, HeartbeatProperties heartbeatProperties) {
super(client, properties, ttlScheduler, heartbeatProperties);
}
@Override
public void register(ConsulRegistration reg) {
//重新设计id,此处用的是名字也可以用其他方式例如instanceid、host、uri等
reg.getService().setId(reg.getService().getName()+"-"+reg.getService().getAddress()+"-"+reg.getPort());
super.register(reg);
}
}
最主要的代码已经实现了,这也是在本文前边说的一篇文章的内容,如果写到这,重启服务去查看consul发现还是没有变化,仍然采用的是:spring.application.name-server.port进行注册的,那么如何将重写的方法真正的用起来呢,自己找的时候也是一头雾水毕竟是刚学习Spring Cloud 很多特性还不是很了解,带着疑问源码中ConsulServiceRegistry是如何进行使用的呢,全局搜索(包括jar)ConsulServiceRegistry,最后发现在ConsulServiceRegistryAutoConfiguration类中有这么一段代码:
@Bean
@ConditionalOnMissingBean
public ConsulServiceRegistry consulServiceRegistry(ConsulClient consulClient, ConsulDiscoveryProperties properties,
HeartbeatProperties heartbeatProperties) {
return new ConsulServiceRegistry(consulClient, properties, ttlScheduler, heartbeatProperties);
}
那么我自己能不能按照这个方式将重写的consulRegister用起来呢,于是在启动类中增加了类似的代码:
@Bean
public GatewayRegister consulServiceRegistry(ConsulClient consulClient, ConsulDiscoveryProperties properties,
HeartbeatProperties heartbeatProperties) {
return GatewayRegister(consulClient, properties, ttlScheduler, heartbeatProperties);
}
增加后发现ttlScheduler参数报错需要指定一个,接着看ConsulServiceRegistryAutoConfiguration中是如何使用的,发现只是注解引用,也加到启动类中,完整代码:
/**
* @Description :
* @Param
* @author : Erick
* @version : 1.0
* @Date : 2018-10-26
*/
@SpringBootApplication
public class SpringcloudGatewayApplication {
//参照源码定义声名
@Autowired(required = false)
private TtlScheduler ttlScheduler;
//重写register方法
@Bean
public GatewayRegister consulServiceRegistry(ConsulClient consulClient, ConsulDiscoveryProperties properties,
HeartbeatProperties heartbeatProperties) {
return new GatewayRegister(consulClient, properties, ttlScheduler, heartbeatProperties);
}
public static void main(String[] args) {
SpringApplication.run(SpringcloudGatewayApplication.class, args);
}
}
再次启动查看consul注册服务发现成功了变为“spring.application.name- ip地址-server.port”了,至此重写register方法解决多实例注册问题。