spring cloud bus Consumer Refresh过程

spring cloud bus Consumer Refresh 过程

还是从 Bus Server 的 /actuator/busrefresh/{serviceName}:** 端点发出的刷新事件,由 BusConsumer 消费

Consumer 刷新 时序图:

在这里插入图片描述

可以看出, 消费者在接收到刷新事件后,做了两件事件:

  • BusConsumer 为入口,接收到消息后 publish RemoteApplicationEvent 事件

  • RemoteApplicationEventListener 处理

  • RefreshListener 处理刷新事件,刷新上下文容器,重新绑定Properties

RemoteApplicationEventListener

public class RemoteApplicationEventListener implements ApplicationListener<RemoteApplicationEvent> {

    private final Log log = LogFactory.getLog(getClass());

    private final ServiceMatcher serviceMatcher;

    private final BusBridge busBridge;

    public RemoteApplicationEventListener(ServiceMatcher serviceMatcher, BusBridge busBridge) {
        this.serviceMatcher = serviceMatcher;
        this.busBridge = busBridge;
    }

    @Override
    public void onApplicationEvent(RemoteApplicationEvent event) {
        if (this.serviceMatcher.isFromSelf(event) && !(event instanceof AckRemoteApplicationEvent)) {
            if (log.isDebugEnabled()) {
                log.debug("Sending remote event on bus: " + event);
            }
            // TODO: configurable mimetype?
            this.busBridge.send(event);
        }
    }

}

//PathServiceMatcher.java
public boolean isFromSelf(RemoteApplicationEvent event) {
    String originService = event.getOriginService();
    String serviceId = getBusId();
    return this.matcher.match(originService, serviceId);
}


**很明显,serviceMatcher.isFromSelf(event) 为false , originService 为 cloud-config-server (配置中心), serviceId 为 cloud-client (自定义的服务), 所以 RemoteApplicationEventListener 相当于没有没有处理任何操作 **

RefreshListener

public class RefreshListener implements ApplicationListener<RefreshRemoteApplicationEvent> {

    private static Log log = LogFactory.getLog(RefreshListener.class);

    private ContextRefresher contextRefresher;

    private ServiceMatcher serviceMatcher;

    public RefreshListener(ContextRefresher contextRefresher, ServiceMatcher serviceMatcher) {
        this.contextRefresher = contextRefresher;
        this.serviceMatcher = serviceMatcher;
    }

    @Override
    public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
        log.info("Received remote refresh request.");
        if (serviceMatcher.isForSelf(event)) {
            Set<String> keys = this.contextRefresher.refresh();
            log.info("Keys refreshed " + keys);
        }
        else {
            log.info("Refresh not performed, the event was targeting " + event.getDestinationService());
        }
    }

}
  • 判断出是给自己处理的事件
  • 交给 ContextRefresher 的 refresh 方法处理
public abstract class ContextRefresher {

    protected final Log logger = LogFactory.getLog(getClass());

    protected static final String REFRESH_ARGS_PROPERTY_SOURCE = "refreshArgs";

    protected static final String[] DEFAULT_PROPERTY_SOURCES = new String[] {
        // order matters, if cli args aren't first, things get messy
        CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME, "defaultProperties" };

    protected Set<String> standardSources = new HashSet<>(
        Arrays.asList(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
                      StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
                      StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME,
                      StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
                      StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, "configurationProperties"));

    //omit...
    public synchronized Set<String> refresh() {
        Set<String> keys = refreshEnvironment();
        this.scope.refreshAll();
        return keys;
    }

    public synchronized Set<String> refreshEnvironment() {
        Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
        updateEnvironment();
        Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
        this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
        return keys;
    }

    protected abstract void updateEnvironment()

}

  • refreshEnvironment 更新上下文
    • 调用 refreshEnvironment
      • 从上下文中复制一份老的配置内容
      • updateEnvironment 从 远程拉取最新的配置内容
      • 新老配置项对比出差异 key
      • 发布 EnvironmentChangeEvent 来重新绑定 Properties
  • RefreshScope 调用 refreshAll 刷新 spring 容器中 Scope 为 refreshScope 的Bean(简单的来讲就是删除,在下次 getBean 的时候放入到 RefreshScope 中的Bean 缓存中)
从上下文中复制一份老的配置内容

这个没有什么好说的

updateEnvironment 从 远程拉取最新的配置内容

通过 ConfigDataLoader 接口的 load 方法从远程配置中心拉取最新配置内容, 实现类是 ConfigServerConfigDataLoader:

// 部分代码
public class ConfigServerConfigDataLoader implements ConfigDataLoader<ConfigServerConfigDataResource>, Ordered {

	@Override
	public int getOrder() {
		return -1;
	}

	@Override
	// TODO: implement retry LoaderInterceptor
	public ConfigData load(ConfigDataLoaderContext context, ConfigServerConfigDataResource resource) {
		if (context.getBootstrapContext().isRegistered(ConfigServerInstanceMonitor.class)) {
			// force initialization if needed
			context.getBootstrapContext().get(ConfigServerInstanceMonitor.class);
		}
		if (context.getBootstrapContext().isRegistered(LoaderInterceptor.class)) {
			LoaderInterceptor interceptor = context.getBootstrapContext().get(LoaderInterceptor.class);
			Binder binder = context.getBootstrapContext().get(Binder.class);
			return interceptor.apply(new LoadContext(context, resource, binder, this::doLoad));
		}
		return doLoad(context, resource);
	}

