HZERO开源版学习笔记二(配置中心)

HZERO-CONFIG

自己学习时的笔记,关于该模块基础概念请参考官网

注意:开源版有两处bug,可能影响测试。
已报告到了github,访问链接可以查看。

HZERO的配置中心实现方式,没有采用Spring Cloud默认提供的git等文件形式的配置提供,而是采用了数据库配置的方式。

1. 补充配置含义

hzero-config的bootstrap.yml中有这样一段配置,没有注释,其含义为

eureka:
  instance:
    # 以IP注册到注册中心
    preferIpAddress: ${EUREKA_INSTANCE_PREFER_IP_ADDRESS:true}
    # eureka client发送心跳给server的频率
    leaseRenewalIntervalInSeconds: 10
    # eureka server 至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间
    # 内若没有收到下一次心跳,则移出该instance
    leaseExpirationDurationInSeconds: 30

其中,leaseRenewalIntervalInSeconds默认值为30,leaseExpirationDurationInSeconds默认值为90。

2. 排除spring cloud config server默认实现类

在启动类org.hzero.config.ConfigApplication中,排除了ConfigServerAutoConfiguration

/**
 * HZERO 配置中心
 */
@EnableHZeroConfig
@EnableDiscoveryClient
@SpringBootApplication(exclude = ConfigServerAutoConfiguration.class)
public class ConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigApplication.class, args);
    }
}

排除ConfigServerAutoConfiguration的目的,是要把配置中心换成HZERO的自己实现,剔除Spring Cloud默认实现。
org.springframework.cloud.config.server.EnableConfigServer是标识配置中心的注解,EnableConfigServer是怎么装配ConfigServerAutoConfiguration的呢?

首先打开

/**
 * @author Dave Syer
 * @author
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {

}

发现只是引入了ConfigServerConfiguration类,继续打开

/**
 * @author Spencer Gibb
 */
@Configuration
public class ConfigServerConfiguration {
	class Marker {}

	@Bean
	public Marker enableConfigServerMarker() {
		return new Marker();
	}
}

可以得到,EnableConfigServer仅仅是创建了 Marker对象。

再从spring.factories 入手,
在这里插入图片描述
打开org.springframework.cloud.config.server.config.ConfigServerAutoConfiguration

@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
		ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {

}

可以发现,ConfigServerAutoConfiguration只有在ConfigServerConfiguration.Marker存在时,才会加载。
结合 EnableConfigServer创建了 Marker,就得知了原因。

3.使用数据库配置示例

配置会保存在 hzero-admin 数据库的 hadm_service_config表中。表结构如图:
在这里插入图片描述
我增加了简单的hzero-admin模块的配置,测试数据如下:

1,1,hzero-admin,default,{“year”:2021,“month”:“01”},1,2021-01-29
14:34:36,-1,-1,2021-01-29 14:34:36

增加测试代码并重启hzero-admin。

/**
 * 测试配置中心取值
 *
 * @author bo.yan04@hand-china.com
 * @date 2021/1/29 15:18
 */
@RestController("test")
public class TestController {

    @Value("${year}")
    private String year;

    @Value("${month}")
    private String month;

    @PostMapping("/yearMonth")
    public String yearMonth() {
        return  year+"-"+month;
    }

}

执行HTTP请求,得到正确结果:

POST http://localhost:8060/hadm/yearMonth

HTTP/1.1 200 OK Expires: 0 Cache-Control: no-cache, no-store,
max-age=0, must-revalidate X-XSS-Protection: 1; mode=block Pragma:
no-cache X-Frame-Options: DENY Date: Fri, 29 Jan 2021 07:34:02 GMT
Connection: keep-alive X-Content-Type-Options: nosniff Content-Type:
text/plain;charset=UTF-8 Content-Length: 7

2021-01

Response code: 200 (OK); Time: 12ms; Content length: 7 bytes

说明1:

在HZERO开源版中,并不支持数据库中配置多层级的json值。
我把数据库配置的数据改为:

{
	"year": 2021,
	"month": "01",
	"date": {
		"value": "29"
	}
}

修改代码:

