Apollo配置Client源码解析及Disconf配置

1,383 阅读5分钟

Apollo client获取服务器的三种方式

1.启动的时候http从服务器获取

2.定时任务定时从服务器获取

3.服务器主动推送到客户端

若不能连接到服务器,则从本地文件获取。

LocalFileConfigRepository 继承 AbstractConfigRepository
RemoteConfigRepository 继承 AbstractConfigRepository

PropertySourcesProcessor继承 BeanFactoryPostProcessor 加载的入口

  1. NamespaceHandler.doParse()方法解析Apollo的XMl配置
  2. 解析调用PropertySourcesProcessor.addNamespaces()方法新增到NAMESPACE_NAMES
  3. PropertySourcesProcessor.postProcessBeanFactory()
  4. PropertySourcesProcessor.initializePropertySources()
  5. 循环ConfigService.getConfig(namespace);
  6. LocalFileConfigRepository createLocalConfigRepository 里面先调用远程的 LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
  7. 远程则loadApolloConfig() 通过http请求去服务器请求配置信息。
 RemoteConfigRepository
        trySync()
            sync()
               ApolloConfig loadApolloConfig() 获取apollo上的配置信息
                   http://apolloserver:8080/configs/项目目/集群名/namespace?ip=apollo客户端ip
  1. 调用本地LocalFileConfigRepository 文件写入本地文件。

Apollo长连接

前面提到了Apollo客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。

长连接实际上我们是通过Http Long Polling实现的,具体而言:

客户端发起一个Http请求到服务端 服务端会保持住这个连接30秒 如果在30秒内有客户端关心的配置变化,被保持住的客户端请求会立即返回,并告知客户端有配置变化的namespace信息,客户端会据此拉取对应namespace的最新配置 如果在30秒内没有客户端关心的配置变化,那么会返回Http状态码304给客户端 客户端在服务端请求返回后会自动重连 考虑到会有数万客户端向服务端发起长连,在服务端我们使用了async servlet(Spring DeferredResult)来服务Http Long Polling请求。

一,轮询,长轮询

轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。  优点:后端程序编写比较容易。  缺点:请求中有大半是无用,浪费带宽和服务器资源。  实例:适于小型应用。

长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。  优点:在无消息的情况下不会频繁的请求,耗费资源小。  缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。  实例:WebQQ、Hi网页版、Facebook IM。

在源码的config package下有一个java类
PropertySourcesProcessor实现的接口有 BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered。 EnvironmentAware 通过实现EnvironmentAware接口,获取环境变量。

获取的是spring的context里面的初始化的环境变量信息。

BeanFactoryPostProcessor的使用
BeanFactoryPostProcessor的主要作用是让你能接触到bean definitions,对bean definitions进行一定hack,不允许在BeanFactoryPostProcessor中触发到bean的实例化。
but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects
可以修改bean的属性值

Apollo配置
通过页面配置

新增后

resoures下新增文件

客户端依赖的maven文件

       <dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>1.1.0</version>
        </dependency>

spring配置文件中新增配置

<apollo:config/>
<apollo:config namespaces="apolloDealer"/>

调用测试

 
    @SpringBootApplication
    public class ApolloTest {
    	public static void main(String args[]) {
    		SpringApplication.run(ApolloTest.class, args);
    		String[] fn = new String[] {"apollo.xml"};
    		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(fn);
    		User user = ctx.getBean("user", User.class);
    		while (true){
    			System.out.println("name = " + user.getName());
            }
    	}
    }

测试类


@Service
public class User {

    @Value("${name:1222}")
    private String name;

    public String getName(){
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


Disconf是百度的一个分布式配置中心,目前已经开源。而且它是基于java实现的,有简单的配置页面。

一 配置文件的方式

1.新增app

2.选择新建配置文件

上传方式可以采用输入文本或者上传配置文件,效果是一样的。

java应用系统调用,resources下新增disconf.properties文件


# 是否使用远程配置文件,true(默认)会从远程获取配置,false则直接从本地获取配置
disconf.enable.remote.conf=true

# 配置服务器的HOST,用逗号分隔
disconf.conf_server_host=disconf链接地址

# 版本号
disconf.version=1.0.0

# APP的名称
disconf.app=p-dealer

# 添加的配置文件所在的环境
disconf.env=local

# 忽略分布式配置
disconf.ignore=

# 获取远程配置重试次数
disconf.conf_server_url_retry_times=1
# 获取远程配置重试时休眠时间
disconf.conf_server_url_retry_sleep_seconds=1

# 用户自定义的下载路径
#disconf.user_define_download_dir=./disconf

applicationContext.xml 文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.jeff" />

    <aop:aspectj-autoproxy proxy-target-class="true"/>

    <!-- 使用disconf必须添加以下配置 -->
    <bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
          destroy-method="destroy">
        <property name="scanPackage" value="com.jeff"/>
    </bean>
    <bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
          init-method="init" destroy-method="destroy">
    </bean>

    <bean id="configproperties_disconf"
          class="com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:/redis.properties</value>
                <value>classpath:/system.properties</value>
            </list>
        </property>
    </bean>

    <bean id="propertyConfigurer"
          class="com.baidu.disconf.client.addons.properties.ReloadingPropertyPlaceholderConfigurer">
        <property name="ignoreResourceNotFound" value="true" />
        <property name="ignoreUnresolvablePlaceholders" value="true" />
        <property name="propertiesArray">
            <list>
                <ref bean="configproperties_disconf" />
            </list>
        </property>
    </bean>
</beans>

java测试文件

package com.jeff;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.support.ClassPathXmlApplicationContext;

@SpringBootApplication
// 引入disconf
public class DisconfRunMain{

	public static void main(String args[]) {

		SpringApplication.run(DisconfRunMain.class, args);

		String[] fn = new String[] {"applicationContext.xml"};
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(fn);

		JedisConfig jedisConfig = ctx.getBean("jedisConfig", JedisConfig.class);
		while(true) {
			System.out.println("host=" + jedisConfig.getHost());
			System.out.println("port=" + jedisConfig.getPort());
		}
	}
}

运行起来后从页面更新disconf配置文件,系统立马可以读取。

host=192.168.111.199
port=6389

二 配置项
配置项的新增与修改与配置文件略有不同。

package com.jeff;

import com.baidu.disconf.client.common.annotations.DisconfItem;
import org.springframework.stereotype.Service;

@Service
public class User {

    private String name;

    @DisconfItem(key="name")
    public String getName(){
        return name;
    }
}

使用的时候直接DisconfItem加上配置的key值,就可以直接读取。

修改配置项的内容,项目里面读取的内容也能及时得到更新。

原理
Disconf怎么做到实时修改?
Disconf主要是依靠zookeeper的Watch机制来做配置实时修改的,我们都知道ZK是通过目录挂载的方式来做服务的自动注册与发布。客户端启动时注册了一个回调接口,当zk目录发生变化时会回调所有客户端节点,从而做到"实时"更新配置的目的。