	public ConfigData doLoad(ConfigDataLoaderContext context, ConfigServerConfigDataResource resource) {
		ConfigClientProperties properties = resource.getProperties();
		List<PropertySource<?>> composite = new ArrayList<>();
		Exception error = null;
		String errorBody = null;
		try {
			String[] labels = new String[] { "" };
			if (StringUtils.hasText(properties.getLabel())) {
				labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel());
			}
			String state = ConfigClientStateHolder.getState();
			// Try all the labels until one works
			for (String label : labels) {
				Environment result = getRemoteEnvironment(context, resource, label.trim(), state);
				//omit...
			}
			errorBody = String.format("None of labels %s found", Arrays.toString(labels));
		}
		catch (HttpServerErrorException e) {
		//omt...
		}
		logger.warn("Could not locate PropertySource: " + (error != null ? error.getMessage() : errorBody));
		return null;
	}


	protected Environment getRemoteEnvironment(ConfigDataLoaderContext context, ConfigServerConfigDataResource resource,
			String label, String state) {
		ConfigClientProperties properties = resource.getProperties();
		RestTemplate restTemplate = context.getBootstrapContext().get(RestTemplate.class);

		String path = "/{name}/{profile}";
		String name = properties.getName();
		String profile = resource.getProfiles();
		String token = properties.getToken();
		int noOfUrls = properties.getUri().length;
		if (noOfUrls > 1) {
			logger.info("Multiple Config Server Urls found listed.");
		}

		Object[] args = new String[] { name, profile };
		if (StringUtils.hasText(label)) {
			// workaround for Spring MVC matching / in paths
			label = Environment.denormalize(label);
			args = new String[] { name, profile, label };
			path = path + "/{label}";
		}
		ResponseEntity<Environment> response = null;
		List<MediaType> acceptHeader = Collections.singletonList(MediaType.parseMediaType(properties.getMediaType()));

		for (int i = 0; i < noOfUrls; i++) {
			ConfigClientProperties.Credentials credentials = properties.getCredentials(i);
			String uri = credentials.getUri();
			String username = credentials.getUsername();
			String password = credentials.getPassword();

			logger.info("Fetching config from server at : " + uri);

			try {
				HttpHeaders headers = new HttpHeaders();
				headers.setAccept(acceptHeader);
				addAuthorizationToken(properties, headers, username, password);
				if (StringUtils.hasText(token)) {
					headers.add(TOKEN_HEADER, token);
				}
				if (StringUtils.hasText(state) && properties.isSendState()) {
					headers.add(STATE_HEADER, state);
				}

				final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);
				response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args);
			}
			catch (HttpClientErrorException e) {
			
			}

			if (response == null || response.getStatusCode() != HttpStatus.OK) {
				return null;
			}

			Environment result = response.getBody();
			return result;
		}

		return null;
	}

	

}

新老配置项对比出差异 key

没什么好说的

发布 EnvironmentChangeEvent 来重新绑定 Properties

当发布 EnvironmentChangeEvent事后, ConfigurationPropertiesRebinder 会处理,这个类主要是处理 使用了 ConfigurationProperties 注解的Bean,进行属性的重新绑定

public class ConfigurationPropertiesRebinder
    implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {

    private ConfigurationPropertiesBeans beans;

    private ApplicationContext applicationContext;

    private Map<String, Exception> errors = new ConcurrentHashMap<>();

    public ConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
        this.beans = beans;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
	 * A map of bean name to errors when instantiating the bean.
	 * @return The errors accumulated since the latest destroy.
	 */
    public Map<String, Exception> getErrors() {
        return this.errors;
    }

    @ManagedOperation
    public void rebind() {
        this.errors.clear();
        for (String name : this.beans.getBeanNames()) {
            rebind(name);
        }
    }

    @ManagedOperation
    public boolean rebind(String name) {
        if (!this.beans.getBeanNames().contains(name)) {
            return false;
        }
        if (this.applicationContext != null) {
            try {
                Object bean = this.applicationContext.getBean(name);
                if (AopUtils.isAopProxy(bean)) {
                    bean = ProxyUtils.getTargetObject(bean);
                }
                if (bean != null) {
                    // TODO: determine a more general approach to fix this.
                    // see https://github.com/spring-cloud/spring-cloud-commons/issues/571
                    if (getNeverRefreshable().contains(bean.getClass().getName())) {
                        return false; // ignore
                    }
                    this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
                    this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
                    return true;
                }
            }
            catch (RuntimeException e) {
                this.errors.put(name, e);
                throw e;
            }
            catch (Exception e) {
                this.errors.put(name, e);
                throw new IllegalStateException("Cannot rebind to " + name, e);
            }
        }
        return false;
    }

    @ManagedAttribute
    public Set<String> getNeverRefreshable() {
        String neverRefresh = this.applicationContext.getEnvironment()
            .getProperty("spring.cloud.refresh.never-refreshable", "com.zaxxer.hikari.HikariDataSource");
        return StringUtils.commaDelimitedListToSet(neverRefresh);
    }

    @ManagedAttribute
    public Set<String> getBeanNames() {
        return new HashSet<>(this.beans.getBeanNames());
    }

    @Override
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        if (this.applicationContext.equals(event.getSource())
            // Backwards compatible
            || event.getKeys().equals(event.getSource())) {
            rebind();
        }
    }

}

处理过程就是:

  • 先通过 getBean 获取 Bean 实例
  • 然后调用 bean 的初始化方法 initializeBean
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值