我的架构之路 — 配置中心(三)— 动态更新配置

包含两方面的含义:
一、变化的配置能够下发到客户端.
二、客户端能够方便地处理这些变化的配置。下面会讲普通监听器回调方式和spring 注解到field的方式。

配置下发

1)客户端间隔20秒(可配)去数据库拉取配置,sql传递的参数有配置名称、环境、上一次最大更新时间
2)取到配置后,合并更新本地备份。
3)通过监听器的方式通知配置变化。


#监听器接口
public interface ConfigUpdateListener {
   void update(Properties properties);
}

#启动时添加监听器
 public void addConfigUpdateListener(ConfigUpdateListener configUpdateListener) {
        configUpdateListeners.add(configUpdateListener);
    }

#配置变化,调用监听器进行通知
  for (ConfigUpdateListener configUpdateListener : configUpdateListeners) {
                configUpdateListener.update(changedProperties);
            }

spring 注解方式绑定成员变量

通过@ConfigValue注解来实现变量绑定,当配置变化的时候会同时更新这个变量。这种方式可以用于一起开关切换、降级处理的场景。

@ConfigValue("baseUrl")
private String baseUrl;

实现原理:
1)从配置中心获取配置。
2)通过BeanPostProcessor 遍历每个spring bean,找出所有包含@ConfigValue注解的field,然后设置值为配置中心的属性值,同时把记录field到Map对象,以后更新配置时就不用想办法遍历了。这里有一个严格处理原则,如果配置中心找不到相应key或值赋值时候报异常,终止程序,保证安全性。
其实代码对非spring的、static 成员变量也支持,只是需要程序启动时候指定包含这些static变量的类名列表。

public class PropertyPlaceholderConfigurer extends
        org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
        implements BeanPostProcessor
{
    private Map<String,List<BeanField>> configMap= new ConcurrentHashMap<String,List<BeanField>>();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        ConfigInjectSupport.inject(mergedProperties,bean.getClass(),bean,configMap);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        return bean;
    }
public static void inject(Properties properties, Class clazz, Object bean, Map<String,List<BeanField>> configMap){
        Field[] fields = clazz.getDeclaredFields();
        if (null != fields) {
            for (Field f : fields) {
                // 如果 bean ==null(非spring),则必须是静态方法
                if(bean==null &&  !Modifier.isStatic(f.getModifiers())) continue;
                ConfigValue configValue = f.getAnnotation(ConfigValue.class);
                if (configValue != null) {
                    String key = configValue.value();
                    f.setAccessible(true);
                    Object value = properties.get(key);
                    if (value != null) {
                        try {
                            f.set(bean, converter.convertIfNecessary(value, f.getType(), f));
                        } catch (IllegalAccessException e) {
                            throw  new RuntimeException(e);
                        }
                    }else{
                        RuntimeException e= new RuntimeException("can not find config. key="+key+", class="+clazz+", field="+f.getName());
                        e.printStackTrace();
                        throw e;
                    }
                    List<BeanField> list = configMap.get(key);
                    if (list == null) {
                        list = new ArrayList<BeanField>();
                        configMap.put(key, list);
                    }
                    BeanField beanField=new BeanField();
                    beanField.setBean(bean);
                    beanField.setField(f);
                    list.add(beanField);
                }

            }

        }
        clazz = clazz.getSuperclass();
    }

3)加入监听器,接收变化的配置,更新field值

public static void addConfigUpdateListener(final Map<String,List<BeanField>> configMap) {
        DefaultConfigClient.addConfigUpdateListener(new ConfigUpdateListener() {
            @Override
            public void update(Properties properties) {
                for (Map.Entry<Object, Object> e : properties.entrySet()) {
                    List<BeanField> beanFields = configMap.get(e.getKey().toString());
                    if (beanFields != null) {
                        Object value = properties.get(e.getKey());
                        for (BeanField beanField : beanFields) {
                            try {
                                ConfigValue configValue = beanField.getField().getAnnotation(ConfigValue.class);
                                if(configValue.updatable()) {
                                    ConfigInjectSupport.setFieldValue(beanField, value);
                                }
                            } catch (IllegalAccessException ex) {
                                ex.printStackTrace();
                            }
                        }
                    }

                }
            }
        });
    }

 public static void setFieldValue(BeanField beanField,Object value) throws IllegalAccessException {
        Field f=beanField.getField();
        f.set(beanField.getBean(), ConvertUtils.convert(value,f.getType()));
    }
时间: 2024-09-29 16:30:47

我的架构之路 — 配置中心(三)— 动态更新配置的相关文章

CentOS下Google Authenticator配置SSH登录动态验证码配置

