Spring Cloud 微服务公共配置处理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud 微服务公共配置处理相关的知识,希望对你有一定的参考价值。

Spring Cloud Config Server提供了微服务获取配置的功能,这些配置文件(application.yml或者application.properties)通常维护在git或者数据库中,而且支持通过RefreshScope动态刷新,使用起来还是比较灵活的。但是当微服务越来越多时,会遇到下面几个问题:

  1. 配置文件的敏感数如数据库地址和账号信息,据呈现在每个配置文件中,替换起来需要一个个配置文件进行修改。
  2. 各个微服务配置文件存在很多冗余配置(如Eureka,Feign),一旦这些部分调整,需要针对每个微服务进行调整,运维压力大增。

为了解决上述问题,我们可以从configServer服务着手进行改造,示意如下:
技术图片

不同的服务ABC,不管是在配置中心仓库端配置了多少个文件,从ConfigServer返回的,一定是服务最终应用的配置。获取配置的方式,通常是调用ConfigServer的一个地址,如:

http://localhost:8021/common_rent/dev/aliyun_dev

common_rent是application name,dev是profile,aliyun_dev是label(git的分支)。这个地址的处理接口,是ConfigServer的EnvironmentController,所以通过拦截这个接口,将敏感信息或者公共配置抽取到configServer的application.yml, 返回前进行替换或者拼接,即可实现上述目的。

代码示例:

  1. 拦截器实现

    @Component
    @Aspect
    public class ResourceLoaderInterceptor 
    
    private static Log logger = LogFactory.getLog(ResourceLoaderInterceptor.class);
    @Resource
    ExternalProperties externalProperties;
    
    @Around("execution(* org.springframework.cloud.config.server..*Controller.*(..)) ")
    public Object commonPropertiesResolve(ProceedingJoinPoint joinPoint) throws Throwable 
        Object returnObj = null;
        Object[] args = joinPoint.getArgs();
        StopWatch stopWatch = new StopWatch();
        try 
            stopWatch.start();
            returnObj = joinPoint.proceed(args);
            if (Environment.class.isInstance(returnObj)) 
                Environment environment = (Environment) returnObj;
                if (environment.getPropertySources() != null && environment.getPropertySources().size() > 0) 
                    for (PropertySource propertySource : environment.getPropertySources()) 
                        placeHolderResolve((Map<String, String>) propertySource.getSource());
                    
                
            
         catch (Throwable throwable) 
            logger.error(ExceptionUtils.getStackTrace(throwable));
         finally 
            stopWatch.stop();
            System.out.println(stopWatch.getTotalTimeMillis());
        
        return returnObj;
    
    
    
    private void placeHolderResolve(Map<String, String> source) 
        Map<String, String> placeHolders = collectConfigSet();
        for (String key : source.keySet()) 
            String value = source.get(key);
            if (value != null && value.contains("#")) 
                for (String variable : placeHolders.keySet()) 
                    String vk = "#" + variable + "";
                    value = StringUtils.replace(value, vk, placeHolders.get(variable));
                
                source.put(key, value);
            
        
    
    
    private Map<String, String> collectConfigSet() 
        Map<String, String> placeHolders = new HashMap<>();
        Field[] fields = ExternalProperties.class.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) 
            try 
                Field propertiesField = fields[i];
                ResourcePrefix resourcePrefix = propertiesField.getAnnotation(ResourcePrefix.class);
                String prefix = resourcePrefix.value();
                ExtDataSource extDataSource = (ExtDataSource) BeanUtils.getPropertyDescriptor(ExternalProperties.class, propertiesField.getName()).getReadMethod().invoke(externalProperties);
                if (extDataSource != null) 
                    Field[] fields2 = ExtDataSource.class.getDeclaredFields();
                    for (Field datasourceField : fields2) 
                        try 
                            ResourcePrefix annotation = datasourceField.getAnnotation(ResourcePrefix.class);
                            String suffix = annotation.value();
                            String sourceFieldValue = (String) BeanUtils.getPropertyDescriptor(ExtDataSource.class, datasourceField.getName()).getReadMethod().invoke(extDataSource);
                            if (StringUtils.isNotEmpty(sourceFieldValue)) 
                                placeHolders.put(prefix + "." + suffix, sourceFieldValue);
                            
                         catch (Exception e) 
                            logger.error(ExceptionUtils.getStackTrace(e));
                        
                    
                
             catch (Exception e) 
                logger.error(ExceptionUtils.getStackTrace(e));
            
        
        return placeHolders;
    
    
  2. ExternalProperites实现
