解决Spring Cloud Bus不刷新所有节点的问题及理解"Application Context ID must be unique"

如果同一微服务的多个实例使用的端口相同,当配置修改时,使用Spring Cloud Bus不会刷新全部实例的配置。此时需要配置各个实例的spring.application.index为不同的值。下面我们来分析一下原因。

在Spring Cloud Config上有这么一段:

Application Context ID must be unique

The bus tries to eliminate processing an event twice, once from the original ApplicationEvent and once from the queue. To do this, it checks the sending application context id againts the current application context id. If multiple instances of a service have the same application context id, events will not be processed. Running on a local machine, each service will be on a different port and that will be part of the application context id. Cloud Foundry supplies an index to differentiate. To ensure that the application context id is the unique, setspring.application.index to something unique for each instance of a service. For example, in lattice, setspring.application.index=${INSTANCE_INDEX} in application.properties (or bootstrap.properties if using configserver).

这段话的意思,大致上是说如果相同微服务的多个实例,使用的是相同的端口时,需要配置spring.application.index 属性,本文来分析一下为什么。

(1) 我们知道定位Spring Boot的问题,往往可以从配置开始。按照这个思路,先找到spring.application.index 所在的类ContextIdApplicationContextInitializer。至于怎么找到的,可以看这里:http://docs.spring.io/spring-boot/docs/1.4.2.RELEASE/reference/htmlsingle/#common-application-properties ,搜索spring.application.index即可。

(2) 在org.springframework.boot.context.ContextIdApplicationContextInitializer 类的getApplicationId() 方法中,有类似以下的内容:

 
     
private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.resolvePlaceholders( this.name);
String index = environment.resolvePlaceholders(INDEX_PATTERN);
String profiles = StringUtils
.arrayToCommaDelimitedString( environment .getActiveProfiles());
if (StringUtils.hasText(profiles)) {
name = name + ":" + profiles;
}
if (! "null". equals(index)) {
name = name + ":" + index;
}
return name;
}

其中,name的表达式如下:

${spring.application.name:${vcap.application.name:${spring.config.name:application}}},也就是配置的spring.application.name (以主流方式为例,当然也可能是spring.config.name)。 

而index的表达式是:

${vcap.application.instance_index:${spring.application.index:${server.port:${PORT:null}}}}

也就是如果什么都不配置,就取server.port。

综上,如果什么都不配置,那么getApplicationId返回的是${spring.application.name}:${server.port}

(3) 在Spring Cloud Bus中的org.springframework.cloud.bus.ServiceMatcher 有以下代码:

 
     
public boolean isFromSelf(RemoteApplicationEvent event) {
String originService = event.getOriginService();
String serviceId = getServiceId();
return this.matcher. match(originService, serviceId);
}
public boolean isForSelf(RemoteApplicationEvent event) {
String destinationService = event.getDestinationService();
return ( destinationService == null || destinationService.trim().isEmpty() || this.matcher
. match(destinationService, getServiceId()));
}
public String getServiceId() {
return this.context.getId();
}

从代码可知,如果什么都不设置,并且相同微服务的多个实例使用的是相同的端口的话,那么isFromSelf将会返回true。

(4) 在org.springframework.cloud.bus.BusAutoConfiguration.acceptRemote(RemoteApplicationEvent)中的代码:

 
     
@StreamListener( SpringCloudBusClient. INPUT)
public void acceptRemote(RemoteApplicationEvent event) {
if (event instanceof AckRemoteApplicationEvent) {
if ( this .bus .getTrace().isEnabled() && ! this .serviceMatcher .isFromSelf(event)
&& this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent( event);
}
// If it's an ACK we are finished processing at this point
return;
}
if ( this .serviceMatcher .isForSelf(event)
&& this.applicationEventPublisher != null) {
if (! this .serviceMatcher .isFromSelf(event)) {
this.applicationEventPublisher.publishEvent( event);
}
if ( this .bus .getAck().isEnabled()) {
AckRemoteApplicationEvent ack = new AckRemoteApplicationEvent( this,
this .serviceMatcher .getServiceId(),
this .bus .getAck() .getDestinationService(),
event.getDestinationService(), event.getId(), event.getClass());
this.cloudBusOutboundChannel
.send( MessageBuilder .withPayload( ack) .build());
this .applicationEventPublisher .publishEvent( ack);
}
}
if ( this .bus .getTrace().isEnabled() && this .applicationEventPublisher != null) {
// We are set to register sent events so publish it for local consumption,
// irrespective of the origin
this.applicationEventPublisher.publishEvent( new SentApplicationEvent( this,
event.getOriginService(), event.getDestinationService(),
event.getId(), event.getClass()));
}
}

看到这段代码,原因已经一目了然了。

Github上的相关issue:https://github.com/spring-cloud/spring-cloud-bus/issues/18 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值