第三章 使用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
本节的讲述就到这里了。。。