文章目录
- 1. 配置服务的作用
- 2. 配置服务需要完成的工作
- 3. 使用zookeeper实现简单的配置服务
- 3.1. 功能点
- 3.2. 简单代码实现
- 3.2.1. 基本类
- 3.2.2. 启动时注册配置项监听器
- 3.2.3. 配置ConfigManager
- 3.3. 不足及改进方案
- 3.3.1. 监听器注册
- 3.3.2. 配置文件管理
1. 配置服务的作用
一般我们把配置写在配置文件中,但在分布式系统中,各个系统分布在各个不同的服务器中
这极大的增加了修改配置文件的工作量,且手动更新无法保证配置文件的一致性和及时性
为了解决这个问题,有人提出了配置服务的概念,当配置发生变更时,所有服务收到通知并进行相对应的响应
2. 配置服务需要完成的工作
- 在配置变更时通知节点
- 在配置发生时进行处理
- 能对配置进行集中管理
3. 使用zookeeper实现简单的配置服务
3.1. 功能点
- 启动时自动加载配置,如果zk中没有加载默认并设置到zk
- 启动时注册watch到zk中的系统配置节点
- 为每个配置项注册监听器,在配置发生变更时进行相应的操作
- 在数据变更时,触发数据相应的监听器
3.2. 简单代码实现
3.2.1. 基本类
- Listener
public interface Listener<T> {
void process(String config,T newValue,T oldValue,ChangeType changeType);
}
- ChangeType
public enum ChangeType {
UPDATE,
DELETE,
ADD,
NONE
}
- ConfigManager
public class ConfigManager implements InitializingBean {
private Map<String,Object> configMap;
private Map<String, Listener> listenerMap;
private String zookeeperPath;
private ZooKeeper zk;
private ObjectMapper objectMapper;
public void regiest(String key, Listener listenter){
if(listenerMap == null){
listenerMap = new HashMap<>();
}
listenerMap.put(key,listenter);
}
private void configUpudate(Map<String,Object> configMap){
if(this.configMap == null){
configMap.forEach((key,value)->{
triggerListenter(key,null,value);
});
}else {
Set<String> keySet = new HashSet();
keySet.addAll(configMap.keySet());
keySet.addAll(this.configMap.keySet());
keySet.forEach(item -> {
Object oldValue = this.configMap.get(item);
Object newValue = configMap.get(item);
triggerListenter(item, oldValue, newValue);
});
}
this.configMap = configMap;
}
private void triggerListenter(String key,Object oldValue,Object newValue){
if(listenerMap == null) return;
Listener listenter = listenerMap.get(key);
if(listenter == null) {
return;
}
ChangeType changeType = ChangeType.NONE;
if(oldValue == null&& newValue == null){
changeType = ChangeType.NONE;
}else if(oldValue == null){
changeType = ChangeType.ADD;
}else if(newValue == null){
changeType = ChangeType.DELETE;
}else if(oldValue!=null&&newValue!=null&&!oldValue.equals(newValue)){
changeType = ChangeType.UPDATE;
}else{
return;
}
listenter.process(key,newValue,oldValue,changeType);
}
@Override
public void afterPropertiesSet() throws Exception {
loadConfig();
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("数据发生变化了");
try {
byte[] data = zk.getData(zookeeperPath, false, new Stat());
HashMap configMap = objectMapper.readValue(data, HashMap.class);
configUpudate(configMap);
zk.exists(zookeeperPath, this);
}catch (Exception e){
e.printStackTrace();
}
}
};
zk.exists(zookeeperPath,watcher);
}
public Map<String, Object> getConfigMap() {
return configMap;
}
public void setConfigMap(Map<String, Object> configMap) {
this.configMap = configMap;
}
public Map<String, Listener> getListenerMap() {
return listenerMap;
}
public String getZookeeperPath() {
return zookeeperPath;
}
public void setZookeeperPath(String zookeeperPath) {
this.zookeeperPath = zookeeperPath;
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public void loadConfig(){
try {
zk.create("/sumanit", "sumanit".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}catch (Exception e){
}
try {
Map configMap = new HashMap();
configMap.put("server.port",8080);
byte[] bytes = objectMapper.writeValueAsBytes(configMap);
zk.create("/sumanit/config", bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
setConfigMap(configMap);
}catch (Exception e){
}
}
}
3.2.2. 启动时注册配置项监听器
@Component
public class Runner implements CommandLineRunner {
@Autowired
private ConfigManager configManager;
@Override
public void run(String... args) throws Exception {
configManager.regiest("server.port", new Listener() {
@Override
public void process(String config, Object newValue, Object oldValue, ChangeType changeType) {
System.out.println("newValue:"+newValue);
}
});
}
}
3.2.3. 配置ConfigManager
@Configuration
public class ConfigManagerConfiguration {
@Autowired
private ObjectMapper objectMapper;
@Bean
public ConfigManager configManager(){
ConfigManager configManager = new ConfigManager();
try {
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 5000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("连接成功 ");
}
}, true);
configManager.setZk(zk);
configManager.setZookeeperPath("/sumanit/config");
configManager.setObjectMapper(objectMapper);
}catch (Exception e){
e.printStackTrace();
}
return configManager;
}
}
3.3. 不足及改进方案
3.3.1. 监听器注册
需要单独为每个配置手动注册,每增加一个配置项就要增加一个监听器
其实大部分的配置都是直接一个属性值的设置,可以考虑给这个配置增加一个注解
然后当值发生变化时,通过Spring获取到该对象然后利用反射修改配置的值
也有一些特殊的操作需要去做,比如连接ip发生了变化,现在连接的客户端都要重连
3.3.2. 配置文件管理
可以考虑两种配置文件的管理,一种是使用本地文件,一种是使用git
- 本地文件
当修改了文件之后,调用一个接口,然后提交给zk
加载时也是,如果zk没有配置,那么加载本地并设置,如果有使用zk的配置
当zk修改后,也要将配置同步到本地文件中
- 使用git
原理是使用git的hook实现,当文件修改时会调用对应的url
接收到请求后去git获取文件并设置到zk中就可以了
加载时也默认去git中加载