OpenFeign:
负载均衡+RPC调用
启动类添加@EnableFeignClients,
业务逻辑接口添加 @FeignClient(name = "定义当前客户端client名字") 如果client同名,可以用contextId起别名。
然后实现类继承逻辑接口,写具体实现,Controller调用的时候,直接调用加了OpenFeign的接口即可
OpenFeign超时控制:
如果生产者需要处理3s,消费者请求1s,就会出现超时的情况。OpenFeign客户端(消费者)的等待时间默认是1s
OpenFeign的底层使用Ribbon,所以设置超时时间也是改Ribbon的配置:
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
表示的是我OpenFeign客户端可以等待5秒中,而现在支付微服务提供者的处理时间需要3秒钟,再来测试是没有问题的。
OpenFeign的日志打印功能:
日志:对Feign接口的调用情况进行监控并输出
日志级别:
-
NONE:默认的,不显示任何日志;
-
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
-
HEADERS:除了BASIC中定义的信息外,还有请求和响应的头信息;
-
FULL:除了HEADERS中定义的信息外,还有请求和响应的正文及元数据。
首先配置日志的Bean:
@Configuration
public class FeignLogConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
然后配置Yml文件需要开启日志的Feign客户端:
Feign客户端是加了@FeignClient的那个接口的路径
logging:
level:
cn.bdqn.service.PaymentFeignService: debug
Hystrix断路器:
服务的扇出链路多,如果每个环节出问题就会长时间占用资源导致系统崩溃。
缓存雪崩问题。
问题:
如果用高并发压测去测生产者微服务,出现了转圈卡顿,线程被打满没有多余线程分解压力。
如果生产者被打满,消费者再去调用生产者,就会转圈或者超时。
解决方式:
如果转圈/超时--->不再等待
出错(宕机/运行出错)--->有一个默认的处理方式去执行。降级!
降级:
生产者自身修复
@HystrixCommand
设置一个自身调用的时间限制,超过多少时间视为异常进行降级处理。
@HystrixCommand(fallbackMethod = "getInfo_timeoutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
在这里,自己写一个fallbackmethod处理降级
启动类加注解:@EnableCircuitBreaker
消费者自身修复:
1.添加Yml配置
feign:
hystrix:
enabled: true
2.在启动类添加@EnableHystrix注解
3.在对应接口添加
@HystrixCommand(fallbackMethod = "getInfo_timeoutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
})
问题一、冗余
如果每个接口我们都写一个降级方法,会造成代码的臃肿,我们可以写一个公共的降级方法,对于那些没有指定降级方法的接口统一调用默认的降级方法。
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
问题二、解耦
把降级方法和业务逻辑代码写在一起耦合度过高
我们可以重新写一个特定业务的降级实现类,让该类继承业务实现类。
并在业务实现类中通过注解指定降级方法。
例如:
单写一个对应的业务降级实现类:PaymentFallbackService
继承业务实现类。 PaymentFallbackService implements PaymentService
在业务实现类中使用注解标明降级方法:
@FeignClient(name = "springcloud-provider-hystrix-payment" ,
fallback = PaymentFallbackService.class)
public interface PaymentService {xxxxxx}
熔断break
1.修改HystrixCommand注解
// 在10秒内,如果10(或者以上)次请求有6次是失败的,就会进行服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
})
服务降级---->服务熔断----->恢复链路
服务降级是保底方法,熔断是一段时间内的失败次数过多的措施,然后才会通过监听慢慢恢复链路。熔断后,就算是调用正确也不会立刻恢复。
涉及到断路器的三个重要参数:快照时间窗/时间范围、请求总数阈值、错误百分比阈值。
-
1、快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
-
2、请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
-
3、错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阈值情况下,这时候就会将断路器打开。
断路器开启/关闭的条件:
-
1、当满足一定的阈值的时候(默认10秒内超过20个请求次数)
-
2、当失败率达到一定的时候(默认10秒内超过50%的请求失败)
-
3、到达以上阈值,断路器将会开启
-
4、当开启的时候,所有请求都不会进行转发
-
5、一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5
链路恢复:
1、断熔后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback,通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
2、原来的主逻辑要如何恢复呢?对于这一问题,hystrix也为我们实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
总结:断路器在监听请求的失败率达到一定次数时进行服务降级策略,原来的主逻辑被替换为降级逻辑的实现,然后有一个休眠时间窗(就像冷静期一样),等冷静期一到,断路器会变成半开状态,试探的把一次请求交给主逻辑运行,如果运行无误就恢复链路调用。
GateWay网关:
yml文件:
server:
port: 9500
spring:
application:
name: springcloud-gateway
cloud:
gateway:
routes:
- id: payment_routh1 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001
predicates:
- Path=/payment/getInfo/** #断言,路径相匹配的进行路由
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,
http://eureka7002.com:7002/eureka,
http://eureka7003.com:7003/eureka
instance:
prefer-ip-address: true # 使用ip地址注册
相当于在请求某个接口路径的时候,在外面再套一层网关,通过网关去进行过滤,路由,监控等操作。
例如我们请求8001接口,但是我们套一层端口为9500的网关,这样请求的时候用9500端口即可,安全性保证。
一般我们不会在网关的配置文件把对应的路由地址写死,使用服务名进行动态路由。如下:
yml:
server:
port: 9500
spring:
application:
name: springcloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,,利用微服务名进行路由
routes:
- id: payment_routh1 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001
uri: lb://springcloud-payment-provider-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001
uri: lb://springcloud-payment-provider-service
predicates:
- Path=/payment/getInfo/** #断言,路径相匹配的进行路由
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,
http://eureka7002.com:7002/eureka,
http://eureka7003.com:7003/eureka
instance:
prefer-ip-address: true # 使用ip地址注册
在这里的url的lb标识loadbalance,使用gateway的负载均衡功能
断言有很多种形式,path方式只是其中的一种。
path:路径
before:在某个时间点前
after:在某个时间点后
between:在时间点之间
cookie:cookie值匹配
header:header值匹配
host:根据主机名匹配
Method:根据请求方式
Query:参数
RemoteAddr:指定ip地址
也可以配置多个断言,满足所有断言后才能够进行路由转发。
gateway过滤器
生命周期pre和post
pre:这种过滤器在请求被路由之前调用,我们可以利用这种过滤器实现身份验证,在集群中选择请求的微服务,日志的记录等。
post:这种过滤器在路由到微服务以后执行,这种过滤器可用来为响应添加标准的http header,收集统计信息和指标、将响应从微服务发送给客户端。
局部过滤器(GatewayFilter):是针对单个路由的过滤器,可以对访问的url过滤,进行切面处理
全局过滤器(GlobalFilter):通过全局过滤器可以实现对权限的统一校验,安全性验证等功能,并且全局过滤器也是我们使用最多的过滤器,所以要重点掌握
自定义全局过滤器:1.实现GlobalFilter
, Ordered两个接口。分别实现Filter和getOrder方法,实现过滤器的具体逻辑和执行顺序。
在一般的开发中的权限认证/鉴权:1.客户端首次登陆,服务器对用户进行信息认证,颁发token作为之后的登陆凭证,之后客户端登陆通过token来鉴别是否有权限。
网关过滤算法:
1.计数器算法:
单位时间内请求有一个阈值,如果请求超过阈值,之后的请求都被拒绝,除非单位时间过去。
比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候, counter就加1,如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内,那么说明请求数过多; 如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter。
弊端:对于秒级以上的时间周期来说,会存在一个非常严重的问题,那就是临界问题。 如果时间单位是分钟,那么恶意攻击可以发生在1s内大量请求,压垮服务器
2.漏桶算法:
控制流量的速率,大流量打进来,也只是保留一定的数据,溢出一部分数据。
弊端:针对小数据量和稍大的数据量来说,速率是一定的,所以大流量吃亏。
3.令牌桶算法:
以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。
放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行。
随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS(每秒查询率)=100,则间隔是10ms)往桶里加入Token,那么限流器初始化完成一秒后,桶中就已经有100个令牌了
令牌桶算法生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。
这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,可以处理瞬时流量,而且拿令牌的过程并不是消耗很大的事情。令牌桶算法通常可以用于限制被访问的流量,保护自身系统。
gateway限流:
gateway的限流是通过内置的过滤器工厂 RequestRateLimiterGatewayFilterFactory。 通过Redis和lua脚本结合的方式进行流量控制。
限流的yml配置:
spring: application: name: springcloud-gateway redis: host: localhost port: 6379 database: 0 cloud: gateway: discovery: locator: enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_routh1 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://springcloud-payment-provider-service predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://springcloud-payment-provider-service predicates: - Path=/payment/getInfo/** #断言,路径相匹配的进行路由 filters: - name: RequestRateLimiter args: key-resolver: '#{@hostAddrKeyResolver}' redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 3
主要关注三个参数:
-
burstCapacity,令牌桶总容量。
-
replenishRate,令牌桶每秒填充平均速率。
-
key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
编写key-resolver:
@Configuration
public class KeyResolverConfig {
@Bean
public KeyResolver pathKeyResolver(){
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getPath().toString());
}
};
}
}
gateway的高可用:
nginx集群-->gateway集群--->微服务集群
涉及到断路器的三个重要参数:快照时间窗/时间范围、请求总数阈值、错误百分比阈值。
-
1、快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
-
2、请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
-
3、错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阈值情况下,这时候就会将断路器打开。