《Spring Cloud 入门》Spring Cloud Config 基于JDBC 的分布式配置中心
《Spring Cloud 入门》Spring Cloud Config 基于JDBC 的分布式配置中心
标签:spring cloud 入门,spring cloud config,spring cloud config jdbc, spring cloud 配置中心基于数据库,spring cloud config 入门,spring cloud config 底层,spring cloud config怎么用
一、什么是分布式配置中心?
随着互联网的飞速发展我们越来越多的开发者尝试使用分布式系统架构来降低项目的复杂度,将各个模块以微服务的方式拆分成一个个子服务。使得每个模块之间的耦合度大大降低,但是这种方式会导致每个服务我们都要写很多的配置信息并且会产生很多配置文件,如果要修改某个服务配置的话可能还需要修改配置文件并且重新打包发布。
这是后分布式配置中心就出现了,我们只需要在配置中心来统一管理一些必要的配置信息就可以了。他能够集中管理配置文件、不同环境不同配置,动态化的配置更新,分环境部署比如/dev/test/prod/uat、运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉去配置自己的信息。
当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置。
将配置信息以REST接口的形式暴露。
二、Spring Cloud Config 的使用
1.Spring cloud config 组成与Maven引用
Spring Cloud Config 主要分为两个部分
1.spring cloud config server config 的服务端
Maven 依赖引入
<!-- server 端jar 引入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
2. spring cloud config client config 的客户端
<!-- client 端jar 引入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
2.Spring Cloud Config Server 的选择
从官方文档中我们可以看出,spring cloud server 支持 native、git、svn、vault、credhub、jdbc和composits 这几中方式来管理配置文件
这几中方式的切换是根据该配置来切换的
# 根据自己的需要配置 native、git、svn、vault、credhub、jdbc或composits
# 本文是讲解基于jdbc 的所以就直接配置jdbc了
spring.profiles.active=jdbc
详细配置可以在源代码中发现
3.Server端基础代码
具体引入Maven依赖
<!--添加配置中心的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!--security 安全框架,可以给配置中心设置用户名和密码,如果不需要此功能可以不适用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<!-- spring jdbc 用户查询数据库-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MYSQL 数据库连接jar-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置文件配置 bootstrap.yml
# bootstrap.yml 优先加载与application.yml
#端口号
server:
port: 8888
servlet:
context-path: /
spring:
application:
name: config-springcloud-jdbc
profiles:
active: jdbc
cloud:
config:
server:
# 请求路径前缀 如果有需要则可以加
prefix:
default-label: master
jdbc:
# 此处sql 表和字段可以按照自己的方式创建, 但是后面几个问好中的值顺序不能变
#第一个问号‘?’对应的是application,第二个是profile,第三个是LABEL
sql: SELECT `attribute`, `VALUE` from Config_Application WHERE APPLICATION_NAME=? AND PROFILE=? AND LABEL=?
# mysql 属性配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
# url 中的host地址和端口,还有ConfigServerDB库名根据你的具体情况创建
url: jdbc:mysql://localhost:3306/ConfigServerDB?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
# 安全中心配置登录用户的账号密码
security:
user:
name: bugwfq
password: bugwfq
本文中测试用到的数据库和表数据等
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for Config_Application
-- ----------------------------
DROP TABLE IF EXISTS `Config_Application`;
CREATE TABLE `Config_Application` (
`id` int(11) NOT NULL,
`attribute` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`application_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'application' COMMENT '服务名称,对应的服务可设置独立的配置 默认值为( application)不管是传入的时什么都会加载application_name 为 application 且profile 为default 和传入的值的所有配置',
`profile` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'default' COMMENT '配置的环境,一般可设置为dev、test或者其他 默认值设置为(default)不管传入dev还是其他任何环境都可以查出',
`label` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'master' COMMENT '可分为不同版本来存储配置',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '配置中心' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of Config_Application
-- ----------------------------
INSERT INTO `Config_Application` VALUES (1, 'wfq', '王福强', 'cloud-client', 'dev', 'master');
INSERT INTO `Config_Application` VALUES (2, 'zsf', '张三丰', 'cloud-client', 'test', 'master');
INSERT INTO `Config_Application` VALUES (3, 'ls', '李四', 'user-manager', 'dev', 'master');
INSERT INTO `Config_Application` VALUES (4, 'mrpz', '全局默认配置', 'application', 'default', 'master');
INSERT INTO `Config_Application` VALUES (5, 'client_default', 'client默认配置', 'cloud-client', 'default', 'master');
INSERT INTO `Config_Application` VALUES (6, 'manager_default', 'manager默认配置', 'user-manager', 'default', 'master');
INSERT INTO `Config_Application` VALUES (7, 'devpz', '全局dev配置', 'application', 'dev', 'master');
INSERT INTO `Config_Application` VALUES (8, 'testpz', '全局test配置', 'application', 'test', 'master');
INSERT INTO `Config_Application` VALUES (9, 'wangfuqiang', '强爸爸', 'registry-eureka', 'dev', 'master');
SET FOREIGN_KEY_CHECKS = 1;
启动类代码
package com.fuqiang.framework.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* 配置中心启动类
*
* @Author 王福强
* @Since 2019年05月24日 17:25
* @Email aaa@qq.com
*/
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class);
}
}
可以用浏览器简单测试一下ConfigServer是否可用
先在数据库加一条数据
访问地址格式
http://localhost:8888/{application_name}/{profile}/{label}
如果配置了安全中心,则输入账号密码,如果没有配置忽略此图
不出意外的话应该是可以了
4.Client端的简单使用
spring cloud config client 的使用
首先我们创建一个新项目
下面是maven 依赖
<!--配置中心客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--security 如果需要security则引入,不需要请忽略-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
下面是bootstrap.yml文件配置
# bootstrap.yml 优先加载与application.yml
#端口号
server:
port: 8889
servlet:
context-path: /
spring:
application:
name: security-oauth2
profiles:
active: dev
cloud:
config:
hostname: localhost
port: 8888
username: bugwfq
password: bugwfq
name: ${spring.application.name}
profile: ${spring.profiles.active}
url: http://${spring.cloud.config.hostname}:${spring.cloud.config.port}
security:
user:
name: bugwfq
password: bugwfq
根据官方文档可以看出,client客户端拉取配置的时候需要配置下面这几个属性
如果不配置的话
name默认为application 尽量配置为${spring.application.name} 作为服务本身的配置
profile默认为default 尽量配置为${spring.profiles.active} 根据项目的环境而拉取对应的配置
label 默认为master
#这个是我们在数据库中配置application_name 呢个列,官方的列名为application
spring.cloud.config.name=
spring.cloud.config.profile=
spring.cloud.config.label=
数据库存入的数据
获取配置的代码
@Configuration
public class WebSecurityConfig {
@Value("${wang.fu.qiang}")
private String name;
启动类,和顺便查看获取到的值
如果有兴趣可以继续往下看
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
三、基于jdbc的Server配置中心剖析
从官方文档我们可以看出如果我们需要使用jdbc就可以直接将spring.profiles.active 这个属性的值配置为jdbc ,然后会自动加载JdbcEnvironmentRepository这个对象到容器中,呢们这个对象具体做了一件什么样的事情呢
首先他是在这里装配的
然后我们来解析一下两个参数
JdbcEnvironmentRespositoryFactory factory和JdbcEnvironmentProperties environmentProperties
首先看 factory
是在EnvironmentRepositoryConfiguration内部类这里被装配的,然后我们看看他里面的方法
public class JdbcEnvironmentRepositoryFactory implements EnvironmentRepositoryFactory<JdbcEnvironmentRepository,
JdbcEnvironmentProperties> {
private JdbcTemplate jdbc;
public JdbcEnvironmentRepositoryFactory(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
/**
可以看出在这里Factory 主要的作用就是用来构建JdbcEnvironmentRepository 对象,需要把装配的jdbc对象
与JdbcEnvironmentProperties 在构建时传入。
*/
@Override
public JdbcEnvironmentRepository build(JdbcEnvironmentProperties environmentProperties) {
return new JdbcEnvironmentRepository(jdbc, environmentProperties);
}
}
上面知道了JdbcEnvironmentRepositoryFactory 是在那里装配的并且在注释中说明了存在的意义,接下来我们看一下第二个参数
JdbcEnvironmentProperties
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
/**
* @author Dylan Roberts
* 可以看出该类是以配置类的方式而被装配的 下面再注释中讲解一下其中包含的字段
*/
@ConfigurationProperties("spring.cloud.config.server.jdbc")
public class JdbcEnvironmentProperties implements EnvironmentRepositoryProperties {
/*这个是默认sql 可以看出只要我们遵循这几个对应的字段去创建表就可以直接使用了,但是这种方式毕竟比较麻烦,而且再mysql 中如果使用默认sql的话会报错,因为key与value是关键字*/
private static final String DEFAULT_SQL = "SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?";
private int order = Ordered.LOWEST_PRECEDENCE - 10;
/** SQL used to query database for keys and values
这个字段可以再配置中配置对应的属性来自定义sql,默认为上面的sql语句
如果需要自定义的话就要配置
spring.cloud.config.server.jdbc.sql= sql语句
要注意的是APPLICATION=? and PROFILE=? and LABEL=?" 后面这几个?对应的字段顺序不能变 (不管命名为什么,其默认是按照这种方式去查的)
*/
private String sql = DEFAULT_SQL;
public int getOrder() {
return order;
}
@Override
public void setOrder(int order) {
this.order = order;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
}
可以看出 JdbcEnvironmentProperties被作为配置类来管理自定义sql 与order 这两个属性配置的,如果要使用自定义sql 或order只需要在配置文件中配置下面两个属性即可,而且sql 的配置中可以使用自定义表,但是其后面的参数顺序不能改变
spring.cloud.config.server.jdbc.order=
spring.cloud.config.server.jdbc.sql=SELECT `attribute`, `VALUE` from Config_Application WHERE APPLICATION_NAME=? AND PROFILE=? AND LABEL=?
当然也可以完全按照官网介绍的呢种去创建表之类的,都没有什么影响
下面我们来剖析一下JdbcEnvironmentRepository 类主要干了什么,我把代码的分析继续写到注解中
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.util.StringUtils;
/**
*从关系数据库中获取数据的{@link EnvironmentRepository}。的
*数据库应该有一个名为“PROPERTIES”的表,其中列有“APPLICATION”、“PROFILE”、
*“LABEL”(与通常的{@link Environment}含义相同),加上“KEY”和“VALUE”
*键和值对在{@link Properties}风格。属性值的行为方式相同
*如果它们来自名为Spring Boot的属性文件,就会这样
* <代码>{应用}-{概要}。属性</code>,包括所有加密和
*解密,将作为后处理步骤应用(即不在此存储库中)
*直接)。
*
* @作者戴夫·西尔
*/
public class JdbcEnvironmentRepository implements EnvironmentRepository, Ordered {
private int order;
// jdbcTemplate 封装了查询之类的方法
private final JdbcTemplate jdbc;
//用于查询的sql 可以自定义,本身的可能会执行错误
private String sql;
private final PropertiesResultSetExtractor extractor = new PropertiesResultSetExtractor();
public JdbcEnvironmentRepository(JdbcTemplate jdbc, JdbcEnvironmentProperties properties) {
/**在前面讲过的Factory中会传入两个参数,其中一个是装配的jdbc,一个是装备的属性类
属性类中的sql 就是我们在配置文件中配置的这个sql ,如果没有配置该属性呢么会使用properteis中的默认sql(这个源代码前面看过了已经)
*/
this.jdbc = jdbc;
this.order = properties.getOrder();
this.sql = properties.getSql();
}
public void setSql(String sql) {
this.sql = sql;
}
public String getSql() {
return this.sql;
}
@Override
public Environment findOne(String application, String profile, String label) {
String config = application;
//阮如的时候先判断label这个字段是否为空,如果为空则默认赋值为master
if (StringUtils.isEmpty(label)) {
label = "master";
}
//如果profile配置为空的话默认是default
if (StringUtils.isEmpty(profile)) {
profile = "default";
}
//默认会将profile为default的值的配置也获取出来
if (!profile.startsWith("default")) {
profile = "default," + profile;
}
/*将拼接后的profile转换为数组供下面循环查询配置查询
注意因为这里有切分,所以我们可以在该位置以逗号分隔的方式去传入参数,就会取出多个配置信息
如http://localhost:8888/cloud-client/dev,test/master
*/
String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
//创建基础的Environment对象
Environment environment = new Environment(application, profiles, label, null,
null);
//给config拼接一个application 我们可以把一些公共的配置放到application值为application的配置中
if (!config.startsWith("application")) {
config = "application," + config;
}
/**
这里会切分config 并且转为list,所以说我们在传application时可以以,号分割的方式来传值
如:http://localhost:8888/cloud-client,user-manager/dev,test/master
*/
List<String> applications = new ArrayList<String>(new LinkedHashSet<>(
Arrays.asList(StringUtils.commaDelimitedListToStringArray(config))));
//将profiles数组转为list
List<String> envs = new ArrayList<String>(new LinkedHashSet<>(Arrays.asList(profiles)));
//在上面排序后reverse
Collections.reverse(applications);
Collections.reverse(envs);
for (String app : applications) {
for (String env : envs) {
/*因为JdbcTemplate分装了query,所以我们只需要传入读取到的我们在配置设置的sql
然后就可以查出对应的值了
*/
Map<String, String> next = (Map<String, String>) jdbc.query(this.sql,
new Object[] { app, env, label }, this.extractor);
if (!next.isEmpty()) {//判断查出的值是否为空
//如果不为空则新建该add 该属性
environment.add(new PropertySource(app + "-" + env, next));
}
}
}
return environment;
}
@Override
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
}
class PropertiesResultSetExtractor implements ResultSetExtractor<Map<String, String>> {
@Override
public Map<String, String> extractData(ResultSet rs)
throws SQLException, DataAccessException {
Map<String, String> map = new LinkedHashMap<>();
while (rs.next()) {
map.put(rs.getString(1), rs.getString(2));
}
return map;
}
}
注意:
如果使用默认的sql 的话 因为mysql 中key value 为关键字,所以运行的话会报以下错误
四、几种获取配置的方式
比较全面的获取方式可以在以下类中查看,我只说下我在分析代码的时候发现的东西
我在分析代码时提到的几个地方
1.默认的profile (我们可以在某个application下设置几个default值,这样就算我们在profile中没有加他也会获取到该配置)
话不多说请看图
数据库中的几个值
2.默认的application 配置 (会把默认配置的 application 名为 application prefile 为default 和当前传入的prefile对应的值查出来看下面这个例子)
官方文档中也有说明不要再定义spring.application.name的时候使用保留的application-前缀
数据库数据
总结:
取值时:http://localhost:8888/{application}/{prefile}/master
application与prefile =可以传多个用,号分开
application 如果没有字段为 application开头的,可设置配置为默认
全局配置可以根据prefile来取
数据还是如上面的
推荐阅读
-
Spring Cloud Config 配置中心实践过程中,你需要了解这些细节!
-
跟我学SpringCloud | 第七篇:Spring Cloud Config 配置中心高可用和refresh
-
spring cloud config 配置中心快速实现过程解析
-
spring cloud 入门系列七:基于Git存储的分布式配置中心--Spring Cloud Config
-
Spring Cloud 之分布式配置中心--Config
-
SpringCloud学习笔记(7):使用Spring Cloud Config配置中心
-
使用Spring Cloud Config搭建配置中心
-
使用Spring Cloud Config搭建配置中心
-
Spring Boot(二)之搭建spring cloud config配置中心
-
SpringCloud实战(六)-高可用的分布式配置中心(Spring Cloud Config)