关于Springboot微服务使用Apollo配置管理中心的热刷新问题

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

      近日,公司项目中使用携程网的Apollo配置管理中心进行统一配置管理,为了方便环境部署和运维,能避免很多配置问题导致的环境部署错误;很多网友估计都用过Apollo;

在我们项目组使用前做了一些预研,发现还需要解决连接池的热刷新问题,否则意味着Apollo的portal界面上修改配置后还得重启服务才能生效;

可能很多人会说,Apollo配置管理中心本身就支持配置的热刷新,但是,这只适用于普通应用场景(如一些不需要复杂的初始化操作的组件和spring-boot默认支持的组件);

对于Druid连接池、Jedis连接池、Lettuce连接池等等之类的组件,实现配置的热刷新仍然需要自己做一些代码适配;

网上有热心网友也给出了一些对通用配置的热刷新代码实现,这种方式对spring-boot默认集成的第三方组件是有效的,比如spring-boot 2.x的默认数据库连接池 Hikari(其实查看源代码就能发现,spring-boot为它监听了EnvironmentChangeEvent事件并实现了热刷新逻辑);

那么,对基于Apollo配置管理中心的Druid连接池、Jedis连接池、Lettuce连接池等等之类的客户端组件热刷新问题如何解决呢?

本文暂时只给出思路,详细代码后续会放出来;实现方法步骤如下:

1. 编写配置监听器,并将Apollo客户端的ConfigChangeEvent事件封装为EnvironmentChangeEvent事件,在Spring IOC容器中广播出去;

@Component
public class ApolloConfigListener implements ApplicationContextAware {
	private static final Logger LOGGER = LoggerFactory.getLogger(ApolloConfigListener.class);
	@Autowired
	private ApplicationContext applicationContext = null;
	@Autowired
	private RefreshScope refreshScope = null;
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
	@ApolloConfigChangeListener
	public void onChange(ConfigChangeEvent changeEvent) {
		this.refreshProperties(changeEvent);
	}
	public void refreshProperties(ConfigChangeEvent changeEvent) {
		this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
		this.refreshScope.refreshAll();
	}
}

2. 使用 @ApolloConfig  或者API方式 ConfigService.getConfig(namespace)等获取最新配置

3. 编写Spring事件监听器,实现如下接口ApplicationListener,ApplicationContextAware, BeanDefinitionRegistryPostProcessor等;

需要记录 BeanDefinitionRegistry  和  ApplicationContext;

在监听到 EnvironmentChangeEvent事件中,进行如下逻辑处理:

判断配置变化的key是否跟对应的连接池组件相关

如果不相关,则忽略;否则,刷新IOC容器,即通过记录的 BeanDefinitionRegistry 引用删除连接池对应的 BeanDefinition,并重新根据Apollo客户端接收到的最新配置重建IOC容器中跟连接池相关的所有bean;然后,刷新DI,即扫描类路径下引用的连接池相关Bean的类,刷新其依赖关系;

public abstract class AbstractRefreshListener implements ApplicationListener,
		ApplicationContextAware, BeanDefinitionRegistryPostProcessor {
	private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRefreshListener.class);
	protected ApplicationContext applicationContext = null;
	protected BeanDefinitionRegistry registry = null;

	public static interface DIFilter {
		public boolean accept(Class clazz);
	}
	@Override
	public final void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
	@Override
	public final void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		this.registry = registry;
	}
	@Override
	public final void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
		LOGGER.info("----------------------------------------------------");
		LOGGER.info("AbstractRefreshListener.postProcessBeanFactory() register beans:");
		for (String beanDefinitionName : beanDefinitionNames) {
			LOGGER.info("beanDefinitionName=" + beanDefinitionName);
		}
		LOGGER.info(
				"AbstractRefreshListener.postProcessBeanFactory() register bean count=" + beanDefinitionNames.length);
		LOGGER.info("----------------------------------------------------");
	}
	@Override
	public void onApplicationEvent(EnvironmentChangeEvent changeEvent) {
		if (this.needRefresh(changeEvent)) {
			this.refreshIOC(changeEvent);
		}
	}
	protected boolean needRefresh(EnvironmentChangeEvent changeEvent) {
		boolean flag = (changeEvent != null && this.registry != null);
		return flag;
	}
	protected abstract void refreshIOC(EnvironmentChangeEvent changeEvent);
	protected void refreshDI(List> classList, DIFilter filter) {
		classList = Optional.ofNullable(classList).orElse(new ArrayList>());
		for (Class item : classList) {
			try {
				if (filter != null && filter.accept(item)) {
					this.applicationContext.getBean(item);
				}
			} catch (Throwable th) {
				LOGGER.error("AbstractRefreshListener.refreshDI() Throwable", th);
			}
		}
	}
}

这里的代码只是给出大概思路,具体实现大家可以自己去编写;

当然,虽然这种做法可以解决热刷新问题,也存在着一些问题,比如刷新过程中会导致服务中断;

但通常情况下,连接池的地址变更通常只发生在部署过程前后,在环境部署完毕之后应该不会有这种场景,还是有一些实用价值的;

如果大家有更好的方案,不防评论里指点一下,谢谢大家......

转载于:https://my.oschina.net/morpheusWB/blog/3048420

你可能感兴趣的:(关于Springboot微服务使用Apollo配置管理中心的热刷新问题)