/**
 * 测试配置中心取值
 *
 * @author bo.yan04@hand-china.com
 * @date 2021/1/29 15:18
 */
@RestController("test")
public class TestController {

    @Value("${year}")
    private String year;

    @Value("${month}")
    private String month;

    @Value("${date.value}")
    private String date;

    @PostMapping("/yearMonth")
    public String yearMonth() {
        return  year+"-"+month +"-"+ date;
    }

}

启动报错:
Could not resolve placeholder 'date.value' in value "${date.value}"

其实,我个人觉得多层级json支持一下还是有必要的,也比较简单。
只需要在org.hzero.config.domain.entity.ServiceConfig#jsonToMap方法中,兼容下多层级json就可以了,把多层级的json转map时,也最后搞成一个层级map。

 private static final ObjectMapper MAPPER = new ObjectMapper();

    public Map<String, Object> jsonToMap() {
        if (StringUtils.isNotEmpty(this.configValue)) {
            try {
                return MAPPER.readValue(this.configValue, Map.class);
            } catch (IOException e) {
                LOGGER.warn("deserialize json error.");
            }
        }
        return new HashMap<>();
    }

在这里插入图片描述

说明2

目前的开源版本HZERO还没支持config_yaml配置,应该只是预留了字段。

4.配置中心自定义存储方式

Spring Cloud 配置中心提供了org.springframework.cloud.config.server.environment.EnvironmentRepository用来自定义存储,我们搜一下这个类的实现。
在这里插入图片描述
找到hzero的实现类,打开类图。
在这里插入图片描述
代码:

/**
 * 默认config server从git拉取配置
 * 修改为从数据库获取配置
 *
 * @author youbin.wu
 */
public class DbEnvironmentRepository extends SearchPathCompositeEnvironmentRepository {

    private static final Logger LOGGER = LoggerFactory.getLogger(DbEnvironmentRepository.class);

    @Value("${spring.application.name:hzero-config}")
    private String applicationName;

    private PullConfigService pullConfigService;

    private Map<String, HashMap<String, Environment>> cache = new ConcurrentHashMap<>();

    public DbEnvironmentRepository(List<EnvironmentRepository> environmentRepositories) {
        super(environmentRepositories);
    }

    public void setPullConfigService(PullConfigService pullConfigService) {
        this.pullConfigService = pullConfigService;
    }

    @Override
    public Locations getLocations(String application, String profile, String label) {
        return new Locations(application, profile, label, null, new String[0]);
    }

    @Override
    public Environment findOne(String application, String profile, String label) {
        String[] profiles = new String[1];
        profiles[0] = profile;
        Environment env;
        String info = label == null ? application : application + "-" + label;
        try {
            LOGGER.info("{} 获取配置", info);
            Config config = pullConfigService.getConfig(application, label);
            Map<String, Object> configMap = getDefaultConfig(config.getValue());
            PropertySource propertySource = new PropertySource(application + "-" + profile + "-" + label, configMap);
            String version = config.getConfigVersion();
            env = new Environment(applicationName, profiles, label, version, null);
            env.add(propertySource);
            setCache(application, label, env);
        } catch (Exception e) {
            if (getCache(application, label) != null) {
                env = getCache(application, label);
                LOGGER.warn("获取配置失败,返回上次缓存的配置. info {}", info, e);
            } else {
                throw e;
            }
        }
        return env;
    }

    private void setCache(String application, String label, Environment env) {
        this.cache.computeIfAbsent(application, key -> new HashMap<>(16))
                .putIfAbsent(label, env);
    }

    private Environment getCache(String application, String label) {
        if (this.cache.get(application) != null && this.cache.get(application).get(label) != null) {
            return this.cache.get(application).get(label);
        }
        return null;
    }

    private Map<String, Object> getDefaultConfig(Map<String, Object> map) {
        if (map == null) {
            map = new LinkedHashMap<>();
        }
        map.put("spring.cloud.config.allowOverride", true);
        map.put("spring.cloud.config.failFast", false);
        map.put("spring.cloud.config.overrideNone", false);
        map.put("spring.cloud.config.overrideSystemProperties", false);
        map.put("spring.sleuth.integration.enabled", false);
        map.put("spring.sleuth.scheduled.enabled", false);
        map.put("sampler.percentage", 1.0);
        return map;
    }
}