@ConfigurationProperties(prefix = "external", ignoreUnknownFields = true)
public class ExternalProperties implements Serializable 

    @ResourcePrefix(value = "spring.datasource")
    private ExtDataSource datasource;
    @ResourcePrefix(value = "spring.data.mongodb")
    private ExtDataSource mongodb;
    @ResourcePrefix(value = "spring.data.redis")
    private ExtDataSource redis;
    @ResourcePrefix(value = "spring.rabbitmq")
    private ExtDataSource rabbitmq;

    public ExtDataSource getDatasource() 
        return datasource;
    

    public void setDatasource(ExtDataSource datasource) 
        this.datasource = datasource;
    

    public ExtDataSource getRabbitmq() 
        return rabbitmq;
    

    public void setRabbitmq(ExtDataSource rabbitmq) 
        this.rabbitmq = rabbitmq;
    

    public ExtDataSource getMongodb() 
        return mongodb;
    

    public void setMongodb(ExtDataSource mongodb) 
        this.mongodb = mongodb;
    

    public ExtDataSource getRedis() 
        return redis;
    

    public void setRedis(ExtDataSource redis) 
        this.redis = redis;
    
  1. ExtDataSource实现

    public class ExtDataSource 
    @ResourcePrefix(value = "host")
    private String host;
    @ResourcePrefix(value = "port")
    private String port;
    @ResourcePrefix(value = "url")
    private String url;
    @ResourcePrefix(value = "uri")
    private String uri;
    @ResourcePrefix(value = "username")
    private String userName;
    @ResourcePrefix(value = "password")
    private String password;
    
    public String getUrl() 
        return url;
    
    
    public void setUrl(String url) 
        this.url = url;
    
    
    public String getHost() 
        return host;
    
    
    public void setHost(String host) 
        this.host = host;
    
    
    public String getPort() 
        return port;
    
    
    public void setPort(String port) 
        this.port = port;
    
    
    public String getUri() 
        return uri;
    
    
    public void setUri(String uri) 
        this.uri = uri;
    
    
    public String getUserName() 
        return userName;
    
    
    public void setUserName(String userName) 
        this.userName = userName;
    
    
    public String getPassword() 
        return password;
    
    
    public void setPassword(String password) 
        this.password = password;
    
    
  2. ResourcePrefix实现
    @Target(ElementType.FIELD, ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ResourcePrefix 
    String value();
    

然后在configServer的application.yml中增加相关信息,如

external:
  datasource:
    host: 122.122.111.111
    port: 3307
    userName: usr
    password: pwd
  mongodb:
    host: 122.122.111.111
    port: 20467
    uri: 122.122.111.111:20467,122.122.111.112:20467,122.122.111.112:20467
    userName: usr
    password:  pwd
  redis:
    uri: 122.122.111.113:6379,122.122.112.113:6379,122.122.111.113:6379
    password: redispassword
  rabbitmq:
    host: 122.122.111.113
    port: 20467
    userName: usr
    password: pwd

将ServiceA的配置文件serviceA_dev.yml中的数据库相关信息替换成变量,以mysql为例
spring.datasource.uri: url: jdbc:mysql://#spring.datasource.host:#spring.datasource.port/dbName?useUnicode=true&characterEncoding=utf8,
serviceB和serviceC配置文件做同样处理,即可实现一次性替换。

后续如果需要增加公共配置,可以直接在ConfigServer的配置中间中增加,调整下拦截器的实现逻辑即可。

以上是关于Spring Cloud 微服务公共配置处理的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud微服务:公共模块的搭建

Spring Cloud微服务体系的组成

Spring Cloud Alibaba - 18 Nacos Config配置中心加载相同微服务的不同环境下的通用配置

微服务框架之Spring Cloud简介

Spring Cloud微服务如何设计异常处理机制?

Spring Cloud构建微服务架构 消息驱动的微服务(消费分区)Dalston版