《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 [email protected]
 */
@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來取

 數據還是如上面的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章