说明: 1.一般ssh登录服务器,只需要输入账号和密码. 2.本教程的目的:在账号和密码之间再增加一个验证码,只有输入正确的验证码之后,再输入密码才能登录.这样就增强了ssh登录的安全性. 3.账号.验证码.密码三者缺一个都不能登录,即使账号和密码正确,验证码错误,同样登录失败. 4.验证码:是动态验证码,并且是通过手机客户端自动获取(默认每隔30秒失效一次). 5.最终目的:远程ssh登录一台服务器,需要正确的账号.密码.及一个可以获取到动态验证码的手机(目前支持Android和ios手机系统

dlink路由器配置上网之动态IP配置上网(光纤入户)

光纤线路连接: 首次安装,建议准备两条网线.从光猫LAN口连接一条网线到路由器INTRNET口,再用另一条网线从路由器LAN口连接到电脑.若只有一条线,也可以先连接电脑和路由器,配置好路由器后再拔掉,重新连接到ADSL Modem和路由器INTERNET口:或用笔记本.手机等设备无线连接到路由器进行配置. 如图连线完成后,查看被连线接口对应的指示灯是否正常亮,如果不亮,请参考:指示灯不亮,如何处理? 登陆路由器管理界面: 打开浏览器在地址栏输入http://192.168.0.1  ,如图:  

我的架构之路 — 配置中心(一)—简单实用的配置中心

离开淘宝,我没有去处于风口的摩拜,而是加入了铁甲网,可能也是一种中庸之道吧.不过铁甲竟然也搬家到了亮马桥河畔,不远处就是摩拜.到铁甲第一个项目就是搭建一个配置中心,实现配置的统一管理,实现配置的动态更新,初步要求就是尽快出来,简单.稳定. 淘宝有diamond,但没有开源(内部绑定太多,很早之前有个开源版本),否决了:百度有disconf,但需要mysql.redis.zookeper.nginx 一堆东西,好吧,经过讨论,咱是要一个简单好用的配置管理,那就pass掉吧. 于是又到GitHub找

高可用的分布式配置中心(Spring Cloud Config)

上一篇内容讲述,一个服务如何从配置中心读取文件,配置中心如何从远程Git读取配置文件,当服务很多时,都需要同时从配置中心读取文件的时候,这时我们可以考虑将配置中心做成一个微服务,并且将其集群化,从而达到高可用,架构图如下: 一.准备工作 继续使用上一篇文章的工程,创建一个eureka-server工程,用作服务中心. 其pom.xml: <?xml version="1.0" encoding="UTF-8"?> <project xmlns=&q

springcloud config 分布式配置中心

一.介绍 1.场景: 微服务系统中,系统多.实例多,如果每个系统都有自己一套配置加载.维护的机制,会导致在生产过程中因为配置问题引发的不必要的沟通成本.故障风险.需要采用分布式配置中心统一管理.统一实现方式. 2.Spring cloud config特性 服务端:存储方式(git.svn.本地文件).配置读取方式(环境化.多项目仓库).安全性(访问密码.加密存储).自动刷新. 客户端:加载远程配置.刷新配置.@refreshscope作用域刷新.集成消息总线. 二.docker中运行gitla

思科模拟器静态路由和动态路由配置

思科模拟器 静态路由,动态路由 RIP配置 动态路由协议 RIP 命令就两行比较简单 实验拓扑图如下: 更多精彩内容:http://www.bianceng.cnhttp://www.bianceng.cn/Network/lyjs/ 要想实现pc1 和pc2 通讯就要对R1 ,R2,R3 进行配置 1. 静态配置命令格式如下: 对于R1 Router>en Router#conf Configuring from terminal, memory, or network [terminal]?

我的架构之路 — 配置中心(二)— 在已有项目中实际应用

要把配置中心整合进现有的项目,要做的事情还不少 接管spring mvc属性文件 现在的java应用一般都是spring框架的,spring属性文件配置占了绝大多数比例,所以配置中心的,首要任务就是把spring配置文件集中到配置中心来管理.其实spring cloud本身也有一套配置管理,它是通过git或svn进行配置的,在版本控制上比较方便,但在界面使用上就比太方便了. spring mvc属性文件是通过PropertyPlaceholderConfigurer来管理的,所以要接管sprin

买单侠数据库架构之路

摘要:在2017杭州云栖大会阿里云HTAP技术专场上,上海秦苍信息科技有限公司DBA负责人赵怀刚为大家分享了HTPA型数据库产品在现实中的落地应用以及企业级数据库架构设计中的HTPA的应用. 本文内容根据嘉宾演讲视频以及PPT整理而成. 本次分享的主题是买单侠数据库架构之路.秦苍科技是一家互联网消费金融公司,我们所有的产品基本都是托管在阿里云上的,在自己的系统中大概应用了20多种阿里云数据库产品.基于阿里云平台,秦苍科技的数据库架构与传统RDS数据运维相比存在着本质的区别.接下来着重介绍一下在产

springcloud(九):配置中心和消息总线(配置中心终结版)

我们在springcloud(七):配置中心svn示例和refresh中讲到,如果需要客户端获取到最新的配置信息需要执行refresh,我们可以利用webhook的机制每次提交代码发送请求来刷新客户端,当客户端越来越多的时候,需要每个客户端都执行一遍,这种方案就不太适合了.使用Spring Cloud Bus可以完美解决这一问题. Spring Cloud Bus Spring cloud bus通过轻量消息代理连接各个分布的节点.这会用在广播状态的变化(例如配置变化)或者其他的消息指令.Spr