DEBUG一下,也确实找到了我们配置的数据。
在这里插入图片描述

5.HZAERO配置中心的4个HTTP端点

ConfigEndpoint提供了4个http端点,完成配置的增删改查功能,代码如下:

@RestController
@RequestMapping("config")
public class ConfigEndpoint {

    @Autowired
    private ServiceConfigService configService;

    @GetMapping("/fetch")
    public Map<String, Object> fetch(@RequestParam("serviceName") String serviceName,
                                     @RequestParam("label") String label) {
        return configService.getConfig(serviceName, label);
    }

    @PostMapping("/publish")
    public void publish(@RequestBody ConfigPublishDTO dto) {
        configService.publishConfig(dto.getServiceName(), dto.getLabel(), dto.getFileType(), dto.getContent());
    }

    @PostMapping("/publish-kv")
    public void publishKv(@RequestBody ConfigItemPublishDTO dto) {
        configService.publishConfigItem(dto.getServiceName(), dto.getLabel(), dto.getKey(), dto.getValue());
    }

    @PostMapping("/listen")
    public void listen(@RequestBody ConfigListenDTO dto) {
        configService.registerListener(dto.getServiceName(), dto.getLabel(), new DefaultConfigListener(dto.getKeys(), dto.getNotifyAddr()));
    }


    @GetMapping("/service-instances/{applicationName}")
    public List<ServiceInstance> serviceInstancesByApplicationName(@PathVariable String applicationName) {
        System.out.println("services:"+this.discoveryClient.getServices());
        return this.discoveryClient.getInstances(applicationName);
    }
}

下面分别测试这4个端点

  1. fetch端点
    fetch端点仅仅通过service-name和label获取数据库配置。测试请求如下:
GET http://localhost:8010/config/fetch?serviceName=hzero-admin&label=default

HTTP/1.1 200 OK
Expires: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
X-XSS-Protection: 1; mode=block
Pragma: no-cache
X-Frame-Options: DENY
Date: Mon, 01 Feb 2021 01:47:21 GMT
Connection: keep-alive
X-Content-Type-Options: nosniff
Transfer-Encoding: chunked
Content-Type: application/json;charset=UTF-8

{
  "month": "6",
  "year": "2023"
}

Response code: 200 (OK); Time: 146ms; Content length: 27 bytes

  1. listen端点
    该端点主要功能是注册一个监听事件: 把serviceName和label拼接作为哈希容器的key,List<ConfigListener>作为哈希容器的value,参数keys和notifyAddr作为ConfigListener的主要属性,当哈希容器的key不存在时,当keys改变时,http调用notifyAddr进行通知。
    请求示例如下:

notifyAddr的方法,是我自己加的,仅仅是打印了参数:
@PostMapping("/lister") public void lister(String body) { System.out.println("body==="+body); }

POST http://localhost:8010/config/listen
Content-Type: application/json

{"label":"default",
  "serviceName":"hzero-admin",
  "keys":["month","year"],
  "notifyAddr":"http://localhost:8010/config/lister"
}

主要代码及debug值:

    @Override
    public void registerListener(String serviceName, String label, ConfigListener listener) {
        String key = buildListenerKey(serviceName, label);
        List<ConfigListener> listeners = configListeners.get(key);
        if (listeners == null) {
            listeners = Collections.synchronizedList(new ArrayList<>());
            listeners.add(listener);
            configListeners.putIfAbsent(key, listeners);
        } else {
            listeners.add(listener);
        }
    }

在这里插入图片描述

  1. publish端点
    该端点主要完成的内容:
  • 修改数据库配置值
  • 根据serviceName和label获取监听器,如果有该监听且keys值与数据库配置值不同,则http调用notifyUrl。

如果有同学测试该端点的时候,可能发现并不能正常触发监听事件。原因可能是该端点有两个bug,已在github上报,可以点进去查看。

  1. publish-kv端点
    该端点与publish端点几乎一致,仅仅是只修改kv对。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值