第三章 使用SpringCloud 配置
微服務的配置一般會要求以下三點:
- 配置與代碼的分離
- 構建服務器、應用程序,在不同環境中保持一致
- 配置信息一般是通過環境變量在服務啓動時傳入,更多的情況下是從配置中心讀取
配置管理架構
當一個微服務實例啓動時,它將調用一個服務端點讀取其所在環境的配置信息,而實際的配置信息是在配置中心中,每當進行配置管理更改時,必須通知應用程序刷新其對應的配置。構建配置管理的解決方案有很多,如使用ETCD,其是k8s的配置選擇,還有Eureka,Zk等一些優秀的開源項目和中間件。作者選擇的是使用SpringCloud配置服務器,其實原因也很簡單: - 易於搭建和使用
- 與SpringBoot的緊密集成,註解即可解決
- 可以方便地與Eureka等集成
本節將會構建如下程序:
1、創建一個SpringCloud配置服務器,並使用文件和Git庫來提供應用程序配置數據
2、讓服務可以從數據庫中檢索數據
3、將配置服務器與微服務掛鉤,以提供配置數據
在原來項目的基礎上,引入一個confsvr的子工程,其目錄結構與構建腳本如下:
可以發現,其主要多了兩個和配置相關的依賴。關於配置相關的信息可以參考如下圖
我們要清楚的一點時,我們一直強調,服務與配置分離,但是我們的配置服務其實也是一個服務,它也將在環境中運行,也可以通過暴露HTTP端點訪問這些信息。
下面再看下引導類,引導類的代碼如下,不做過多解釋
@SpringBootApplication
@EnableConfigServer//使服務成爲Spring Cloud Config服務
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
好了 ,準備工作已經完成,那麼啓動配置服務類,然後我們通過HTTP請求的方式獲取配置信息。
需要注意的是,如果在默認的配置文件(xxx.yml)中定義一個屬性,並且不在任何其他環境(如xxx-dev.xml或者xxx-pro.yml)中定義,那麼spring框架將使用它。
下面將要做的就是啓動我們的一個微服務,讓其從配置服務器拉取對應的配置,當我們的服務啓動時,它將傳遞兩條信息:一個是Spring的profile,其用於確定要讀取哪個環境的配置信息,一個是與配置服務器通信暴露的端點信息。大概如下圖所示:
其實想從配置服務器拿配置數據並不複雜,只需要添加對應的本地配置即可,我們一般將本地配置信息寫到bootstrap.yml和application.yml中,bootstramp.yml一般配置的是服務的應用程序名稱、應用程序要讀取的對應的profile信息和連接到配置服務器的url,如下所示
spring:
application:
#配置服務要查找的服務名稱,也就是該服務在配置服務器的身份信息
name: licensingservice
profiles:
#環境信息
active:
default
cloud:
#調用配置服務的服務端點
config:
uri: http://localhost:8888
我們需要注意的,spring.application.name是應用程序的名稱,要想生效,配置服務需要有對應的目錄,也就是得有 licensingservice這樣一個目錄,我們發現,配置服務器已經有這樣一個目錄了。
下面我們進行一個聯調,首先啓動配置服務器,然後啓動我們的許可證服務,可以看下許可證服務啓動時的啓動日誌
可以看到,它會建立到配置服務的鏈接,那麼我們通過客戶端進行訪問,看下正在運行的環境,結果如下:
結果雖然沒有截完,但是結果顯而易見,我們運行的是默認環境,許可證服務監聽的端口是8080 ,還有一些其他信息。
。
我們現在可以拿到正確的配置信息了,那麼下面利用這些配置信息鏈接下數據庫試下吧,作者使用的是postgres,這個數據庫筆者確實沒有使用過,我想其他公司使用的也不多,我們還是走尋常路線,修改下配置信息,來連下mysql數據庫。
本節使用的持久層框架是JPA,先看下整體包及類結構
不做過多解釋,從名字上就可以看出是幹什麼的。先看下模型的代碼如下:
package com.thoughtmechanix.licenses.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
//表示將這個POJO映射到保存數據的對象
@Entity
//映射到哪張表
@Table(name = "licenses")
public class License{
//自增主鍵
@Id
//映射的表字段名稱
@Column(name = "license_id", nullable = false)
private String licenseId;
@Column(name = "organization_id", nullable = false)
private String organizationId;
@Column(name = "product_name", nullable = false)
private String productName;
@Column(name = "license_type", nullable = false)
private String licenseType;
@Column(name = "license_max", nullable = false)
private Integer licenseMax;
@Column(name = "license_allocated", nullable = false)
private Integer licenseAllocated;
@Column(name="comment")
private String comment;
public Integer getLicenseMax() {
return licenseMax;
}
public void setLicenseMax(Integer licenseMax) {
this.licenseMax = licenseMax;
}
public Integer getLicenseAllocated() {
return licenseAllocated;
}
public void setLicenseAllocated(Integer licenseAllocated) {
this.licenseAllocated = licenseAllocated;
}
public String getLicenseId() {
return licenseId;
}
public void setLicenseId(String licenseId) {
this.licenseId = licenseId;
}
public String getOrganizationId() {
return organizationId;
}
public void setOrganizationId(String organizationId) {
this.organizationId = organizationId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getLicenseType() {
return licenseType;
}
public void setLicenseType(String licenseType) {
this.licenseType = licenseType;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public License withId(String id){
this.setLicenseId(id);
return this;
}
public License withOrganizationId(String organizationId){
this.setOrganizationId(organizationId);
return this;
}
public License withProductName(String productName){
this.setProductName(productName);
return this;
}
public License withLicenseType(String licenseType){
this.setLicenseType(licenseType);
return this;
}
public License withLicenseMax(Integer licenseMax){
this.setLicenseMax(licenseMax);
return this;
}
public License withLicenseAllocated(Integer licenseAllocated){
this.setLicenseAllocated(licenseAllocated);
return this;
}
public License withComment(String comment){
this.setComment(comment);
return this;
}
}
爲了使用Mysql,我們需要做一點小小的整改,首先,在pom中加入mysql的依賴:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
其次,將我們配置服務器的數據庫連接信息修改如下:
example.property: "I AM IN THE DEFAULT"
spring.datasource.testWhileIdle: "true"
spring.datasource.validationQuery: "SELECT 1"
spring.jpa.properties.hibernate.dialect: "org.hibernate.dialect.PostgreSQLDialect"
redis.server: "redis"
redis.port: "6379"
signing.key: "345345fsdfsf5345"
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/test
driver-class-name: com.mysql.jdbc.Driver
我們就做這些修改就可以, 這裏把對應的SQL腳本貼出來:
DROP TABLE IF EXISTS licenses;
CREATE TABLE licenses (
license_id VARCHAR(100) PRIMARY KEY NOT NULL,
organization_id TEXT NOT NULL,
license_type TEXT NOT NULL,
product_name TEXT NOT NULL,
license_max INT NOT NULL,
license_allocated INT,
comment VARCHAR(100));
INSERT INTO licenses (license_id, organization_id, license_type, product_name, license_max, license_allocated)
VALUES ('f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a', 'e254f8c-c442-4ebe-a82a-e2fc1d1ff78a', 'user','customer-crm-co', 100,5);
INSERT INTO licenses (license_id, organization_id, license_type, product_name, license_max, license_allocated)
VALUES ('t9876f8c-c338-4abc-zf6a-ttt1', 'e254f8c-c442-4ebe-a82a-e2fc1d1ff78a', 'user','suitability-plus', 200,189);
INSERT INTO licenses (license_id, organization_id, license_type, product_name, license_max, license_allocated)
VALUES ('38777179-7094-4200-9d61-edb101c6ea84', '442adb6e-fa58-47f3-9ca2-ed1fecdfe86c', 'user','HR-PowerSuite', 100,4);
INSERT INTO licenses (license_id, organization_id, license_type, product_name, license_max, license_allocated)
VALUES ('08dbe05-606e-4dad-9d33-90ef10e334f9', '442adb6e-fa58-47f3-9ca2-ed1fecdfe86c', 'core-prod','WildCat Application Gateway', 16,16);
下面我們重新啓動配置服務器和許可證服務,然後用postman調用其中一個暴露的端點:
就是如此的神奇,Spring Data 會自動地將從配置服務器讀取的數據庫鏈接信息注入數據庫連接對象中,但所有其他屬性必須使用@Value註解來進行注入,像示例中的comment屬性,@Value註解從配置服務器提取example.property ,並將其注入該屬性中。
@Component
public class ServiceConfig{
@Value("${example.property}")
private String exampleProperty;
public String getExampleProperty(){
return exampleProperty;
}
}
example.property: "I AM IN THE DEFAULT"
spring.datasource.testWhileIdle: "true"
spring.datasource.validationQuery: "SELECT 1"
spring.jpa.properties.hibernate.dialect: "org.hibernate.dialect.PostgreSQLDialect"
redis.server: "redis"
redis.port: "6379"
signing.key: "345345fsdfsf5345"
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/test
driver-class-name: com.mysql.jdbc.Driver
本節的講述就到這裏了。。。