说明:由于学习资料是本地视频,尚未找到相关链接,后续找到原视频链接后会及时说明学习视频链接
0. 学习目标
- 使用Feign进行远程调用
- 搭建Spring Cloud Gateway网关服务
- 配置Spring Cloud Gateway路由过滤器
- 编写Spring Cloud Gateway全局过滤器
- 搭建Spring Cloud Config配置中心服务
- 使用Spring Cloud Bus实时更新配置
1. Feign
1.1 简介
在前面的学习中,使用了Ribbon的负载均衡功能,大大简化了远程调用时的代码
String url = "http://user-service/user/" + id; User user = this.restTemplate.getForObject(url, User.class)
可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方 式,来对这些代码再次优化呢? 可用Feign
Feign的作用:使用Feign实现consumer-demo代码中调用服务
Feign也叫伪装
Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。
1.2 快速入门
1.导入启动器依赖
在consumer-demo
中导入OpenFeign依赖
<!-- Feign远程服务调用依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
OpenFeign在Feign基础上,支持Spring MVC注解,例如@RequestMapping等
OpenFeign的
@FeignClient
可以解析SpringMVC的@RequestMapping
注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
2.开启Feign功能
在consumer-demo
项目的启动类上添加@EnableFeignClients注解开启Feign功能
//整合@SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker
@SpringCloudApplication
@EnableFeignClients //开启Feign功能
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
// 构建一个RestTemplate对象加入到spring容器中进行统一管理
@Bean
// 开启负载均衡
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Feign中已经自动集成了Ribbon负载均衡,因此不需要自己定义RestTemplate进行负载均衡的配置。
3.编写Feign客户端
在consumer-demo
中编写UserClient接口,指定user-service路由
// 声明当前类是一个Feign客户端,指定服务名为user-service
@FeignClient("user-service")
public interface UserClient {
// 和Mybatis的mapper接口类似,会通过动态代理生成这个接口的实现类
// 拼接http://user-service/user/13
@GetMapping("/user/{id}")
User queryById(@PathVariable Long id);
}
首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像
@FeignClient ,声明这是一个Feign客户端,同时通过 value 属性指定服务名称
接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果
@GetMapping中的**/user**,请不要忘记;因为Feign需要拼接可访问的地址
4.编写处理器ConsumerFeignController(注入Feign客户端并使用)
在consumer-demo
中编写ConsumerFeignController接口,通过userClient访问服务
@RestController
@RequestMapping("/cf")
public class ConsumerFeignController {
@Autowired
UserClient userClient;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id) {
return userClient.queryById(id);
}
}
5.测试
1.3 负载均衡
Feign中已经集成了Ribbon依赖和自动配置
因此不需要额外引入依赖,也不需要再注册 RestTemplate 对象。
Fegin内置的ribbon默认设置了请求超时时长,默认是1000,我们可以通过手动配置来修改这个超时时长
ribbon: ReadTimeout: 2000 # 读取超时时长 ConnectTimeout: 1000 # 建立链接的超时时长
因为ribbon内部有重试机制,一旦超时,会自动重新发起请求。如果不希望重试,可以添加配置:
ribbon: ReadTimeout: 2000 # 读取超时时长 ConnectTimeout: 1000 # 建立链接的超时时长 MaxAutoRetries: 0 # 当前服务器的重试次数 MaxAutoRetriesNextServer: 0 # 重试多少次服务 OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
给UserService的方法设置上线程沉睡时间2秒
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public User queryById(@PathVariable("id") Long id) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return userService.queryById(id); } }
重新测试http://localhost:8083/cf/13显示访问失败(因为ribbon被取消了重连功能)
1.4 Hystrix集成
Feign默认有对Hystrix的集成
默认情况下是关闭的。需要通过下面的参数来开启:
feign: hystrix: enabled: true # 开启Feign的熔断功能
Feign中的Fallback配置不像Ribbon中那样简单
1.定义一个类,实现UserFeignClient
作为fallback的处理类
// 服务降级处理逻辑编写类
@Component
public class UserClientFallback implements UserClient {
@Override
public User queryById(Long id) {
User user = new User();
user.setId(id);
user.setName("用户异常");
return user;
}
}
2.在UserFeignClient
中添加fallback = UserClientFallback.class
指定实现类
// 声明当前类是一个Feign客户端,指定服务名为user-service
@FeignClient(value = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
// 和Mybatis的mapper接口类似,会通过动态代理生成这个接口的实现类
// 拼接http://user-service/user/13
@GetMapping("/user/{id}")
User queryById(@PathVariable Long id);
}
3.测试
1.5 请求压缩
Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过在
consumer-demo
中的application.yml
配置下面的参数即可开启请求与响应的压缩功能:feign: compression: request: enabled: true # 开启请求压缩 response: enabled: true # 开启响应压缩
也可对请求的数据类型,以及触发压缩的大小下限进行设置:
feign: compression: request: enabled: true # 开启请求压缩 mime-types: text/html,application/xml,application/json # 设置压缩的数据类型,默认值为这三种压缩的数据类型 min-request-size: 2048 # 设置触发压缩的大小下限,默认值为2048 response: enabled: true # 开启响应压缩
1.6 日志级别
前面讲过,通过 logging.level.xx=debug 来设置日志级别。然而这个对Fegin客户端而言不会产生效果。
因为**@FeignClient 注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例**。
需要额外指定这个日志的级别才可以
1.在consumer-demo
的配置文件中设置日志级别为debug
logging:
level:
# 项目的包名
com.demo: debug
2.在consumer-demo
编写FeignConfig配置类,定义日志级别
// Feign配置类
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
//记录所有请求和响应的明细,包括头信息、请求体、元数据
return Logger.Level.FULL;
}
}
Feign支持4种级别:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
3.在consumer-demo
的UserClient
接口类上的@FeignClient中添加configuration = FeignConfig.class
指定配置类
// 声明当前类是一个Feign客户端,指定服务名为user-service
@FeignClient(value = "user-service", fallback = UserClientFallback.class, configuration = FeignConfig.class)
public interface UserClient {
// 和Mybatis的mapper接口类似,会通过动态代理生成这个接口的实现类
// 拼接http://user-service/user/13
@GetMapping("/user/{id}")
User queryById(@PathVariable Long id);
}
4.重启项目进行测试
可以看到访问的日志
2. Spring Cloud Gateway 网关
2.1 简介
为什么需要网关?
- 有很多的微服务,使用网关只向外暴露一个统一的接口,隔离原本需要暴露接口的微服务,增强服务调用的安全性
Spring Cloud Gateway是Spring官网基于Spring5.0、Spring Boot2.0、Project Reactor等技术开发的网关服务
Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等
Spring Cloud Gateway为微服务架构提供简单、有效且同意的API路由管理方式
Spring Cloud Gateway是可替代Netflix Zuul的一套解决方案
Spring Cloud Gateway组件的核心是一系列过滤器,通过这些过滤器可将客户端发送的请求转发(路由)到对应的微服务中。
Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务节点IP端口信息,从而加强微服务安全性
Spring Cloud Gateway本身也是一个微服务,需要注册到Eureka服务注册中心:
- 添加依赖
- 启动类添加注解开启Gateway
- 配置文件中添加Eureka注册中心地址
网关的核心功能:
- 过滤
- 路由
2.2 架构
不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都可经过网关,然后再由网关来实现鉴权、动态路由等操作。
简而言之,Gateway就是服务的统一入口
2.3 核心概念
- 路由(route):由一个ID、一个目的URL、一组断言工厂、一组Filter组成。如果路由断言为真,则说明请求URL和配置路由匹配
- 断言(Predicate):Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange,断言函数允许开发者定义匹配来自Http Request中的任何信息(例如请求头和参数)
- 过滤器(Filter):一个标准的Spring WebFilter。过滤器Filter会对请求和响应进行修改处理。Spring Cloud Gateway中的Filter分为两种类型:
- Gateway Filter
- Global Filter
2.4 Gateway入门
目标:搭建网关服务工程,并测试网关服务作用(过滤+路由)
需求:通过网关系统将包含/user的请求路由到http://localhost/user/用户id
1.创建网关工程
在原项目spring-cloud
中创建新的modulegateway
2.添加启动器依赖
<dependencies>
<!-- 添加Gateway网关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 添加Client客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
3.编写启动引导类和配置文件,在配置文件application.yml
中设置路由信息
@SpringBootApplication
@EnableDiscoveryClient // 开启Client服务客户端
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
server:
port: 8034
spring:
application:
name: gateway
cloud:
gateway:
routes:
# 路由id,可任意写 注意id前的空格
- id: user-service
uri: http://127.0.0.1:8080
# 路由断言:可匹配映射路径
predicates:
# 当访问路径以/user开头的时候将请求转发到uri对应的微服务中 注意Path前的空格
- Path=/user/**
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
4.测试
Gateway将http://localhost:8034/user/13转发到http://localhost:8080/user/13
2.5 面向服务的路由
目标:使用在Eureka注册的服务作为路由指向地址
分析:
如果将路由规则中的服务地址写死是明显不合理:
- 和前面的服务访问一样,访问的时候需要记住服务提供者的地址
- 并且一个服务有多个实例的话配置麻烦
应该根据服务的名称,去Eureka注册中心查询服务对应的所有实例列表,然后进行动态路由。在Spring Cloud Gateway中可以通过配置动态路由解决
1.修改gateway
网关项目中的application.yml
,将服务user-service的uri改为lb://user-service
server:
port: 8034
spring:
application:
name: gateway
cloud:
gateway:
routes:
# 路由id,可任意写
- id: user-service
# uri: http://127.0.0.1:8080
# lb: loadBalance负载均衡,lb表示从eureka中获取具体服务,通过user-service服务名去Eureka注册中心找对应的服务列表
uri: lb://user-service
# 路由断言:可匹配映射路径
predicates:
# 当访问路径以/user开头的时候将请求转发到uri对应的微服务中
- Path=/user/**
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
2.测试
面向服务的路由:只需要在配置文件中指定路由路径,类似:
lb://user-service
,其中user-service为注册到eureka的服务id名称注:lb之后编写的服务名必须要在eureka中注册才能使用
2.6 路由前缀处理
目标:可以对请求到网关服务的地址添加或去除前缀
分析:
假设提供服务的地址为http://127.0.0.1:8080/user/13
- 添加前缀:对请求地址添加前缀路径之后再作为代理的服务地址
http://127.0.0.1:8034/13 --> http://127.0.0.1:8080/user/13 添加前缀路径/user
- 去除前缀:对请求地址路径去除一些前缀后再作为代理的服务地址
http://127.0.0.1:8034/user-service/user/13 --> http://127.0.0.1:8080/user/13 去除前缀路径/user-service
2.6.1 添加前缀
1.gateway
的application.yml
中添加filters
过滤配置,并在其中添加- PrefixPath
前缀
spring:
application:
name: gateway
cloud:
gateway:
routes:
# 路由id,可任意写
- id: user-service
# uri: http://127.0.0.1:8080
# lb: loadBalance负载均衡,lb表示从eureka中获取具体服务,通过user-service服务名去Eureka注册中心找对应的服务列表
uri: lb://user-service
# 路由断言:可匹配映射路径
predicates:
# 当访问路径以/user开头的时候将请求转发到uri对应的微服务中
# - Path=/user/**
- Path=/**
filters:
# 添加请求路径的前缀
- PrefixPath=/user
上述配置的意思:将任何http://127.0.0.1:8034/开头的访问都添加一个/user前缀改为http://127.0.0.1:8034/user/并转发到user-service服务中,根据user-service服务名去eureka注册中心找对应的服务地址列表,并选择一个进行请求访问
过滤器PrefixPath:实现映射路径中地址的添加
2.测试
2.6.2 去除前缀
1.gateway
的application.yml
中添加filters
过滤配置,并在其中添加- StripPrefix
前缀
spring:
application:
name: gateway
cloud:
gateway:
routes:
# 路由id,可任意写
- id: user-service
# uri: http://127.0.0.1:8080
# lb: loadBalance负载均衡,lb表示从eureka中获取具体服务,通过user-service服务名去Eureka注册中心找对应的服务列表
uri: lb://user-service
# 路由断言:可匹配映射路径
predicates:
- Path=/user-service/user/**
filters:
# 1表示过滤一个前缀,2表示过滤两个前缀,以此类推
- StripPrefix=1
上述配置的意思:将任何http://127.0.0.1:8034/user-service/user/开头的请求访问url都去掉user-service改为http://127.0.0.1:8034/user/,并转发到user-service服务中(user-service服务必须在eureka注册中心注册成功,并且可通过user-service服务名称找到这个服务提供地址),根据user-service服务名去eureka注册中心找对应的服务地址列表,并选择一个进行请求访问
过滤器StripPrefix:实现映射路径中地址的去除
2.测试
小结:
当客户端的请求地址与微服务的服务地址不一致的时候,可通过在网关项目的
application.yml
中配置路径过滤器实现路径前缀的添加/去除。
2.7 过滤器
目标:Gateway默认过滤器的用法和过滤器类型学习
2.7.1 简介
Gateway网关的一个重要功能,就是实现请求的鉴权。这个鉴权操作往往通过网关提供的过滤器实现(2.6中的路由前缀也是有过滤器实现)
过滤器名称 | 说明 |
---|---|
AddRequestHeader | 对匹配上的请求加上Header |
AddRequestParameters | 对匹配上的请求路由添加参数 |
AddResponseHeader | 对从网关返回的相应添加Header |
StripPrefix | 对匹配上的请求路径去除前缀 |
2.7.1.1 配置全局默认过滤器
1.在gateway
项目的application.yml
配置文件中添加default-filters
配置全局默认过滤器
spring:
application:
name: gateway
cloud:
gateway:
routes:
# 路由id,可任意写
- id: user-service
# uri: http://127.0.0.1:8080
# lb: loadBalance负载均衡,lb表示从eureka中获取具体服务,通过user-service服务名去Eureka注册中心找对应的服务列表
uri: lb://user-service
# 路由断言:可匹配映射路径
predicates:
# 当访问路径以/user开头的时候将请求转发到uri对应的微服务中
# - Path=/user/**
# - Path=/**
- Path=/user-service/user/**
filters:
# 添加请求路径的前缀
# - PrefixPath=/user
# 1表示过滤一个前缀,2表示过滤两个前缀,以此类推
- StripPrefix=1
# 默认过滤器,对所有路由都生效
default-filters:
# 对从网关返回的相应添加Header
- AddResponseHeader=X-Response-Default-MyName, spring-cloud-demo
2.测试
访问http://127.0.0.1:8034/user-service/user/13,F12查看发现请求的响应头中有配置的Header
注:如果使用Chrome查看不到,可更换为IE浏览器
2.7.1.2 过滤器类型
Gateway有两种过滤器:
- 局部过滤器:通过
spring.cloud.gateway.routes.filters
配置在具体的路由下,只作用于当前路由上。若配置spring.cloud.gateway.default-filters
会对所有路由生效也算是全局过滤器(这些过滤器实现通过实现GatewayFilterFactory接口)- 全局过滤器:不需要在配置文件中配置,作用在所有路由上(实现
GlobalFilter
接口即可)
2.7.2 过滤器生命周期
Spring Cloud Gateway的Filter生命周期类似Spring MVC的拦截器有两个:“pre”和“post”,分别在请求被执行前调用和被执行后调用
此处
pre
和post
可通过过滤器的GatewayFilterChain
执行filter方法前后实现
2.7.3 使用场景
- 请求鉴权:一般
GatewayFilterChain
执行filter方法前,若发现没有权限则直接返回空 - 异常处理:一般
GatewayFilterChain
执行filter方法后,记录异常并返回 - 服务调用时长统计:
GatewayFilterChain
执行filter方法前后根据时间统计
小结:
过滤器的用法:在配置文件中指定要使用的过滤器名称
过滤器的类型:局部、全局
适用场景:请求鉴权、异常处理、记录调用时长等
2.8 自定义过滤器
2.8.1 自定义局部过滤器
目标:按照默认过滤器编写并配置一个自定义局部过滤器,该过滤器可通过配置文件中的参数名称获取请求参数值
需求:在过滤器中将http://localhost:8043/user-service/user/13?name=ciery中的参数name的值获取到并输出到控制台
1.配置过滤器
与其他局部过滤器配置一致,在filters中添加配置即可
spring:
application:
name: gateway
cloud:
gateway:
routes:
# 路由id,可任意写
- id: user-service
# lb: loadBalance负载均衡,lb表示从eureka中获取具体服务,通过user-service服务名去Eureka注册中心找对应的服务列表
uri: lb://user-service
# 路由断言:可匹配映射路径
predicates:
- Path=/user-service/user/**
filters:
# 1表示过滤一个前缀,2表示过滤两个前缀,以此类推
- StripPrefix=1
# 配置自定义局部过滤器
- MyParam=name
2.编写过滤器
@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
static final String PARAM_NAME = "param";
public MyParamGatewayFilterFactory() {
super(Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_NAME);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 获取请求参数中param对应的参数名的参数值
ServerHttpRequest request = exchange.getRequest();
if (request.getQueryParams().containsKey(config.param)) {
// 请求中包含param,则打印输出
request.getQueryParams().get(config.param).forEach(value -> System.out.printf("-----局部过滤器---%s = %s------", config.param, value));
}
return chain.filter(exchange);
};
}
public static class Config{
// 对应在配置过滤器的时候指定的参数名
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
}
3.测试
请求中携带?name=ciery之后,经过过滤器输出对应的字符串
如果携带其他参数例如?age=23,经过过滤器不拦截,和普通请求一致
参数名可变,即不一定每次都是name;需要可通过配置过滤器的时候做到配置参数名,即修改
application.yml
中的-MyParam即可,例如修改为age,则请求url中携带age可以输出
自定义过滤器命名应该为:***GatewayFilterFactory
2.8.2 自定义全局过滤器
目标:定义一个全局过滤器检查请求中是否携带有token参数
需求:编写全局过滤器,在过滤器中检查请求地址中是否携带token参数。如果token参数的值存在则放行;如果token的参数值为空或者不存在则设置返回的状态码:未授权也不再执行下去
1.编写全局过滤器
@Component // 过滤器是一个组件,需要使用@Component进行注册
public class MyGlobalFilter implements GlobalFilter, Ordered {
/**
* Process the Web request and (optionally) delegate to the next {@code WebFilter}
* through the given {@link GatewayFilterChain}.
*
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("--------全局过滤器MyGlobalFilter-----------");
// 获取请求中第一个参数值
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isBlank(token)) {
// 设置响应状态码为未授权
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 有token放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 值越小越先执行
return 1;
}
}
2.测试
当请求中未加token参数的时候,全局过滤器MyGlobalFilter进行拦截,返回401 Unauthorized错误;当请求中添加token参数的时候(后续也可对token参数的值进行限制,例如登录后生成统一类型的token,之后的请求都需要携带这个token进行访问,否则跳转登录页面等),全局过滤器放行,请求可以得到正常访问并响应。
2.9 负载均衡和熔断处理
Gateway默认集成Ribbon负载均衡和Hystrix熔断机制。但是所有的超时策略都使用的是默认值,例如熔断超时时间只有1s,这就意味着只要1s没有请求到服务就会进行服务降级。所以实际应用中建立手动配置
# 熔断配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
# 负载均衡配置
ribbon:
ConnectTimeout: 1000
ReadTimeout: 2000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 0
2.10 Gatewey跨域配置
一般网关都是所有微服务的统一入口,必然在调用的时候会出现跨域问题。
跨域:在js请求访问中,如果访问的地址与当前服务器的域名、ip或者端口号不一致则称为跨域请求。如果不解决的话就不能获取对应地址的返回结果
(例如一般的web项目中,如果前后端分离开发,在测试的过程中会因为前后端不是一个ip所以存在跨域问题;当前后端整合之后启动因为ip相同所以跨域问题不存在)
例如:从http://127.0.0.1:8033的js访问http://127.0.0.1:8080的数据,因为端口不同所以也是跨域请求
在访问Spring Cloud Gateway网关服务器的时候如果出现跨域问题的话,可以在网关服务器中通过配置解决,配置允许哪些服务是可以跨域请求的
spring:
cloud:
gateway:
# 配置跨域请求
globalcors:
corsConfigurations:
'[/**]':
#allowedOrigins: * # 这种写法或者下面的都可以,*表示全部
allowedOrigins:
- "http://docs.spring.io"
allowedMethods:
- GET
上述配置说明:
可允许来自http://docs.spring.io的get请求方式获取服务数据
allowedOrigins:指定允许访问的服务器地址,例如http://localhost:10080也是可以的
‘[/**]’:表示对所有访问到网关服务器的请求地址
2.11 Gateway的高可用
启用多个Gateway服务,自动注册到Eureka形成集群。
如果是服务内部访问,访问Gateway自动负载均衡,没啥问题
但是对于Gateway来讲,更多的是外部访问,例如PC端、移动端等,它们无法通过Eureka进行负载均衡,解决的方法就是使用其他服务网关,对Gateway进行代理,例如Nginx
2.12 Gateway与Feign的区别
- Gateway:作为整个应用的流量入口,接收所有的请求,例如PC、移动端等,并将不同的请求转发至不同的微服务模块,其作用可视为nginx;大部分情况下用作权限鉴定、服务端流量控制
- Feign:将当前微服务的部分服务接口暴露出来,且主要用于各个微服务之间的服务调用
Gateway网关一般直接给终端请求使用;Feign一般用在微服务之间调用
3. Spring Cloud Config分布式配置中心
3.1 简介
目标:了解分布式配置中心的作用
在分布式系统中,由于服务数量非常多,配置文件分散在不同的微服务项目中,管理不方便。
为方便配置文件集中管理,需要分布式配置中心组件
在Spring Cloud中,提供了Spring Cloud Config,其支持配置文件放在配置服务的本地,也支持放在远程Git仓库中(例如码云、GitHub等)
配置中心本质上也是一个微服务,同样需要注册到Eureka服务注册中心
小结:Spring Cloud Config可以通过修改在git仓库中的配置文件实现其它所有微服务的配置文件的修改
3.2 Git配置管理
目标:创建码云的远程公开git参数,搭建配置中心微服务config-server
1.创建git仓库(码云创建)并将user-service
项目的application.yml
配置加入其中
2.搭建配置中心config-server(一个Spring Boot项目)
2.1 添加依赖
<dependencies>
<!-- 添加eureka服务客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 添加config-server配置中心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
2.2 启动类添加@EnableConfigServer配置开启配置服务中心
@SpringBootApplication
@EnableConfigServer // 开启config server配置服务中心
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
2.3 配置application.yml
,添加config.server.uri.git
git仓库的链接
server:
port: 12000
spring:
application:
name: config-server
cloud:
config:
server:
git:
# 配置git仓库链接
uri: https://gitee.com/ciery/spring-cloud.git
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
3.测试
启动eureka注册中心和config配置中心,访问http://localhost:12000/user-dev.yml,可查看到码云存储的user-dev.yml文件
当码云中这个user-dev.yml有改动时,刷新上述网址可得到更新后的数据
git上配置文件有统一的命名方式:{application}–{profile}.yml或{application}-{profile}.properties
application:应用名称
profile:区分开发环境、测试环境、生产环境等(dev、test、provided)
例如user-dev.yml:用户微服务开发环境下的配置文件
3.3 获取配置中心配置
目标:改造用户微服务user-service,配置文件信息不再由微服务项目提供,而是从配置中心获取
需求:将服务提供工程user-service中的application.yml删除,修改为从配置中心config-server中获取
1.添加启动器依赖
user-service
项目中添加config-server
的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
2.修改配置文件
删除user-service
中原本的application.yml
文件,替换为bootstrap.yml
文件,并在其中配置仓库中对应的配置文件路径和eureka注册中心地址(同时config-server
中原本的关于git地址的配置也得在,才能找到仓库)
spring:
cloud:
config:
# 要与仓库中的配置文件的application保持一致
name: user
# 要与仓库中的配置文件的profile保持一致
profile: dev
# 要与仓库中的配置文件所属的版本(分支)一致
label: master
discovery:
# 使用配置中心
enabled: true
# 配置中心服务名
service-id: config-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
3.测试
启动eureka-server
、user-service
和config-server
,可以从eureka注册中心看到user-service服务注册成功,并且可正常访问http://localhost:8080/user/13,即可正常从配置中心config-server中获取user-service的配置文件连接到数据库,获取id=13对应的数据
小结:将原来的
application.yml
删除,添加bootstrap.yml
配置文件同样是Spring Boot的默认配置文件,
bootstrap.yml
:其内容经常是配置一些项目中固定的配置项如果是项目经常变动的应该配置到
application.yml
中,现使用了配置中心则应配置到git仓库中对应的配置文件user-dev.yml
bootstrap.yml文件相当于项目启动时的引导文件,内容相对固定。application.yml文件是微服务的一些常规配置参数,变化比较频繁
感觉上讲,
config-server
配置中心更是方便微服务项目部署之后,对于某个微服务配置文件的在线修改(如果没有配置中心的话,就必须得在项目中更新配置文件然后重新部署这个服务;如果是配置文件在配置中心的话,直接更新配置文件,调用服务的时候就会从配置中心获取最新的配置信息
4. Spring Cloud Bus服务总线
4.1 Spring Cloud Config中配置中心3.3存在的问题
3.2-3.3中已经完成将微服务中的配置文件集中存储在远程Git仓库,并通过配置中心微服务从Git仓库拉取配置文件,当用户微服务启动时会连接配置中心获取配置信息从而启动用户微服务
但是如果更新Git仓库中的配置文件,用户微服务并不会及时接收到新的配置信息并更新,需要一定的等待时间
1.Git仓库中修改user-dev.yml
server:
port: ${port:8080}
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?useSSL=false
username: root
password: root
application:
# 应用名(将来会作为服务的id使用)
name: user-service
mybatis:
type-aliases-package: com.demo.domain
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
# 设置服务实例更倾向于使用ip寻址而非host名,
prefer-ip-address: true
# 配置服务实例具体的ip地址
ip-address: 127.0.0.1
# 续约间隔,默认30s(半分钟)
lease-renewal-interval-in-seconds: 5
# 服务失效间隔,默认90s(一分钟半)
lease-expiration-duration-in-seconds: 5
test:
name: ciery
2.修改user-service
,输出配置文件中的test.name
值
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${test.name}")
String name;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
System.out.println("-----配置中心中配置文件的test.name=" + name);
return userService.queryById(id);
}
}
3.测试
启动注册中心eureka-server
、配置中心config-server
和用户服务user-service
,访问http://localhost:8080/user/13
第一个输出为更新前的输出,user-service从配置中心获取配置文件,拿到test.name的值为ciery并输出
Git仓库更新
user-dev.yml
server: port: ${port:8080} spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/springcloud?useSSL=false username: root password: root application: # 应用名(将来会作为服务的id使用) name: user-service mybatis: type-aliases-package: com.demo.domain eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: # 设置服务实例更倾向于使用ip寻址而非host名, prefer-ip-address: true # 配置服务实例具体的ip地址 ip-address: 127.0.0.1 # 续约间隔,默认30s(半分钟) lease-renewal-interval-in-seconds: 5 # 服务失效间隔,默认90s(一分钟半) lease-expiration-duration-in-seconds: 5 test: name: ciery1
第二个输出为更新后的输出,user-service并没有及时更新配置文件,依旧输出的是ciery而非ciery1
重启user-service项目之后访问可以得到ciery1
结论:对Git仓库中配置文件的修改没有及时更新到用户微服务,只有重启用户服务才会生效
如果想在不重启的情况下及时更新配置,可用Spring Cloud Bus实现配置的自动更新
4.2 Spring Cloud Bus简介
Spring Cloud Bus消息总线是用轻量级的消息代理将分布式的节点连接起来,可用于广播配置文件的更改或服务的监控管理,即消息总线可以为微服务做监控,也可实现应用程序间的相互通信
Spring Cloud Bus可选的消息代理有RabbitMQ和Kafka
很多微服务注册到Eureka服务中心
配置中心也注册到Eureka服务中心,同时配置项目配置文件对应的git仓库地址
每个微服务都有各自的配置(固定的配置是关于Git仓库中配置文件的定位信息),访问某个微服务,这个服务会去访问配置中心,配置中心根据服务中的配置获取到Git仓库中的配置文件(实际上配置中心会根据微服务中的关于配置文件的命名、位置等信息去Git仓库中拉取相应的配置文件到本地缓存,以供这个微服务使用)
如果这个时候用户在Git仓库中修改了某个微服务的配置文件,配置中心会发起一个消息(说明有一个配置项被修改),所有的微服务(消息的消费者)会监听到RabbitMQ的改变,然后及时进行更新
总结:Spring Cloud Bus可以将git仓库的配置文件更新,在不重启系统的情况下实现及时同步到各个微服务
4.3 Spring Cloud Bus应用
目标:启动RabbitMQ,通过修改Git中的配置文件后,发送Post请求实现及时更新用户微服务中的配置项
需求:在Git仓库中修改
user-dev.yml
文件,实现不重启user-service
的情况下及时更新配置文件
1.启动RabbitMQ
2.修改配置中心config-server
2.1 添加Bus和Rabbit依赖
<!-- 添加spring-cloud-bus消息总线依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<!-- 添加spring-cloud-bus RabbitMQ依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
2.2 配置文件中添加RabbitMQ和服务总线暴露路径的配置
server:
port: 12000
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/ciery/spring-cloud.git
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
management:
endpoints:
web:
exposure:
# 暴露触发消息总线的地址
include: bus-refresh
3.修改服务提供工程user-service
3.1 添加bus、Rabbit和actuator依赖
<!-- 添加spring-cloud-bus消息总线依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<!-- 添加spring-cloud-bus RabbitMQ依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<!-- 添加actuator监控依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3.2 配置文件中添加RabbitMQ配置
spring:
cloud:
config:
# 要与仓库中的配置文件的application保持一致
name: user
# 要与仓库中的配置文件的profile保持一致
profile: dev
# 要与仓库中的配置文件所属的版本(分支)一致
label: master
discovery:
# 使用配置中心
enabled: true
# 配置中心服务名
service-id: config-server
# rabbitmq的配置信息,如下配置的都是默认值,也可直接不配置(系统使用默认值)
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
3.3 UserController中添加@RefreshScope开启刷新配置
@RestController
@RequestMapping("/user")
@RefreshScope // 刷新配置
public class UserController {
@Autowired
private UserService userService;
@Value("${test.name}")
String name;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
System.out.println("-----配置中心中配置文件的test.name=" + name);
return userService.queryById(id);
}
}
4.测试
启动注册中心eureka-server
、配置中心config-server
、用户服务user-service
,访问http://localhsot:8080/user/13获取到正常的结果
修改git仓库中用户服务的配置文件user-dev.yml
,使用postman或RESTClient发送POST请求访问http://127.0.0.1:12000/actuator/bus-refresh
重新访问原链接,可以看到配置文件被更新
说明:请求地址http://127.0.0.1:12000/actuator/bus-refresh中 /actuator是固定的,/bus-refresh对应的是配置中心config-server中的application.yml文件的配置项include的内容。
这个POST请求的作用是访问配置中心的消息总线服务,消息总线服务接收到请求后会向消息队列中发送消息,各个微服务会监听消息队列。当微服务接收到队列中的消息后,会重新从配置中心获取最新的配置信息。
5. Spring Cloud体系技术综合应用说明
目标:了解Spring Cloud中的Eureka、Ribbon、Gateway、Config、Bus和Feign等技术的综合应用
0.根据业务需求开发相应的微服务,例如用户服务和订单服务等
1.这些服务通过注册到Eureka服务注册中心向外界提供服务调用,每个服务都可以做集群部署(一个集群多个相同的服务)
2.Eureka服务注册中心存储服务提供地址列表,并定期检查服务提供者的心跳。若检测到某个微服务哟屋恩替直接剔除
3.服务间可通过Feign进行调用
4.各个微服务的配置文件可以通过配置中心Config Server从Git仓库中拉取对应的配置文件保存在本地缓存中
5.如果Git仓库中微服务的配置文件被更新,会调用post请求的/actuator/bus-refresh进行刷新,实现不重启微服务及时更新自己的配置文件(各个微服务通过监听MQ的变化去更新自己的配置文件)
6.外部请求访问会先到Gateway网关,网关可对其进行权限鉴定、异常处理、日志记录等操作,之后根据配置的微服务id将请求路由到不同的微服务中
7.Gateway默认集成Ribbon,根据访问中的url匹配配置中的微服务id,然后根据id在Eureka注册中心找到对应的服务地址列表,然后负载均衡算法找到一个对应的服务地址以供访问请求
8.当服务出现异常的时候,可以通过Hystrix对服务进行降级处理(线程隔离+服务熔断)
简单总结
-
Feign
- 是一个组件,可实现自动拼接服务地址
- 开启Feign
- 配置
- 负载均衡
- Hystrix服务熔断
- 请求压缩
- 日志级别
-
Spring Cloud Gateway
- 是一个组件,由一系列过滤器组成
- 作用:路由和过滤
- 路由:配置文件中指定服务名和地址、过滤器、断言
- 过滤:可在服务执行之前和之后执行一些非功能性业务
- 过滤器:
- 局部过滤器:针对配置的路由
- 全局过滤器:对所有的路由
- 配置
- 负载均衡
- 服务熔断
- 与Feign的区别
- Feign:用于服务间调用
- Gateway:一般的请求,是所有微服务的入口
-
Spring Cloud Config
- 是一个配置中心服务
- 作用:可获取git仓库中的配置文件,给其他微服务使用,从而使各个微服务的配置在git仓库中进行集中式管理
- 改造user-service:将配置文件删除,然后从配置中心获取
- 问题:git仓库中修改配置项,微服务系统中的配置项没有及时更新,需要重启
-
Spring Cloud Bus
- 可实现修改git仓库中的配置文件并及时同步和不重启微服务系统的情况下更新到配置项
- 使用RabbitMQ进行程序间的消息通信
- 访问刷新配置中心的一个地址bus-refresh,发送MQ消息。微服务接收消息之后获取最新的配置项