因不同的项目有不同的配置文件,且有不同环境的配置,各自保存在本地在开发测试以及提交代码的时候会有很多冲突,还有就是一些生产环境或者公司保密的配置信息不想暴漏在开发服务中,那么我们这个时候就可以用到springcloud的统一配置服务中心configserver组件,该服务也可以启动多实例以实现高可用性。
接上篇:https://blog.csdn.net/huiyunfei/article/details/88847774修改,我们需要实现的功能是每个服务保留自己本地的多环境配置文件(比如开发过程中需要定义的配置依然可以丢在本地配置文件),同时提取一些核心配置(比如说服务启动的端口,服务名等等,本测试直接提取了数据库的配置)到配置中心(使用git服务器),服务中心也支持多环境配置。
1:git添加order、point两个服务的配置文件,目录及文件名如下:
目录cloud-config下为每个项目建立了一个独立的文件夹,每个文件夹下有dev、pro两个环境的配置文件。
命名规则常规的使用有以下两种:
/{name}-{profiles}.yml
/{label}/{name}-{profiles}.yml
name : 文件名,一般以服务名来命名
profiles : 一般作为环境标识
lable : 分支(branch),指定访问某分支下的配置文件
有一点值得注意的是,如果有两个前缀名相同文件,例如一个order.yml,一个order-dev.yml。那么在访问相同前缀的文件时,config-server会对这两个文件进行一个合并。例如order.yml有一段配置是order-dev.yml没有的,理应访问order-dev.yml的时候是没有那段配置的,但访问的结果却是它俩合并之后的内容,即order-dev.yml会拥有order.yml里所配置的内容。
order-server-dev.yml配置文件内容就是前边提到的数据库信息:
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
username: root
password:
driverClassName: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
2:提供config-server服务
pom:
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>config-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>config-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类:
package com.example.configserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
配置文件:
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
label: master #这里不写默认是master
server:
git:
uri: https://github.com/huiyunfei/spring-cloud
search-paths: /cloud-config/** #这里注意我的路径,之前就是这里有问题导致读取到的配置文件一直不是我提供的!!!
username:
password:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
3:point服务提供本地多环境配置以及添加config-client实现读取配置中心配置,结构如下:
这样我们一个服务一个环境就相当于有了三个配置文件。配置中心的order-server-xxx.yml,bootstrap.yml,applicatoin-xxx.yml。
修改application.yml名为bootstrap.yml(注:之所以要用bootstrap.yml,是因为启动SpringBoot项目时,会优先读取bootstrap.yml里的配置,然后才会读取application.yml。如果不通过bootstrap.yml里的配置,先从配置中心拉下相应的配置文件,就会报错。)
按我个人的理解profile的配置是丢在bootstrap中的,eureka的信息也应该保存在bootstrap.yml中,因项目启动的时候会优先读取bootstrap.yml,按照配置的内容去配置中心拉取配置文件,但是在此之前服务需要先去注册中心上找配置中心的调用地址,如果eureka-server不是默认端口更改了的话,就会访问不到配置中心,自然也就无法调用配置中心拉取配置文件了(但是我实际测试可以正常启动注册eureka,不知道是不是我理解有问题)。
bootstrap.yml:
server:
port: 8882
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#启动前加载的配置
spring:
profiles:
active: dev #指定使用哪个环境的配置文件
application:
name: point-server
application-dev.yml:
spring:
cloud:
config:
discovery:
enabled: true #使用config-server配置中心
serviceId: config-server #服务名
profile: dev
label: master #(如果线上使用的不是master而是其他分支,这里一定要加上配置。我自己测试的时候第一次使用dev,且已经在config-server里边设置过读取dev,但是实际测试这里如果不设置的话config-server读取的还是master,不知道是不是个bug,后来为了避免这种“玄学”我就把配置文件都丢在了master分支)
# datasource:
# url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
# username: root
# password:
# driverClassName: com.mysql.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# initialSize: 5
# minIdle: 5
# maxActive: 20
# maxWait: 60000
# timeBetweenEvictionRunsMillis: 60000
# minEvictableIdleTimeMillis: 300000
# validationQuery: SELECT 1 FROM DUAL
# testWhileIdle: true
# testOnBorrow: false
# testOnReturn: false
# poolPreparedStatements: true
# maxPoolPreparedStatementPerConnectionSize: 20
# filters: stat,wall
# connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#开启输出sql日志
logging:
level:
com:
example:
point:
dao: DEBUG
local:
profile: localDev #这里只是为了测试本地的配置文件还生不生效所以加了个注解在代码里取一下
pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.20.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>point</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>point</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.SR5</spring-cloud.version>
<mybatis-spring-boot>1.3.0</mybatis-spring-boot>
<mysql-connector>5.1.39</mysql-connector>
<access.ver>1.0.1</access.ver>
<fastjson.version>1.2.47</fastjson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector}</version>
</dependency>
<!-- SpringBoot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot}</version>
</dependency>
<!-- lombok依赖 可以减少大量的模块代码-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--Slf4j 依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<!-- logback 依赖 是slf4j的实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- access 接口直接使用resultObj对象-->
<dependency>
<groupId>com.bjj</groupId>
<artifactId>access</artifactId>
<version>${access.ver}</version>
</dependency>
<!-- Druid数据库连接池组件 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>point-share</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
controller:
package com.example.point.web;
import com.example.pointshare.feign.PointService;
import com.example.pointshare.model.Point;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
/**
* Created by hui.yunfei@qq.com on 2019/5/14
*/
@RestController
@RequestMapping("/point")
@Slf4j
public class PointController {
@Value("${server.port}")
String point;
@Value("${local.profile}")//这里就是测试本地application中的配置生不生效,测试通过
String localProfile;
@Autowired
private PointService pointService;
@RequestMapping(value="/sayHi",method= RequestMethod.GET)
public void sayHi(@RequestParam(value="name") String name){
log.info("point sayHi in name: {},point: {},localProfile: {}",name,point,localProfile);
pointService.sayHi(name);
}
@RequestMapping(value="/findById/{id}",method= RequestMethod.GET)
public Point findById(@PathVariable(value="id") String id){
log.info("point findById in:{} ",id);
Point point=pointService.findById(id);
return point;
}
@RequestMapping(value="/findByIdAndName",method= RequestMethod.POST)
public Point findByIdAndName(@RequestParam(value="id") String id,@RequestParam(value="name") String name){
log.info("point findByIdAndName in:{},{} ",id,name);
Point point=pointService.findByIdAndName(id,name);
return point;
}
@RequestMapping(value="/update",method= RequestMethod.POST)
public void update(@RequestBody Point point){
log.info("point update in:{}",point);
pointService.update(point);
}
}
order服务配置一样。省略。。。
测试结果:
order、point服务可以正常启动访问到数据库(证明我们的配置中心服务生效),point服务的sayHi正常打印出:
point sayHi in name: yunfei,point: 8882,localProfile: localDev,证明本地配置文件配置也生效。
配置消息总线Spring Cloud Bus,目的是为了在git上修改完配置后,不需要去重启服务实例而达到读取修改后的结构。这个过程需要依赖rabbitmq。安装过程省略。
1:point配置文件application修改添加配置:
spring:
cloud:
config:
discovery:
enabled: true
serviceId: config-server
profile: dev
label: master #(我已经在config-server里边设置过读取dev,但是实际测试这里如果不设置的话config-server读取的还是master,不知道是不是个bug)
#添加rabbitmq配置
rabbitmq:
host: localhost
port: 5672
username: root
password: root
#不加访问刷新服务/bus/refresh会报401
management:
security:
enabled: false
#开启输出sql日志
logging:
level:
com:
example:
point:
dao: DEBUG
local:
profile: localDev
2:pom添加
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
3:启动服务8002端口,修改bootstrap配置文件端口为8003继续启动。idea2019的启动多实例服务是在edit Configuration中勾选Allow parallel run选项。
4:修改git配置文件server:profile值,然后在controller中添加${server.profile}读取,对应配置类(此示例是controller)上添加@RefreshScope注解(不加的话刷新服务后台有收到,但是刷新不起作用)
5:post访问刷新地址 http://localhost:8882/bus/refresh
然后8882、8883服务都读取到了修改后的配置。
代码在:
https://github.com/huiyunfei/spring-cloud,后续添加其他中间件可能会修改代码,以本文档记录为准