spring cloud的各种组件
Feign 声明式服务调用【掌握】
Hystrix 熔断器【掌握】
Gateway 网关【掌握】
每个组件都是为了解决微服务系统中的问题的。
疑问: spring cloud远程调用还是让人觉得不好用,能不能像dubbo那样,直接调用远程的方法?
• Feign 是一个声明式的 REST 客户端,它用了基于接口的注解方式,很方便实现客户端配置。
• Feign 最初由 Netflix 公司提供。eureka也是Netflix 公司
疑问: 如何使用feign客户端进行远程调用?
操作步骤:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
/**
*
* feign声明式接口。发起远程调用的。
*
String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
*
* 1. 定义接口
* 2. 接口上添加注解 @FeignClient,设置value属性为 服务提供者的 应用名称
* 3. 编写调用接口,接口的声明规则 和 提供方接口保持一致。
* 4. 注入该接口对象,调用接口方法完成远程调用
*
*/
@FeignClient(value = "FEIGN-PROVIDER")
public interface GoodsFeignClient {
@GetMapping("/goods/findOne/{id}")
public Goods findGoodsById(@PathVariable("id") int id);
}
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients //开启Feign的功能
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private GoodsFeignClient goodsFeignClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
Goods goods = goodsFeignClient.findGoodsById(id);
return goods;
}
}
http://localhost:9000/order/goods/1
疑问:远程调用,连接或者业务处理慢,超时了会有什么现象?
• Feign 底层依赖于 Ribbon 实现负载均衡和远程调用。
• Ribbon默认1秒超时。
超时会报错ReadTimeout,我们可以根据业务进行设置。
配置方式:
#配置熔断的时间监听
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
# 设置Ribbon的超时时间
ribbon:
ConnectTimeout: 1000 # 连接超时时间 默认1s
ReadTimeout: 3000 # 逻辑处理的超时时间 默认1s
疑问:远程调用都被feign给封装了,如何才能看到请求,响应的细节,数据?
分析:
配置feign的日志记录
操作步骤:
# 设置当前的日志级别 debug,feign只支持记录debug级别的日志
logging:
level:
cn.kinggm520: debug #调用了feign客户端所在代码的包名
@Configuration
public class FeignLogConfig {
/*
NONE,不记录
BASIC,记录基本的请求行,响应状态码数据
HEADERS,记录基本的请求行,响应状态码数据,记录响应头信息
FULL;记录完成的请求 响应数据
*/
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
@FeignClient(value = "FEIGN-PROVIDER",configuration = FeignLogConfig.class)
public interface GoodsFeignClient {
@GetMapping("/goods/findOne/{id}")
public Goods findGoodsById(@PathVariable("id") int id);
}
http://localhost:9000/order/goods/1
• Hystix 是 Netflix 开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败(雪崩)。
• 雪崩:一个服务失败,导致整条链路的服务都失败的情形。
Hystix 主要功能
• 隔离
线程池隔离
信号量隔离
• 降级:异常,超时
• 熔断
• 限流
总结: 主要解决服务器出现意外以后,应用该如何处理。
疑问: 服务提供方何时会降级,如何编写代码?
分析:
服务提供方何时会降级?
两种情况:
服务端代码抛异常
服务端代码超时(默认1秒钟)
操作步骤:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
/**
* 启动类
*/
@EnableEurekaClient
@SpringBootApplication
@EnableCircuitBreaker // 开启Hystrix功能
public class ProviderApp {
/**
* 定义降级方法:
* 1. 方法的返回值需要和原方法一样
* 2. 方法的参数需要和原方法一样
* 3. 就是只有方法名不同
*/
public Goods findOne_fallback(int id){
Goods goods = new Goods();
goods.setTitle("降级了~~~");
return goods;
}
@GetMapping("/findOne/{id}")
@HystrixCommand(fallbackMethod = "findOne_fallback")
public Goods findOne(@PathVariable("id") int id){
。。。
}
@GetMapping("/findOne/{id}")
@HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = {
//设置Hystrix的超时时间,默认1s
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public Goods findOne(@PathVariable("id") int id){
总结:
定义的降级方法要和controller方法完全一致,除了方法名。通过fallbackMethod指定降级方法,只需要设置方法名即可,不要带括号什么的。
疑问:那要是网络有问题,都不能连上提供方,还能降级吗?
分析:
操作步骤:
# 开启feign对hystrix的支持
feign:
hystrix:
enabled: true
/**
* Feign 客户端的降级处理类
* 1. 定义类 实现 Feign 客户端接口
* 2. 使用@Component注解将该类的Bean加入SpringIOC容器
*/
@Component
public class GoodsFeignClientFallback implements GoodsFeignClient {
@Override
public Goods findGoodsById(int id) {
Goods goods = new Goods();
goods.setTitle("又被降级了~~~");
return goods;
}
}
@FeignClient(value = "HYSTRIX-PROVIDER",fallback = GoodsFeignClientFallback.class)
public interface GoodsFeignClient {
@GetMapping("/goods/findOne/{id}")
public Goods findGoodsById(@PathVariable("id") int id);
}
总结:
注意编写的调用方降级类需要实现Feign客户端的接口,还要添加@Component注解交给spring容器管理。
问题:如果服务器端程序抛出异常,那么提供方还是消费方的降级方法生效?
提供方生效,因为提供方降级后,将会给消费方返回了正常的结果,所以消费方就不会生效。
服务处理的时间过长,但是不想因为超时降级,还需要得到服务器的结果,如何配置?
配置两个方面:
#配置熔断的时间监听
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
# 设置Ribbon的超时时间
ribbon:
ConnectTimeout: 1000 # 连接超时时间 默认1s
ReadTimeout: 3000 # 逻辑处理的超时时间 默认1s
@GetMapping("/findOne/{id}")
@HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = {
//设置Hystrix的超时时间,默认1s
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public Goods findOne(@PathVariable("id") int id){
Hystrix 熔断机制,用于监控微服务调用情况,当失败的情况达到预定的阈值(5秒失败20次),会打开断路器,拒绝所有请求,直到服务恢复正常为止。
总结:
熔断的目的是为了保护系统。全自动,不需要干预。
三种状态之间的切换
分析:
熔断是Hystrix 自动的机制,只要使用了Hystrix ,就可以进行系统的熔断保护。
所以我们做这个实验,只需要让我们的代码发生降级就可以。
需求: 让我们的请求ID为1的时候抛出异常,引发降级。
hystrix-provider的GoodsController中进行设置:
public Goods findOne(@PathVariable("id") int id){
//如果id == 1 ,则出现异常,id != 1 则正常访问
if(id == 1){
//1.造个异常
int i = 3/0;
}
可以通过@HystrixCommand注解的commandProperties属性进行配置:
@GetMapping("/findOne/{id}")
@HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = {
//设置Hystrix的超时时间,默认1s
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"),
//监控时间 默认5000 毫秒
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"),
//失败次数。默认20次
@HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "20"),
//失败率 默认50%
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "50")
})
public Goods findOne(@PathVariable("id") int id){
分析:
Hystrix 熔断监控
• Hystrix 提供了 Hystrix-dashboard 功能,用于实时监控微服务运行状态。
• 但是Hystrix-dashboard只能监控一个微服务。
• Netflix 还提供了 Turbine ,进行聚合监控。
搭建Turbine 聚合监控的步骤:
SpringCloud-资料\day02\资料\1. turbine 搭建\Turbine搭建步骤.md
总结: 了解内容,面试能把Turbine 名字说出来就可以。
疑问: 什么是网关?干什么用的?
分析:
定义:
• 网关旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。
• 在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。
• 存在的问题:
• 客户端多次请求不同的微服务,增加客户端的复杂性
• 认证复杂,每个服务都要进行认证
• http请求不同服务次数增加,性能不高
• 网关就是系统的入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、缓存、负载均衡、流量管控、路由转发等
• 在目前的网关解决方案里,有Nginx+ Lua、Netflix Zuul 、Spring Cloud Gateway等等
总结: 网关也是一个应用,有了网关之后,我们所有的请求都访问网关应用,由网关帮我们去调用其他服务。
网关干什么用?主要功能有两个:
路由
过滤
疑问:如何来使用Gateway 网关?
需求:使用Gateway 网关来路由转发请求
操作步骤:
其实就是创建一个最基本的spring cloud模块。
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class,args);
}
}
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
- id: gateway-provider
uri: http://localhost:8001/
predicates:
- Path=/goods/**
- id: gateway-cum
uri: http://localhost:9000/
predicates:
- Path=/orders/**
访问网关应用: http://localhost/goods/findOne/2
疑问: 什么是静态路由?
就是转发的地址是固定的。在开发中不便于维护,不使用。知道概念就可以。
分析:
配置:
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
# filters:配置局部过滤器的
- id: gateway-provider
# 静态路由
uri: http://localhost:8001/
predicates:
- Path=/goods/**
- id: gateway-consumer
uri: http://localhost:9000
predicates:
- Path=/order/**
# 微服务名称配置
疑问:静态路由的缺点是什么?什么是动态路由?有什么用
分析:
静态路由的缺点是什么?
静态路由,在网关中uri的配置是写死的,不便于维护。
什么是动态路由?有什么用?
让网关应用能够从注册中心Eureka中,动态的通过应用的名称来获取应用的地址
配置方式:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {
- id: gateway-consumer
# uri: http://localhost:9000
uri: lb://GATEWAY-CONSUMER
predicates:
- Path=/order/**
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
总结: 为网关添加Eureka客户端依赖,能够从注册中心发现服务。在配置路由的uri时,通过lb://服务名称 来获取转发服务器的地址信息。
疑问: 网关路由转发的应用很多,名称容易冲突,如何进行方便的区分?
分析:
将Eureka注册中心里应用的名称作为uri中的前缀来区分不同的应用。
配置:
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
discovery:
locator:
enabled: true # 设置为true 请求路径前可以添加微服务名称
lower-case-service-id: true # 允许为小写
测试:
访问网关应用: http://localhost/goods/findOne/2
也可以: http://localhost/gateway-provider/goods/findOne/2
• Gateway 支持过滤器功能,对请求或响应进行拦截,完成一些通用操作。
• Gateway 提供两种过滤器方式:“pre”和“post”
• pre 过滤器,在转发之前执行,可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
• post 过滤器,在响应之前执行,可以做响应内容、响应头的修改,日志的输出,流量监控等。
• Gateway 还提供了两种类型过滤器
• GatewayFilter:局部过滤器,针对单个路由
• GlobalFilter :全局过滤器,针对所有路由
• GatewayFilter 局部过滤器,是针对单个路由的过滤器。
• 在Spring Cloud Gateway 组件中提供了大量内置的局部过滤器,对请求和响应做过滤操作。
• 遵循约定大于配置的思想,只需要在配置文件配置局部过滤器名称,并为其指定对应的值,就可以让其生效。
参考资料: SpringCloud-资料\day02\资料\2. gateway内置过滤器工厂
配置:
# filters:配置局部过滤器的
- id: gateway-provider
# 静态路由
# uri: http://localhost:8001/
# 动态路由
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
filters:
- AddRequestParameter=username,zhangsan
总结:
根据文档中的过滤器的作用和名称进行配置,过滤的参数和值之间使用,逗号分隔。
• GlobalFilter 全局过滤器,不需要在配置文件中配置,系统初始化时加载,并作用在每个路由上。
• Spring Cloud Gateway 核心的功能也是通过内置的全局过滤器来完成。
• 自定义全局过滤器步骤:
@Component
public class MyFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定义全局过滤器执行了~~~");
//获取web应用的对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
return chain.filter(exchange);//放行
}
/**
* 过滤器排序
* @return 数值越小 越先执行
*/
@Override
public int getOrder() {
return 0;
}
}
总结:
自定义全局过滤器无需配置,只需要实现接口中的方法就可以。
自定义的过滤器需要添加@Component交给spring容器管理。