qa环境连接到了dev
背景:同事说他本地调试一个6年前的服务时配置了qa环境变量,但是dubbo接口调到了dev上。发布到qa环境或者线上,调用关系是没问题的,关联框架Spring、Apollo、Dubbo、Zookeeper而且这些框架多少都是经过公司大神改造封装过的。
思路
dubbo的执行过程,provider启动注册到zk,consumer启动注册到zk、获取provider列表并订阅通知,dubbo注册中心地址是通过Apollo配置的。
所以可能出问题的点在于:
- dubbo注册中心地址在Apollo上配置错了;
- 系统连接Apollo注册中心地址错了。
排查
dubbo注册中心地址在Apollo上配置错了,这点确认的方式比较简单。直接去注册中心上看下,对应的配置是否正确即可。
系统连接Apollo注册中心地址错了,看下Apollo配置加载的流程:
容器加载入口:
这里有个知识点是Spring的bean容器,Spring的核心价值是对Bean的管理。1
ApplicationContext app = new ClassPathXmlApplicationContext("classpath*:META-INF/spring/applicationContext.xml");
Apollo配置入口
这里有个知识点是xml描述规则xsd和对应的解析以及我们书写时的配置提示,Apollo的配置规则在根路径下的xsd文件中,对应的解析com.ctrip.framework.apollo.spring.config.NamespaceHandler负责,配置入口在spring.handlers文件中,解析描述依赖信息在spring.schemas文件中。这些内容在源码的根路径下都可以找到,这部分数据xml和spring的知识点。1
<apollo:config />
spring.factories配置入口
这里有个知识点是springboot的,如果有自己实现过springboot的start插件,这部分内容是不陌生的。1
2
3
4
5
6org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration
org.springframework.context.ApplicationContextInitializer=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer初始化入口
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer1
2
3
4
5
6
7
8
9
10
11
12
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
if (!environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false)) {
logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
return;
}
logger.debug("Apollo bootstrap config is enabled for context {}", context);
initialize(environment);
}属性赋值
这里有个知识点是属于spring的Bean生命周期,从实例化、初始化、赋值、使用到回收,中间创建的过程有个循环依赖三级缓存的场景也是多数面试官会问及的问题,看过多次也忘过多次。
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer,ApolloApplicationContextInitializer这个类不仅实现了接口ApplicationContextInitializer,而且实现了EnvironmentPostProcessor,EnvironmentPostProcessor作用是在springboot项目启动之前自定义环境变量,从非标准springboot配置文件中获取配置并填充到springboot上下文。
postProcessEnvironment,这个接口一般用来读取配置信息,包括系统环境变量配置、启动时命令参数配置等,可以根据配置选择使用对应环境的配置(多套环境配置文件),可以根据环境变量或启动参数中的配置,确定项目使用的配置文件,然后读取配置文件将配置信息写入System.setProperty中,在使用的时候在使用System.getProperty获取value,这样就可以达到不同的环境使用不同配置的目的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
// should always initialize system properties like app.id in the first place
initializeSystemProperty(configurableEnvironment);
Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);
//EnvironmentPostProcessor should not be triggered if you don't want Apollo Loading before Logging System Initialization
if (!eagerLoadEnabled) {
return;
}
Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);
if (bootstrapEnabled) {
initialize(configurableEnvironment);
}
}
到这里发现,赋值时需要的值是从ConfigurableEnvironment中获取。这里并没有发现针对Apollo配置中心或dubbo注册中心相关的逻辑,猜测我们需要确认的属性设置入口可能在其他地方设置的,回到根容器加载的地方梳理什么时候加载的这些配置。
回到applicationContext.xml
回到xml配置文件中,发下有个资源引入看起来有点意思。进去看下。1
<import resource="classpath:META-INF/spring/applicationContext-soa.xml"/>
applicationContext-soa.xml配置
1
2
3
4
5<!-- 加载dubbo默认配置 -->
<bean class="com.soa.configcenter.MyPropertyPlaceholderConfigurer"
p:applicationName="common"
p:ignoreUnresolvablePlaceholders="true"
p:locations="dubbo-common.properties"/>
这个配置看起来和我们要找的dubbo配置好像有点关系,进去看下。
- 进入MyPropertyPlaceholderConfigurer
MyPropertyPlaceholderConfigurer继承了PropertyPlaceholderConfigurer,实现了InitializingBean。
PropertyPlaceholderConfigurer:PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,就是BeanFactoryPostProcessor接口的一个实现。PropertyPlaceholderConfigurer可以将上下文中的属性值放在一个单独的标准java properties文件中,在XML文件使用${}替换指定的properties文件中的值。在SSH、SSI时代我们经常独立jdbc.properties和app.properties来抽象xml中的配置信息,其中这些配置信息的装载是依赖PropertyPlaceholderConfigurer实现的。
InitializingBean:在初始化bean的时候执行,可以针对某个具体的bean进行配置、在生命周期中他的执行顺序在init-method之前。
MyPropertyPlaceholderConfigurer#afterPropertiesSet代码引用中有这么一段:1
2
3
4
5
6
7
8
9
10
11
12private String getConfigCenterServerUrlByEnv() {
if (ip != null && (ip.startsWith("11.") || ip.startsWith("11."))) {
return Constants.DEV;
}
if (hostName != null && hostName.indexOf("-online-") != -1) {
return Constants.OL;
} else if (hostName != null && hostName.indexOf("-pl-") != -1) {
return Constants.PL;
}
return null;
}
查看引用Constants1
2
3
4
5
6
7
8
9
10
11
12public class Constants {
//配置中心DEV
public final static String DEV = "http://config-dev.com";
//配置中心PL
public final static String PL = "http://config-pl.com";
//配置中心ONLINE
public final static String OL = "http://config-ol.com";
//配置中心默认应用路径
public final static String DF = "http://config-dev.com";
}
至此,问题基本上可以确定,由于环境的配置并不是根据启动参数env来判断的,而是根据ip判断的。所以就出现了背景描述中的场景,在本地调试服务qa会调用到dev环境但是发布到qa和ol服务是好的。如果有分析不对的地方请及时和我联系更新,感谢。
总结:前人挖坑,后人遭殃。