服务雪崩是指因服务提供者不可用导致服务调用者不可用,并将不可用状态在整体服务中逐渐放大的过程。
比如:
在服务中,A给B、C、D提供服务,而B、C、D有分别为其他服务提供(可以看为服务之间的互相调用形成一张调用网)。当其中一个服务停止工作后,那么与他所关联的服务就也会被动的停止服务,从而引起雪崩效应。
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。
1.服务提供者不可用
2.重试加大流量
在服务提供者不可用后,用户由于忍受不了界面上的长时间等待,而不断刷新页面,甚至提交表单。
服务的调用端存在大量服务异常后的重试逻辑。
3.服务调用者不可用(服务雪崩效应的每个阶段都可能由不同的原因造成)造成服务不可能的原因如下:
服务调用者不可用的主要原因是当服务调用者使用同步调用时,会产大量的线程等待占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是服务雪崩效应就产生了。
出现了雪崩问题,那么我们怎么去处理这样的问题呢?
超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待。
船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。与此类似,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
设置每个业务线程上限,不会导致一个业务把所有的资源都耗尽
对已经挂掉的服务,直接不再进行调用,而是直接返回结果,这就是熔断。以此避免已经挂掉的服务对调用者造成的影响。
断路器模式:由 断路器 统计业务执行的异常比例,如果超出阈值则会 熔断 该业务,拦截访问该业务的一切请求。断路器会统计访问某个服务的请求数量,异常比例 当发现访问服务的请求异常比例过高时,认为服务有导致雪崩的风险,会拦截访问服务的一切请求,形成熔断
流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。
可以使用Sentinel去进行限流
1.什么是雪崩问题?
微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。
2.限流 是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种 预防 措施。
3.超时处理、线程隔离、降级熔断 是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种 补救 措施。
http://t.csdnimg.cn/vLtnghttp://t.csdnimg.cn/vLtng
缓存雪崩是指大量的应用请求无法在Redis缓存中进行处理,从而使得大量请求发送到数据库层,导致数据库压力过大甚至宕机。
第一个原因:同一时间缓存中的数据大面积过期。
具体来说,把热点数据保存在缓存中,并且设置了过期时间,如果在某一个时刻,大量的Key同时过期,此时,应用再访问这些数据的话,就会发生缓存缺失。然后应用就会把请求发送给数据库,如果应用的并发请求量很大,(比如秒杀),那么数据库的压力也会很大,这会进一步影响到其他正常业务的请求处理。
第二个原因:Redis 缓存实例发生故障宕机。
针对大量数据集中失效带来的缓存雪崩问题,可以用下面几种方案解决:
- 均匀过期:给热点数据设置不同的过期时间,给每个key的失效时间加一个随机值;
- 设置热点数据永不过期:不设置失效时间,有更新的话,需要更新缓存;
- 服务降级:指服务针对不同的数据采用不同的处理方式:
- 业务访问的是非核心数据,直接返回预定义信息、空值或者报错;
- 业务访问核心数据,则允许访问缓存,如果缓存缺失,可以读取数据库。
解决Redis实例宕机问题
方案一: 实现服务熔断或者请求限流机制
我们通过监测Redis以及数据库实例所在服务器负载指标,如果发现Redis服务宕机,导致数据库的负载压力增大,我们可以启动服务熔断机制,暂停对缓存服务的访问。
但是这种方法对业务应用的影响比较大,我们也可以通过限流的方式降低这种影响。
举个例子:比如业务系统正常运行时,请求入口每秒最大允许进入的请求数是1万个,其中9000请求个可以被缓存处理,余下1000个会发送给数据库处理。
一旦发生雪崩,数据库每秒处理的请求突然增加到1万个,此时我们就可以启动限流机制。在前端请求入口处,只允许每秒进入1000个请求,其他的直接拒绝掉。这样就可以避免大量并发请求发送给数据库。
方案二:事前预防
通过主从节点的方式构建 Redis 缓存高可靠集群。 如果 Redis 缓存的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓存雪崩问题。
在spring cloud gateway中在yml中配置uri有三种方式,包括
#websocket配置方式
spring:
application:
name: song-gateway
cloud:
gateway:
routes:
- id: song-system
uri: ws://localhost:9201/
predicates:
- Path=/system/**
#http地址配置方式
spring:
application:
name: song-gateway
cloud:
gateway:
routes:
- id: song-system
uri: http://localhost:9201/
predicates:
- Path=/system/**
#注册中心配置方式
spring:
application:
name: song-gateway
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: song-system
uri: lb://song-system
predicates:
- Path=/system/**
#注意 此时 lb://注册中心的服务名
package com.atguigu.springcloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
@Bean
public RouteLocator customRouteLocator2(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route_atguigu2",
r -> r.path("/guoji")
.uri("http://news.baidu.com/guoji")).build();
return routes.build();
}
}
有时我们需要设置黑名单,来避免默写ip地址的访问。需要创建一个类 BlackListUrlFilter 继承AbstractGatewayFilterFactory
package com.aaa.demo1.filter;
import com.aaa.demo1.util.AjaxResult;
import com.alibaba.fastjson.JSON;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class BlackListUrlFilter extends AbstractGatewayFilterFactory {
/**
* 用来返回我们的自定义过滤器
* @param config
* @return
*/
@Override
public GatewayFilter apply(Object config) {
return (exchange,chain)->{
// 1 获取当前请求的ip地址 我们实际开发中 需要使用 IPUtils类 用来获取请求的真实IP
String ip = exchange.getRequest().getRemoteAddress().getHostString();
// 2 判断当前请求的ip是否在黑名单中 黑名单的收集 使用redis
if( "127.0.0.1".equals(ip) ) {
// 3 如果是黑名单 则进行拦截
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] bytes = JSON.toJSONBytes( AjaxResult.fail("对不起 您的IP禁止访问") );
DataBuffer wrap = response.bufferFactory().wrap(bytes);
Mono just = Mono.just(wrap);
return response.writeWith(just);
}
// 4 如果不在则放行
return chain.filter(exchange);
};
}
}
我们的使用拦截器去实现类如登陆验证业务时,在每个访问请求中都会被拦截验证,而我们正常的登录请求也会被拦截导致不能正藏登录,所以对于这样的请求我们需要放行。
当有请求进入(并与路由匹配)时,过滤Web处理程序会将GlobalFilter的所有实例和GatewayFilter的所有特定于路由的实例添加到过滤器链中。该组合的过滤器链通过org.springframework.core.Ordered接口排序,可以通过实现getOrder()方法进行设置。
全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,
比如权限认证,IP访问限制等等。单独定义只需要实现GlobalFilter, Ordered这两个接口就可以了。
package com.aaa.demo1.filter;
import com.aaa.demo1.properties.IgnoreWhiteProperties;
import com.aaa.demo1.util.AjaxResult;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private IgnoreWhiteProperties ignoreWhite;
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
// 跳过不需要验证的路径
if (ignoreWhite.getWhites().contains(url))
{
return chain.filter(exchange);
}
//从请求参数中获取token 用在 app中
// String token = exchange.getRequest().getQueryParams().getFirst("token");
// 从请求头中获取 token
String token = exchange.getRequest().getHeaders().getFirst("token");
if (null == token) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONBytes(AjaxResult.fail("对不起 token不能为null")));
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
在其中我们通过配置文件配置了白名单
其中创建配置类为
package com.aaa.demo1.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "ignore")
public class IgnoreWhiteProperties
{
/**
* 放行白名单配置,网关不校验此处的白名单
*/
private List whites = new ArrayList<>();
public List getWhites()
{
return whites;
}
public void setWhites(List whites)
{
this.whites = whites;
}
}
配置文件application.yml
ignore:
whites:
- /auth/logout
- /auth/login
- /*/v2/api-docs
- /user/stu
白名单的实现按照以下步骤:
1.我们现在配置文件中添加了自定义的白名单ignore。
2.创建了配置类IgnoreWhiteProperties用来获取配置文件
3.在拦截器AuthFilter中判断是否访问路径为白名单来进行是否放行
if (ignoreWhite.getWhites().contains(url)) { return chain.filter(exchange); }