SpringCloud
注意
本文档是基于 SpringCloud Finchley 版本编写的,发布日期是 2018 年 6 月,现已停更,笔者学习那会儿还是个穷学生呢!
在之后发布的 Greenwich、Hoxton 版本也都停更,并大量弃用 Netflix 组件(包括 Hystrix、Ribbon 和 Zuul)。目前发布的版本以年份作为系列,比如版本 2023.0.xx 表示在 2023 年发布,新版本提供了一系列组件替代原方案,且基本是 Spring 团队自研,使他们能够被长久支持。
若需使用最新版,参考官网是最佳选择!
注册中心-Eureka
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency><!-- 与服务端依赖一致 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>application.yml
spring:
application:
name: hello-spring-cloud-eureka
server:
port: 8761
eureka:
instance:
#Eureka服务器的地址
hostname: localhost
client:
# 是否将自己注册到Eureka,集群时开启
registerWithEureka: false
# 是否从Eureka获取注册信息,集群时开启
fetchRegistry: false
serviceUrl:
#配置访问Eureka服务器的地址,若是集群,每个url用英文逗号隔开
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/eureka:
client:
serviceUrl:
#若是集群,每个url用引文逗号隔开
defaultZone: http://localhost:8761/eureka/启动类
@SpringBootApplication
@EnableEurekaServer //开启服务端
public class EurekaApplication {
...
}@SpringBootApplication
//开启客户端,也可使用 @EnableDiscoveryClient 注解,适用于多种服务注册中心(例如 Eureka、Consul、ZooKeeper 等)
@EnableEurekaClient
public class EurekaClientApplication {
...
}链路追踪-ZipKin
pom.xml
<!-- springCloud没有帮我们托管zipkin版本,需要在父工程中添加版本管理-->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency><dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>application.yml
# 该端口为ZipKin默认端口
server:
port: 9411spring:
# 链路追踪地址
zipkin:
base-url: http://localhost:9411启动类
@EnableZipkinServer
public class ZipKinApplication {
...
}// 启动类无需添加额外注解,在配置中指定zipkin服务端地址即可健康检查-Admin
pom.xml
<!--版本号需交由父工程管理,jolokia由springCloud自动管理-->
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency><dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>application.yml
# 健康检查配置,服务端和客户端可以一样
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health,infomanagement:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health,info启动类
@EnableAdminServer
public class AdminApplication {
...
}@EnableDiscoveryClient // 注册服务到注册中心
public class AdminClientApplication {
...
}服务提供者
application.yml
spring:
application:
name: hello-spring-cloud-provider
server:
port: 8764
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/启动类
@RestController
@SpringBootApplication
@EnableEurekaClient
public class ServiceProviderApplication {
@Value("${server.port}")
private String port;
@GetMapping("hi")
public String sayHi(@RequestParam(value = "message") String message) {
return String.format("message is : %s---port : %s", message, port);
}
...
}服务消费者
熔断器-hystrix
服务雪崩:因服务提供者不可用导致服务调用者不可用,并将不可用逐渐放大的过程。
解决方案:可使用 hystrix 来解决服务雪崩问题,当服务调用失败返回友好信息,以缓解对其它服务的影响;
如何使用: 与服务消费者配合使用,详见 ribbon 和 openfeign 开启熔断;
ribbon
ribbon 需要自己构建 http 请求,使用 RestTemplate 发送请求调用其他服务,步骤繁琐,实际开发并不常用(通常使用 openfeign,见下节)。
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--在ribbon中使用hystrix熔断器防止服务雪崩-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>application.yml
spring:
application:
name: hello-spring-cloud-ribbon
server:
port: 8764
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/业务代码
service
@Service public class RibbonService { @Autowired //注入java配置中申明的bean private RestTemplate restTemplate; //通过RestTemplate与Eureka联系 @HystrixCommand(fallbackMethod = "sayError") //使用服务熔断 public String sayHi(String message){ //调用服务提供方 return restTemplate.getForObject("http://hello-spring-cloud-provider/hi?message="+message,String.class); } public String sayError(String message){ return String.format("sayHi方法添加了熔断器注解,报错我就被触发!",message); } }controller
@RestController public class RibbonController { @Autowired private RibbonService ribbonService; @RequestMapping(value = "hi",method = RequestMethod.GET) public String sayHi(@RequestParam String message){ return ribbonService.sayHi(message); } }启动类
@EnableEurekaClient @EnableHystrix //开启熔断器 @SpringBootApplication @EnableDiscoveryClient // 注册服务到注册中心 public class RibbonApplication { @Bean @LoadBalanced //使用负载均衡 public RestTemplate restTemplate(){ return new RestTemplate(); } ... }
openfeign
openfeign 默认集成了 Ribbon,是一个声明式的伪 http 客户端,只需要创建一个接口即可实现远程调用;openfeign 自带熔断器,在 yml 文件中开启即可;
OpenFeign 默认使用 Ribbon 作为其负载均衡的实现方式,可以使用 @LoadBalanced 注解来标记 RestTemplate 或者 WebClient 以启用负载均衡。
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--使用熔断器仪表盘监控熔断情况,feign和ribbon的使用方式一样,这里只以feign为例-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>application.yml
spring:
application:
name: hello-spring-cloud-feign
server:
port: 8765
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
# Openfeign 在 Spring Cloud 2020.0 之前的版本自带熔断器,只需打开熔断器配置即可,无需添加依赖
# 在之后的版本中已经移除,需自行添加 Hystrix依赖。也可以集成其他熔断器
feign:
hystrix:
enabled: true业务代码
service
// 传入服务提供者服务名,并传入熔断处理的类 // 熔断器使用和配置详见下节 -- 熔断器代码 @FeignClient(value = "hello-spring-cloud-provider",fallback = MyFeignHystrix.class) public interface MyOpenFeignService { // 注意方法名、参数等需和服务提供者一致 @GetMapping("hi") String sayHi(@RequestParam(value = "message") String message); }controller
@RestController public class OpenFeignController { @Autowired private MyOpenFeignService service; @RequestMapping(value = "hi",method = RequestMethod.GET) public String sayHi(@RequestParam(value = "message") String message){ return service.sayHi(message); } }启动类
@SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient // 注册服务到注册中心 @EnableFeignClients //启用feign客户端 (服务调用者) // 无需添加 @EnableHystrix 依赖,因为openfeign 自动集成了Hystrix,并在上面的配置文件中启用了它 @EnableHystrixDashboard //开启服务熔断仪表盘,监控熔断情况 public class OpenFeignApplication { // 启用负载均衡 @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } ... }熔断器代码
注意在 service 中有
@FeignClient(...fallback = MyFeignHystrix.class)注解,为该业务指定熔断处理类MyFeignHystrix.class,如下:@Component public class MyFeignHystrix implements MyOpenFeignService { @Override public String sayHi(String message) { return String.format("业务类中sayHi方法添加了熔断器注 解,报错我就被触发!",message); } }使用
hystrix-dashboard可以方便的在浏览器上查看熔断情况,配置类如下:/** * 使用方式: * 1. 浏览器访问 localhost:8765/hystrix * 2. 输入监听地址 localhost:8765/hystrix.stream,点击监听即可 */ @Configuration public class HystrixDashboardConfiguration { @Bean public ServletRegistrationBean getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); //新建一个servlet ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); //在springboot中用 ServletRegistrationBean这个类来注册一个servlet registrationBean.setLoadOnStartup(1); // 指定监视地址 registrationBean.addUrlMappings("/hystrix.stream"); //servlet访问路径,用于熔断检测 registrationBean.setName("HystrixMetricsStreamServlet"); //servlet名称 return registrationBean; } }
api 网关-Gateway
Spring Cloud Gateway相当于整个微服务系统中的入口程序,它通常作为单独的一个微服务运行,且不包括任何业务相关代码。它提供一种简单有效的方法来路由到 API,并为它们提供安全性、监控/指标和弹性功能。
一般而言,用户端不会直接访问网关(可能存在多个网关),而是先访问 nginx,通过 nginx 负载均衡到网关,再由网关将请求转发(路由)到对应服务。
在 Spring Cloud Gateway 中,主要概念有路由(Route)、谓词(Predicate)和过滤器(Filter),三者配合实现服务的定位。
配置谓词
谓词(Predicate)用于匹配正确的 HTTP 请求,指定的访问规则必须全部匹配才能放行通过。
内置谓词规则
Gateway 内置了多种访问规则,如下列举了几种常见的:
application.ymlspring: cloud: gateway: routes: - id: hello-spring-cloud-provider # 路由id必须唯一,建议使用服务名 uri: lb://hello-spring-cloud-provider # 负载均衡到服务提供者 predicates: # 配置请求路径 # 访问网关 http://xxx-gateway/echo/pro/** # 等效于访问 http://xxx-provider/echo/pro/** - Path=/echo/pro/** # 限制访问源-域名 - Host=**.spring-cloud.cn,localhost # 限制访问源-ip( 例如 192.168.1.10 就能访问) - RemoteAddr=192.168.1.1/24 # 设置 Header 名称和值,值为正则表达式(为整数),符合则通过 - Header=Token-app, \d+ # 设置某个时间点之后才能访问 - After=2024-12-12T12:00+08:00[Asia/Shanghai] # 设置 Cookie 名称和值,符合则通过 - Cookie=test-cookie, zjx - id: other-provider uri: lb://other-provider predicates: - Path=/other/test/**pom.xml<!-- 引入 gateway 服务网关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--负载均衡,gateway中必须加上这个才能实现用服务名通信,否则只能用ip --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>自定义谓词
若 Gateway 内置的谓词不能满足业务需要,可自定义谓词。参考 After 谓词对应的类
AfterRoutePredicateFactory(因为它足够简单),很容易模仿一个自己的路由谓词规则。配置类/** * 自定义 Gateway 谓词--配置会员才能访问 * 注意类的命名规则为: 名称 + RoutePredicateFactory */ @Slf4j @Component public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> { public MyRoutePredicateFactory() { super(MyRoutePredicateFactory.Config.class); } // 具体放行逻辑 @Override public Predicate<ServerWebExchange> apply(Config config) { // 若配置文件中指定 -My=BIG_VIP,则 http://xxx?tagParam=BIG_VIP才能通过 return serverWebExchange -> { String tagParam = serverWebExchange.getRequest() .getQueryParams().getFirst("tagParam"); if (tagParam == null) { log.info("非常抱歉,你没有告诉我你是不是VIP,走开!!!"); return false; } boolean isVip = tagParam.equals(config.getTag()); if (!isVip) log.info("你不是VIP,走开!!!"); return isVip; }; } @Data public static class Config { private String tag; } /** * ========= 使配置支持简写 ========= * yml简写形式: * predicates: * -My=BIG_VIP * 非简写形式: * predicates: * - name: My * args: * tag: BIG_VIP */ @Override public List<String> shortcutFieldOrder() { return Collections.singletonList("tag"); } }application.ymlspring: cloud: gateway: routes: - id: hello-spring-cloud-provider # 路由id必须唯一,建议使用服务名 uri: lb://hello-spring-cloud-provider # 负载均衡到服务提供者 predicates: - Path=/echo/pro/** # 设置某个时间点之后才能访问 - After=2024-12-12T12:00+08:00[Asia/Shanghai] # 使用自定义谓词 - My=BIG_VIP
配置过滤器
过滤器(filters)能实现请求鉴权、异常处理、接口调用时长统计等功能,可以在发送下游请求之前或之后修改请求和响应。
内置过滤器
Gateway 提供了很多种 内置过滤器,官网列举了几十种之多,其中的几个示例如下:
spring: application: name: hello-spring-cloud-provider cloud: gateway: routes: - id: hello-spring-cloud-provider # 路由id必须唯一,建议使用服务名 uri: lb://hello-spring-cloud-provider # 负载均衡到服务提供者 predicates: - Path=/echo/pro/** filters: # 添加路径前缀 # 前端访问 http://xxx.com/echo/pro,网关实际访问http://xxx.com/my-app/echo/pro - PrefixPath=/my-app # 为请求添加一个请求头 X-Test:good - AddRequestHeader=X-Test, good # 为请求添加请求参数 - AddRequestParameter=red, blue - RemoveRequestHeader=X-Request-Foo # 为请求添加响应头 - AddResponseHeader=X-Response-Red, Blue自定义内置过滤器
和自定义谓词类似,我们可以参考一个足够简单的过滤器源码,如
AddRequestHeaderGatewayFilterFactory,仿照它实现一个自己的过滤器。配置类/** * 自定义 Gateway 过滤器--配置年龄 18+ 的男性 才能访问 * 注意类的命名规则为: 名称 + GatewayFilterFactory */ @Slf4j @Component public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> { public MyGatewayFilterFactory() { super(MyGatewayFilterFactory.Config.class); } @Override public GatewayFilter apply(MyGatewayFilterFactory.Config config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); // 检查请求参数 【注意,request.getBody()只能被调用一次,不太适合在网关层操作请求体】 Integer age = Integer.valueOf(request.getQueryParams().getFirst("age")); String sex = request.getQueryParams().getFirst("sex"); // 放行 if ( age >= config.getAge() && config.getSex().equals(sex)) { return chain.filter(exchange); } // 不放行 log.info("未满18岁或非男性,不适合访问!"); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); }; } @Data public static class Config { private Integer age; private String sex; } /** * ========= 使配置支持简写 ========= * yml简写形式: * filters: * - My=18,男性 * 非简写形式: * filters: * - name: My * args: * age: 18 * sex: 男性 */ @Override public List<String> shortcutFieldOrder() { return Arrays.asList("age","sex"); } }application.yml# 示例配置 spring: cloud: gateway: routes: filters: # 为请求添加一个请求头 X-Test:good - AddRequestHeader=X-Test, good # 自定义过滤器【年龄18+ 且 为男性】 - My=18,男性自定义全局过滤器
全局过滤器作用于所有请求,可用于鉴权、处理 404 等操作。如下为一个简单的示例,目地是:统计整个系统中接口的处理耗时。
@Component @Slf4j public class MyCustomGlobalFilter implements GlobalFilter, Ordered { public static final String BEGIN_TIME = "beginTime"; // 自定义过滤器逻辑 @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // ServerWebExchange 存放着请求和响应信息,如下示例 // ServerHttpRequest request = exchange.getRequest(); // ServerHttpResponse response = exchange.getResponse(); // 设置开始处理请求时间 exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis()); // 放行,执行下一步,开启统计线程 return chain.filter(exchange).then(Mono.fromRunnable(() -> { // url 不存在,给前端提示一下 if (exchange.getResponse().getStatusCode() == HttpStatus.NOT_FOUND) throw new RuntimeException("您访问的URL不存在!"); // 这里面写请求处理后的逻辑 URI uri = exchange.getRequest().getURI(); Optional.ofNullable((Long) exchange.getAttribute(BEGIN_TIME)) .ifPresent(startTime -> { // 计算耗时 log.info("访问主机:{} 端口:{} url:{} 参数:{}", uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery()); log.info("访问时长:{}毫秒", System.currentTimeMillis() - startTime); });})); } // 过滤器排序,越小优先级越高 @Override public int getOrder() { return -1; } }
api 网关-Zuul
注意
Zuul 网关已经停止维护,推荐使用 Gateway 作为替代品,部分老项目会使用 Zuul,以下内容可参考学习。
Zuul 的主要功能是路由转发和过滤器。路由功能可在.yml 配置文件中设置, 继承 ZuulFilter 类即可自定义过滤器;
由于网络或性能等原因可能会导致路由失败,可以定义一个类,这个类要实现 spring 提供的 FallbackProvider 接口,当路由失败时会触发该类里面的回调方法;
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>启动类
@SpringBootApplication
@EnableEurekaClient //开启Eureka客户端
@EnableZuulProxy //开启Zuul路由
public class ZuulApplication {
...
}路由转发
application.yml
server:
port: 8769
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
zuul:
#路由,固定写法
routes:
# 根据不同路径调用不同服务
# 示例:localhost:8769/api/a/hi?message=god
my-api-a:
path: /api/a/**
serviceId: hello-spring-cloud-ribbon
my-api-b:
path: /api/b/**
serviceId: hello-spring-cloud-feign过滤器
@Slf4j
@Component
public class LoginFilter extends ZuulFilter {
// pre:路由之前;routing:路由之时;post:路由之后;error:发送错误调用
@Override
public String filterType() { return "pre"; }
//路由顺序
@Override
public int filterOrder() { return 0; }
// //是否需要过滤
@Override
public boolean shouldFilter() { return true; }
// 模拟具体业务代码
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String token = request.getParameter("token");
if (token == null) {
context.setSendZuulResponse(false); //则不发送Zuul的响应
context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); //401-未授权
try {
HttpServletResponse response=context.getResponse();
response.setContentType("text/html;charset=utf-8");
context.getResponse().getWriter().write("无Token!");
} catch (IOException e) {}
} else { logger.info("OK"); }
return null;
}
}路由失败回调
实际需为每个所调用的服务设置失败回调,此处以 Feign 为例
@Component
public class FeignFallbackProvider implements FallbackProvider {
//需要被处理的服务
@Override
public String getRoute() { return "hello-spring-cloud-ribbon"; }
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse(){
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {}
@Override
public InputStream getBody() throws IOException { //错误相应内容
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("message", "无法连接,请检查您的网络");
return new ByteArrayInputStream(objectMapper.writeValueAsString(map).getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
};
}
}配置中心-Config
Spring Cloud Config 提供了多种存储配置信息的方式,常见包括本地文件系统、Git 仓库、数据库存储等;这里以 Git 仓库示例;
Spring Cloud Config 客户端配置的优先级大于服务端,若和服务端冲突,本地配置会覆盖服务端配置;若没有冲突,客户端和服务端配置会同时生效;
提示
Config 服务端最好使用 bootstrap.yml 作为配置文件,以免有些配置(修改端口、加密配置等)无法生效!
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency><!-- config客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>bootstrap.yml
# 8888端口为默认端口,在application.yml中修改不会生效;
# 若想修改端口,需用bootstrap.yml配置文件配置
server:
port: 8888
spring:
application:
name: hello-spring-cloud-config
# profiles:
# active: native # 指定从类路径或文件系统中检索或读取配置文件
cloud:
config:
label: master #表示服务端仓库
server:
# 本地文件仓库
# native:
# search-locations: file:///D:/test/
# git仓库
git:
uri: https://github.com/zjx-admin/cloudConfig.git #GitHub仓库地址
search-paths:
- configsLocation # 在子目录中存储配置文件,可以是多个目录,
timeout: 5 # 超时时间,默认5秒
username: xxx # gitHub账号密码
password: xxx
deleteUntrackedBranches: true # 强制删除本地仓库中未跟踪的分支,避免远程分支被删除,但其本地副本仍可用于获取spring:
application:
name: config-client
profiles:
active: dev
cloud:
config:
uri: http://localhost:8888 # config服务端地址
name: config-client # 配置文件名称的前缀;若与应用程序名称一致,可忽略本配置
label: master # 仓库的分支
profile: devGithub 仓库客户端配置configsLocation/config-client-dev.yml示例
server:
port: 8765
say: hello启动类
@EnableConfigServer //开启配置中心服务端
@SpringBootApplication
public class ConfigApplication {
...
}
// 浏览器访问 http://localhost:8888/config-client/dev 可查看配置信息// 无需添加额外注解,在配置文件中指定config服务端url即可
// 测试是否成功加载远程配置
@SpringBootApplication
public class ConfigClientApplication {
@Value("${say}")
private String say;
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
System.out.println("读取远程配置成功 " + say);
}
}刷新配置
Spring Boot 应用程序只会在启动时读取它们的属性,如何在属性变化时动态刷新应用程序呢,Spring Boot Actuator 提供了一个@RefreshScope 注解,允许开发团队访问/refresh 端点,这会强制 Spring Boot 应用程序重新读取其应用程序配置,配置如下:
// 注意,数据库配置等不会被@RefreshScope注解重新加载。
@SpringBootApplication
@RefreshScope
public class ConfigClientApplication {
public static void main(String[] args) {
...
}
}其他刷新配置方法参考:
- Spring Cloud Bus 基于推送的机制,使 Spring Cloud Config 服务器端能够向所有使用服务的客户端发布哪里的配置发生了更改的消息。Spring Cloud Bus 需要一个额外的在运行的中间件:RabbitMQ。
- 编写一个简单的脚本来查询服务发现引擎以查找服务的所有实例,并直接调用/refresh 端点。
- 若服务使用 docker 部署,重新启动所有服务器或容器来接收新的属性;
保护敏感配置信息
Spring Cloud Config 支持使用对称加密(共享秘密)密钥和非对称加密密钥(公钥/私钥)。非对称加密比对称加密更安全,因为它使用更现代和更复杂的算法;以下示例以对称加密为例
设置密钥
对称加密密钥只不过是加密器用来加密值和解密器用来解密值的共享秘密。对称密钥的长度应该是 12 个或更多个字符,最好是一个随机的字符集。
# config server server: port: 8888 encrypt: key: secretkey # 告诉Config服务器端使用这个值作为对称密钥,生产环境可使用简要密钥配置加密属性
一旦密钥被设置,Spring Cloud Config 将检测到,并自动将两个新端点/encrypt 和/decrypt 添加到 Spring Cloud Config 服务,分别用于加密和解密;发送 post 请求至 localhost:8888/encrypt

加密 复制加密属性
加密配置仓库中的敏感信息,可将端点设置的加密内容复制到配置文件中,{cipher}值告诉 Config 服务器端它正在处理已加密的值;
my: name: ZJX idcard: {cipher}492cdada3181326c49fb00f727b59e788dc176ff2e99aad44b497d633c0801c2
配置解耦和区分
多个服务可能共享部分配置,同时具有各自不同的配置,spring cloud config 貌似不支持定位多个配置文件;
如何定位客户端配置在服务端的位置:/{application}/{profile}/{label}
application -> spring.application.name
profile -> spring.profiles.active
label -> master

