• (六)Alian 的 Spring Cloud Config 配置中心(服务端)


    一、简介

      Spring Cloud Config 包含以下两个部分:

    • Config Server :一般称为分布式配置中心,是一个独立运行的微服务应用,它通过连接数据服务(git,db等)为客户端提供获取配置信息、加密信息和解密信息的访问接口
    • Config Client :一般是微服务架构中的各个微服务,它们通过 Config Server 的接口获取和加载配置信息

      本文中的配置中心的 数据是保存在mysql数据库中 ,并且可以 给客户端提供统一的日志管理

    二、数据库

      在实际中,我们的服务有很多,每个服务都有很多的配置,并且有很多配置是一样的,有很多又不是一样的,基于这种情况,我们创建一个数据库config_center,然后新建两个表和一个视图(根据自己需要,你也可以连表查询)。分别是应用表,对应客户端应用,属性表对应每个应用的配置项。

    2.1、应用表

    CREATE TABLE `tb_app` (
      `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `app_name` varchar(30) NOT NULL DEFAULT '' COMMENT '应用名',
      `priority` int unsigned NOT NULL DEFAULT '1' COMMENT '优先级',
      `remark` varchar(45) NOT NULL DEFAULT '' COMMENT '备注',
      PRIMARY KEY (`id`),
      UNIQUE KEY `app_name_UNIQUE` (`app_name`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='应用表';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

      实际工作中会有很多的工程,我们把它叫应用,可以通过后台去添加这些应用。

    2.2、属性表

    CREATE TABLE `tb_property` (
      `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
      `app_id` int NOT NULL COMMENT '应用id',
      `config_key` varchar(200)  NOT NULL DEFAULT '' COMMENT '键',
      `config_value` varchar(500) NOT NULL DEFAULT '' COMMENT '值',
      `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
      `refreshable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否可动态刷新',
      `remark` varchar(200) NOT NULL DEFAULT '' COMMENT '备注',
      `profile` varchar(10) CHARACTER SET utf8 NOT NULL DEFAULT 'default' COMMENT 'profile',
      `label` varchar(10) CHARACTER SET utf8 NOT NULL DEFAULT 'master' COMMENT 'label',
      PRIMARY KEY (`id`),
      UNIQUE KEY `property_un` (`app_id`,`config_key`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='配置表';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      一个应用会有很多的属性,我们也可以去把每个应用的属性管理起来。

    2.3、视图

    CREATE VIEW `v_app_properties` (`id` , `appName` , `configKey` , `configValue` , `PROFILE` , `label`, `priority`) AS
        SELECT 
            `tp`.`id` AS `id`,
            `ta`.`app_name` AS `appName`,
            `tp`.`config_key` AS `configKey`,
            `tp`.`config_value` AS `configValue`,
            `tp`.`profile` AS `profile`,
            `tp`.`label` AS `label`,
            `ta`.`priority` AS `priority`
        FROM
    		`tb_property` `tp`
            LEFT JOIN `tb_app` `ta` ON `tp`.`app_id` = `ta`.`id`
        WHERE
            `tp`.`enabled` = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

      为什么要创建一个视图呢?实际是可以通过应用表和属性表连查得到结果,但是我们这些应用及属性是一个数量有限的,并且数据增长不会很快,为了便于观察和查询,我觉得此处使用视图,可能效果会更好。当然你也可以不用,就使用我上面的连接查询也行。

    2.4、初始化数据

      两个截图!!!(这样看起来结构清晰)
    在这里插入图片描述
    在这里插入图片描述

    三、配置

    3.1、pom.xml

    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <parent>
            <groupId>cn.alian.microservicegroupId>
            <artifactId>parentartifactId>
            <version>1.0.0-SNAPSHOTversion>
        parent>
    
        <artifactId>config-serviceartifactId>
        <version>1.0.0-SNAPSHOTversion>
        <packaging>jarpackaging>
        <name>config-servicename>
        <description>配置中心description>
    
        <dependencies>
    		
            <dependency>
                <groupId>cn.alian.microservicegroupId>
                <artifactId>common-dbartifactId>
                <version>1.0.0-SNAPSHOTversion>
            dependency>
    		
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-config-serverartifactId>
            dependency>
             
    	    <dependency>
    	        <groupId>org.springframework.cloudgroupId>
    	        <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
    	    dependency>
    		
            <dependency>
                <groupId>com.googlecode.log4jdbcgroupId>
                <artifactId>log4jdbcartifactId>
            dependency>
    		
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
            dependency>
    		
            <dependency>
                <groupId>org.zalandogroupId>
                <artifactId>logbook-spring-boot-starterartifactId>
                <version>2.0.0version>
            dependency>
    
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

      需要注意的是

    
    <dependency>
        <groupId>cn.alian.microservicegroupId>
        <artifactId>common-dbartifactId>
        <version>1.0.0-SNAPSHOTversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      是我上一篇文章自定义的starter,不知道的,可以查看上一篇文章,所以我这里就不用再手动引入ebean相关的依赖了,是不是很简洁方便?当然我这里的配置中心也注册到了Eureka了。

    3.2、application.properties

    #应用名
    spring.application.name=config-service
    #后定义的bean会覆盖之前定义的相同名称的bean
    spring.main.allow-bean-definition-overriding=true
    #端口
    server.port=6666
    
    #数据库配置(生产可以换其他的)
    spring.datasource.driver-class-name=net.sf.log4jdbc.DriverSpy
    spring.datasource.url=jdbc:log4jdbc:mysql://10.130.3.99:3306/config_center?autoReconnect=true&useSSL=false
    spring.datasource.username=alian
    spring.datasource.password=Alian1223
    spring.datasource.hikari.connection-timeout=5000
    spring.datasource.hikari.maximum-pool-size=10
    
    #配置采用数据库存储
    spring.profiles.active=jdbc
    #配置中心默认profile
    spring.cloud.config.server.default-profile=default
    #配置中心默认label
    spring.cloud.config.server.default-label=master
    #查询配置中心配置的语句
    spring.cloud.config.server.jdbc.sql=SELECT configKey,configValue FROM v_app_properties WHERE appName=? AND profile=? AND label=? ORDER BY priority;
    #优先级配置(jdbc大于native)
    spring.cloud.config.server.jdbc.order=0
    
    #日志配置文件
    #app.logback.dir=/microservice/config-service/logback
    app.logback.dir=C:/workspace/study/spring-cloud-microservices/config-service/logback
    
    #部署时
    #logging.config=file:config/logback.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

      我们着重关注下这个

    #查询配置中心配置的语句
    spring.cloud.config.server.jdbc.sql=SELECT configKey,configValue FROM v_app_properties 
    WHERE appName=? AND profile=? AND label=? ORDER BY priority;
    
    • 1
    • 2
    • 3

      我这里就是查询我们之前创建的那个视图,因为如果你没有设置这个语句,那么默认查询就是

    private static final String DEFAULT_SQL = "SELECT KEY, VALUE from PROPERTIES"
    			+ " where APPLICATION=? and PROFILE=? and LABEL=?";
    
    • 1
    • 2

      不过你就得按照它的要求去减表和字段了。从这里你就知道我们自定义sql的灵活的优势了,尤其是对于微服务来说。不管怎么样,默认的和自定义的都会有这三个参数:

    • APPLICATION
    • PROFILE
    • LABEL

      对这个地方每个人的看法都不一样,也没有谁对谁错。我就讲下我的理解,第一个APPLICATION是应用的名称(spring.application.name),基本也就固定了,后面两个可以自由发挥;第二个当做环境变量,比如开发(dev)还是测试(test),反正别扯蛋说生产了,谁会生产和测试在一个环境乱搞啊;第三个就当做代码版本配置,比如主干和分支,在没有特别大团队的情况下,一般改动第二个就可以了。含义没有绝对的要求,只要能满足你的查询需求即可。

    3.3、主类

    ConfigServiceApplication.java

    package cn.alian.microservice.configService;
    
    import cn.alian.microservice.configService.config.AppProperties;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cloud.config.server.EnableConfigServer;
    
    @EnableConfigServer
    @EnableConfigurationProperties({AppProperties.class})
    @SpringBootApplication
    public class ConfigServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ConfigServiceApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    注意这里使用注解 @EnableConfigServer

    3.4、应用的默认日志配置

    LogbackController

    package cn.alian.microservice.configService.controller;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.FileCopyUtils;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.io.File;
    import java.io.FileReader;
    
    @Controller
    public class LogbackController {
    
        private static final Logger log = LoggerFactory.getLogger(LogbackController.class);
    
        @Value("${app.logback.dir}")
        private String logbackDir;
    
        @GetMapping({"/logback/{appName}.xml"})
        @ResponseBody
        public String logback(@PathVariable("appName") String appName) throws Exception {
            log.info("logback appName = [" + appName + "]");
            try {
                if (!(new File(this.logbackDir + "/" + appName + ".xml")).exists()) {
                    log.info("指定日志配置【{}】不存在,使用默认配置", appName);
                    appName = "default";
                }
                return FileCopyUtils.copyToString(new FileReader(this.logbackDir + "/" + appName + ".xml"));
            } catch (Exception e) {
                log.error(null, e);
                throw e;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

      日志配置文件: C:/workspace/study/spring-cloud-microservices/config-service/logback/default.xml

    default.xml

    
    
    
    <configuration scan="true" scanPeriod="30 seconds" debug="false">
    
        <property name="appName" value="app"/>
    
        
        <property name="log.path" value="logs"/>
        
        <property name="console.pattern" value="%d{yyyy-MM-dd HH:mm:ss SSS} [%thread] %level %method %line:%m%n"/>
        
        <property name="file.pattern" value="%d{yyyy-MM-dd HH:mm:ss SSS} [%thread] %level %method %line:%m%n"/>
    
        
        <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
            
            <Target>System.outTarget>
            
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>debuglevel>
            filter>
            
            <encoder>
                
                <pattern>${console.pattern}pattern>
                
                <charset>UTF-8charset>
            encoder>
        appender>
    
        
        <appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender">
            
            <File>logs/${appName}.logFile>
            
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                
                <fileNamePattern>logs/${appName}.%d{yyyy-MM-dd}.logfileNamePattern>
                
                
                
                
                
                <MaxHistory>10MaxHistory>
                
                <totalSizeCap>20GBtotalSizeCap>
            rollingPolicy>
            
            <Append>trueAppend>
            
            <encoder>
                
                <pattern>${file.pattern}pattern>
                
                <charset>UTF-8charset>
            encoder>
            
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>infolevel>
            filter>
        appender>
    
        <logger name="cn.alian" level="debug"/>
        <logger name="io.swagger" level="error"/>
    
        <logger name="jdbc.sqlonly" level="ERROR" additivity="false">
            <appender-ref ref="File"/>
        logger>
        <logger name="jdbc.audit" level="ERROR" additivity="false">
            <appender-ref ref="File"/>
        logger>
        <logger name="jdbc.resultset" level="ERROR" additivity="false">
            <appender-ref ref="File"/>
        logger>
        <logger name="jdbc.connection" level="ERROR" additivity="false">
            <appender-ref ref="File"/>
        logger>
        <logger name="jdbc.sqltiming" level="info" additivity="false">
            <appender-ref ref="File"/>
        logger>
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="File"/>
        root>
    configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

      我们这个日志有什么用呢,实际就是当应用通过配置方式请求到配置中心的日志接口时,如

    #日志配置,通过配置中心获取
    logging.config=${spring.cloud.config.uri}/logback/${spring.application.name}.xml
    
    • 1
    • 2

      我们就可以把这个默认的日志提供给应用,这样可以通过配置中心达到统一日志格式。

    3.5、应用属性切面

    AppPropertiesAspect

    package cn.alian.microservice.configService.config;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cloud.config.environment.Environment;
    import org.springframework.cloud.config.environment.PropertySource;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    @Component
    @Aspect
    public class AppPropertiesAspect {
    
        private static final Logger log = LoggerFactory.getLogger(AppPropertiesAspect.class);
    
        @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
        public void requestMapping() {
        }
    
        @Around("requestMapping()")
        public Object printAppProperties(ProceedingJoinPoint point) throws Throwable {
            Object object = point.proceed();
            if (object instanceof Environment) {
                List<PropertySource> propertySources = ((Environment) object).getPropertySources();
                for (PropertySource propertySource : propertySources) {
                    String sourceName = propertySource.getName();
                    Map<String, Object> source = (Map<String, Object>) propertySource.getSource();
                    Set<Map.Entry<String, Object>> entrySet = source.entrySet();
                    for (Map.Entry<String, Object> entry : entrySet) {
                        log.info("sourceName={}, key={}, value={}", sourceName, entry.getKey(), entry.getValue());
                    }
                }
            }
            return object;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

      通过切面我们可以可以打印出每个应用获取的属性配置,便于开发调试时查找问题。其实到这里我们的配置中心服务端就弄好了。主要有两个功能,一个是提供配置,一个是提供日志,接下里我们准备通过订单服务去搭建配置中心客户端吧。

  • 相关阅读:
    请假要组长和经理同时审批该怎么办?来看看工作流中的会签功能
    Spring中的IOC控制翻转容器使用与总结
    consul python sdk
    深入浅出 -- 系统架构之微服务标准组件及职责
    nacos配置中心的核心概念
    点读笔背后的神秘力量,究竟是如何实现即时识别的?
    Web缓存服务——Squid代理服务器
    2024携程校招面试真题汇总及其解答(一)
    hack the box:RouterSpace题解
    ffmpeg批量合并截取音频文件
  • 原文地址:https://blog.csdn.net/Alian_1223/article/details/124025912