HZERO开源版学习笔记二(配置中心)
HZERO-CONFIG我只写一些自己的笔记,关于该模块基础概念请参考官网HZERO的配置中心实现方式,没有采用Spring Cloud默认提供的git等文件形式的配置提供,而是采用了数据库配置的方式。配置hzero-config的bootstrap.yml中有这样一段配置,没有注释,其含义为eureka:instance:# 以IP注册到注册中心preferIpAddress: ${EUREKA
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: 72021-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个端点
- 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
- 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);
}
}
- publish端点
该端点主要完成的内容:
- 修改数据库配置值
- 根据serviceName和label获取监听器,如果有该监听且keys值与数据库配置值不同,则http调用notifyUrl。
如果有同学测试该端点的时候,可能发现并不能正常触发监听事件。原因可能是该端点有两个bug,已在github上报,可以点进去查看。
- publish-kv端点
该端点与publish端点几乎一致,仅仅是只修改kv对。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)