SpringCloud03

2023-12-20 18:51:33

1.服务雪崩以及解决方案

1.1什么是服务雪崩

????????服务雪崩是指因服务提供者不可用导致服务调用者不可用,并将不可用状态在整体服务中逐渐放大的过程。

比如:

? ? ? ? 在服务中,A给B、C、D提供服务,而B、C、D有分别为其他服务提供(可以看为服务之间的互相调用形成一张调用网)。当其中一个服务停止工作后,那么与他所关联的服务就也会被动的停止服务,从而引起雪崩效应。

1.2雪崩问题

微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。

1.3服务雪崩形成的原因

1.服务提供者不可用
2.重试加大流量

在服务提供者不可用后,用户由于忍受不了界面上的长时间等待,而不断刷新页面,甚至提交表单。
服务的调用端存在大量服务异常后的重试逻辑。
3.服务调用者不可用(服务雪崩效应的每个阶段都可能由不同的原因造成)造成服务不可能的原因如下:

  1. 硬件故障可能为硬件损坏造成的服务器主机死机,网络硬件故障造成的服务提供者的不可访问。
  2. 缓存击穿一般发生在缓存应用重启,所有缓存被清空时,以及短时间内大量的缓存失效时。大量的缓存不命中,使请求直接访问后端,造成服务提供者超负荷运行,引起服务不可用。
  3. 在秒杀和大促开始前,如果准备不充分,使用户发起大量的请求,也会造成服务的不可用。

????????服务调用者不可用的主要原因是当服务调用者使用同步调用时,会产大量的线程等待占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是服务雪崩效应就产生了。

出现了雪崩问题,那么我们怎么去处理这样的问题呢?

1.4解决方案

1.4.1超时处理

超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待。

1.4.2?线程隔离(舱壁模式)

????????船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。与此类似,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。

设置每个业务线程上限,不会导致一个业务把所有的资源都耗尽

1.4.3熔断器(断路器)

????????对已经挂掉的服务,直接不再进行调用,而是直接返回结果,这就是熔断。以此避免已经挂掉的服务对调用者造成的影响。

断路器模式:由?断路器?统计业务执行的异常比例,如果超出阈值则会?熔断?该业务,拦截访问该业务的一切请求。断路器会统计访问某个服务的请求数量,异常比例??当发现访问服务的请求异常比例过高时,认为服务有导致雪崩的风险,会拦截访问服务的一切请求,形成熔断

1.4.4限流

流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。

可以使用Sentinel去进行限流

1.5总结

1.什么是雪崩问题?

微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。

2.限流 是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种 预防 措施。

3.超时处理、线程隔离、降级熔断 是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种 补救 措施。

2.缓存雪崩以及解决方案

http://t.csdnimg.cn/vLtngicon-default.png?t=N7T8http://t.csdnimg.cn/vLtng

2.1什么是缓存雪崩

缓存雪崩是指大量的应用请求无法在Redis缓存中进行处理,从而使得大量请求发送到数据库层,导致数据库压力过大甚至宕机。

2.2缓存雪崩的原因

第一个原因:同一时间缓存中的数据大面积过期。

????????具体来说,把热点数据保存在缓存中,并且设置了过期时间,如果在某一个时刻,大量的Key同时过期,此时,应用再访问这些数据的话,就会发生缓存缺失。然后应用就会把请求发送给数据库,如果应用的并发请求量很大,(比如秒杀),那么数据库的压力也会很大,这会进一步影响到其他正常业务的请求处理。

第二个原因:Redis 缓存实例发生故障宕机。

2.3解决方案

针对大量数据集中失效带来的缓存雪崩问题,可以用下面几种方案解决:

  • 均匀过期:给热点数据设置不同的过期时间,给每个key的失效时间加一个随机值;
  • 设置热点数据永不过期:不设置失效时间,有更新的话,需要更新缓存;
  • 服务降级:指服务针对不同的数据采用不同的处理方式:
    • 业务访问的是非核心数据,直接返回预定义信息、空值或者报错;
    • 业务访问核心数据,则允许访问缓存,如果缓存缺失,可以读取数据库。

解决Redis实例宕机问题

方案一: 实现服务熔断或者请求限流机制

我们通过监测Redis以及数据库实例所在服务器负载指标,如果发现Redis服务宕机,导致数据库的负载压力增大,我们可以启动服务熔断机制,暂停对缓存服务的访问。

但是这种方法对业务应用的影响比较大,我们也可以通过限流的方式降低这种影响。

举个例子:比如业务系统正常运行时,请求入口每秒最大允许进入的请求数是1万个,其中9000请求个可以被缓存处理,余下1000个会发送给数据库处理。

一旦发生雪崩,数据库每秒处理的请求突然增加到1万个,此时我们就可以启动限流机制。在前端请求入口处,只允许每秒进入1000个请求,其他的直接拒绝掉。这样就可以避免大量并发请求发送给数据库。

方案二:事前预防

通过主从节点的方式构建 Redis 缓存高可靠集群。 如果 Redis 缓存的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓存雪崩问题。

3.网关如何配置路由

3.1使用yml配置

在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://注册中心的服务名

3.2使用Java配置?

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();
    }
}

4.白名单如何实现

4.1设置黑名单

? ? ? ? 有时我们需要设置黑名单,来避免默写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<DataBuffer> just = Mono.just(wrap);
                return   response.writeWith(just);

            }

            // 4 如果不在则放行
            return  chain.filter(exchange);
        };
    }

}

4.2设置白名单

????????我们的使用拦截器去实现类如登陆验证业务时,在每个访问请求中都会被拦截验证,而我们正常的登录请求也会被拦截导致不能正藏登录,所以对于这样的请求我们需要放行。

当有请求进入(并与路由匹配)时,过滤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<Void> 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<String> whites = new ArrayList<>();

    public List<String> getWhites()
    {
        return whites;
    }

    public void setWhites(List<String> whites)
    {
        this.whites = whites;
    }
}

配置文件application.yml

ignore:
  whites:
    - /auth/logout
    - /auth/login
    - /*/v2/api-docs
    - /user/stu

4.3总结

? ? ? ? 白名单的实现按照以下步骤:

1.我们现在配置文件中添加了自定义的白名单ignore。

2.创建了配置类IgnoreWhiteProperties用来获取配置文件

3.在拦截器AuthFilter中判断是否访问路径为白名单来进行是否放行

if (ignoreWhite.getWhites().contains(url))
{
    return chain.filter(exchange);
}

文章来源:https://blog.csdn.net/g877835148/article/details/135109697
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。