微服务保护

发布时间:2024年01月23日

目录

初识Sentinel

雪崩问题

认识Sentinel

安装Sentinel控制台

引入cloud-demo

微服务整合sentinel

限流规则

簇点链路

快速入门

流控模式

流控模式-关联

流控模式-链路?

流控效果

流控效果-warm up

流控模式-排队等待

热点参数限流

隔离和降级

隔离和降级

Feign整合sentinel

线程隔离

熔断降级

熔断策略-慢调用

熔断策略-异常比例、异常数

授权规则及规则持久化

授权规则

自定义异常结果

规则管理模式

实现push模式


什么是雪崩问题?
1、微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。
如何避免因瞬间高并发流量而导致服务故障?

1、流量控制
如何避免因服务故障引起的雪崩问题?

1、超时处理
2、线程隔离

3、降级熔断


流控模式有哪些?
1、直接:对当前资源限流
2、关联:高优先级资源触发阈值,对低优先级资源限流。

3、链路:阈值统计时,只统计从指定资源进入当前资源的请求,是对请求来源的限流


流控效果有哪些?
1、快速失败:QPS超过阈值时,拒绝新的请求
2、warm up:QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提升的,可以避免冷启动时高并发导致服务宕机。
3、排队等待:请求会进入队列,按照阈值允许的时间间隔依次执行请求;如果请求预期等待时长大于超时时间,直接拒绝


Sentinel支持的雪崩解决方案:
1、线程隔离(仓壁模式)
2、降级熔断
Feign整合Sentinel的步骤:
1、在application.yml中配置:feign.sentienl.enable=true
2、给Feignclient编写FallbackFactory并注册为Bean
3、将FallbackFactory配置到FeignClient


线程隔离的两种手段是?
1、信号量隔离
2、线程池隔离
信号量隔离的特点是?
1、基于计数器模式,简单,开销小
线程池隔离的特点是?
1、基于线程池模式,有额外开销,但隔离控制更强


sentinel熔断降级的策略有哪些?
1、慢调用比例:超过指定时长的调用为慢调用,统计单位时长内慢调用的比例,超过阈值则熔断
2、异常比例:统计单位时长内异常调用的比例,超过阈值则熔断
3、常数:统计单位时长内异常调用的次数,超过阈值则熔断


获取请求来源的接口是什么?

1、Request0riginParser
处理BlockException的接口是什么?

1、BlockExceptionHandler


sentinel的三种配置管理模式是什么?
1、原始模式:保存在内存
2、pull模式:存在本地文件或数据库,定时去读取

3、push模式:保存在nacos,监听变更实时更新

初识Sentinel

雪崩问题

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

解决雪崩问题的常见方式有四种:?

1、超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待2、舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
3、熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。

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

认识Sentinel

Sentinel是阿里巴巴开源的一款微服务流量控制组件。

Sentinel具有以下特征:

1、丰富的应用场景:Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
2、完备的实时监控:Sentinel同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况。
3、广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块,例如与Spring Cloud、Dubbo、gRPC的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
4、完善的SPI扩展点:Sentinel提供简单易用、完善的SPI扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

安装Sentinel控制台

将该文件下载并拷贝到一个非中文路径的目录

sentinel-dashboard-1.8.1icon-default.png?t=N7T8https://pan.baidu.com/s/13FRnAXR7QTN5BexD5Zb5qQ?pwd=gil1

在该目录下cmd进入控制台运行以下命令

java -jar sentinel-dashboard-1.8.1.jar

然后访问localhost:8080,输入账号和密码,都是sentinel

如果要修改sentinel的默认端口、账号、密码,可以通过下列配置:

该命令启动时,使用的端口就成了8090了

java -jar sentinel-dashboard-1.8.1.jar -Dserver.port=8090

配置项默认值说明
server.port8080服务端口
sentinel.dashboard.auth.usernamesentinel默认用户名
sentinel.dashboard.auth.passwordsentinel默认密码

引入cloud-demo

下载该代码,用以前的那个cloud-demo也可以,不过可能需要修改一些代码:

1、所有yml文件的nacos:8848改成localhost:8848

2、所有yml文件的mysql:3306改成localhost:3306

3、有一个服务的端口是8080,这会与sentinel的端口发生冲突,需要改成其他端口

cloud-demoicon-default.png?t=N7T8https://pan.baidu.com/s/1_a4YzJr6R_EsRK6Z9tievQ?pwd=7rg1

把nacos和刚下载的sentinel启动后,再把该项目的三个服务启动,否则会报错?

微服务整合sentinel

1、引入sentinel依赖

在order-service的pom文件,导入依赖

<!--引入sentinel依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--eureka客户依赖-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!--        </dependency>-->
        <!-- nacos客户端依赖包 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--feign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--引入HttpClient依赖-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>
        <!--引入feign统一的api-->
        <dependency>
            <groupId>cn.itcast.demo</groupId>
            <artifactId>feign-api</artifactId>
            <version>1.0</version>
        </dependency>
        <!--引入sentinel依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>

    </dependencies>
    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2、配置控制台地址

sentinel:
  transport:
    dashboard: localhost:8080 #sentinel控制台地址
server:
  port: 8088
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 1234
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: orderservice #order的微服务名称
  cloud:
    nacos:
      server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080 #sentinel控制台地址
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848 #nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow #规则类型。还可以是:degrade,authority,param-flow
#      discovery:
#        cluster-name: HZ #集群名称
##        namespace: 8279562b-ce89-420a-b765-f8b2adfdbe49 #命名空间id
#        ephemeral: false #是否为临时实例
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
#eureka:
#  client:
#    service-url: #eureka地址信息1
#      defaultZone: http://127.0.0.1:10086/eureka
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则:随机。范围:userservice服务
ribbon:
  eager-load:
    enabled: true #开启饥饿加载
    clients:  #指定饥饿加载的服务
      - userservice
#feign:
#  client:
#    config:
#      default: #这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
#        loggerLevel: FULL #日志级别
feign:
  httpclient:
    enable: true #支持httpClient的开关
    max-connections: 200 #最大连接数
    max-connections-per-route: 50 #单个路径的最大连接数

3、访问微服务的任意端点,触发sentinel监控?

先访问服务的数据

再刷新查看sentinel控制台的内容

限流规则

簇点链路

簇点链路:就是项目内的调用链路,链路中被监控的每个接口就是一个资源。默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint),因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。

流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:
?

快速入门

点击资源/order/[orderld}后面的流控按钮,就可以弹出表单。表单中可以添加流控规则,如下图所示:

其含义是限制/order/{orderld}这个资源的单机QPS为5,即每秒只允许5次请求,超出的请求会被拦截并报错。

例如:现在限制为5,我在一秒内访问10次,就会有5个通过,5 个拒绝

流控模式

在添加限流规则时,点击高级选项,可以选择三种流控模式:

直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式

关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

流控模式-关联

关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流

使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是有限支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。

当/order/update资源访问量触发阈值时,就会对/order/query资源限流,避免影响/order/update资源。

在orderController.java添加代码

@GetMapping("/query")
public String queryOrder(){
    //查询订单
    System.out.println("查询订单");
    return "查询订单成功";
}
@GetMapping("/update")
public String updateOrder(){
    return "更新订单成功";
}
package cn.itcast.order.web;

import cn.itcast.order.pojo.Order;
import cn.itcast.order.service.OrderService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("order")
public class OrderController {

   @Autowired
   private OrderService orderService;

    GetMapping("{orderId}")
    public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
        //根据id查询订单并返回
        return orderService.queryOrderById(orderId);
    }

    @GetMapping("/query")
    public String queryOrder(){
        //查询订单
        System.out.println("查询订单");
        return "查询订单成功";
    }


    @GetMapping("/update")
    public String updateOrder(){
        return "更新订单成功";
    }
}

重启orderservice,并访问localhost:8088/order/update和localhost:8088/order/query

再刷新sentinel控制台,会发现新增了:/order/update和/order/query两条资源名

给谁限流,就给谁加流控。要给query限流,就给它加流控

当/order/update资源访问量触发阈值时,就会对/order/query资源限流,避免影响/order/update资源。

?

此时会看到流控规则新增了一条

此时,每秒访问10次update,都是成功的,但此时的query被限流了,无法访问?

流控模式-链路?

链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。

例如:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查询商品的请求统计,并设置限流。

步骤:

1、在orderService中添加一个queryGoods方法,不用实现业务

public void queryGoods(){
    System.err.println("查询商品");
}
package cn.itcast.order.service;

import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        // 2.用Feign远程调用
        User user = userClient.findById(order.getUserId());
        // 3.封装user到order
        order.setUser(user);
        // 4.返回
        return order;
    }

 
    public void queryGoods(){
        System.err.println("查询商品");
    }

//    @Autowired
//    private RestTemplate restTemplate;
//
//    public Order queryOrderById(Long orderId) {
//        // 1.查询订单
//        Order order = orderMapper.findById(orderId);
//        // 2.利用RestTemplate发送http请求,查询用户
//        // 2.1.url路径
//        String url = "http://userservice/user/" + order.getUserId();
//        // 2.2.发送http请求,实现远程调用
//        User user = restTemplate.getForObject(url, User.class);//第一个参数是路径,第二个参数是返回的类=类型
//        // 3.封装user到order
//        order.setUser(user);
//        // 4.返回
//        return order;
//    }
}

2、在orderController中,改造/order/query端点,调用orderService中的queryGoods方法

3、在orderController中添加一个/order/save的端点,调用orderService的queryGoods方法

@GetMapping("/query")
public String queryOrder(){
    //查询商品
    orderService.queryGoods();
    //查询订单
    System.out.println("查询订单");
    return "查询订单成功";
}
@GetMapping("/save")
public String saveOrder(){
    //查询商品
    orderService.queryGoods();
    //查询订单
    System.out.println("新增订单");
    return "新增订单成功";
}
package cn.itcast.order.web;

import cn.itcast.order.pojo.Order;
import cn.itcast.order.service.OrderService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("order")
public class OrderController {

   @Autowired
   private OrderService orderService;

    GetMapping("{orderId}")
    public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
        //根据id查询订单并返回
        return orderService.queryOrderById(orderId);
    }

    @GetMapping("/query")
    public String queryOrder(){
        //查询商品
        orderService.queryGoods();
        //查询订单
        System.out.println("查询订单");
        return "查询订单成功";
    }

    @GetMapping("/save")
    public String saveOrder(){
        //查询商品
        orderService.queryGoods();
        //查询订单
        System.out.println("新增订单");
        return "新增订单成功";
    }


    @GetMapping("/update")
    public String updateOrder(){
        return "更新订单成功";
    }
}

注意:因为sentinel只会监控controller的内容,但是我的controller都会访问orderService的内容,我需要监控service的内容。此时需要两步:

第一步:需要利用@SentinelResource注解

@SentinelResource("goods")
package cn.itcast.order.service;

import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        // 2.用Feign远程调用
        User user = userClient.findById(order.getUserId());
        // 3.封装user到order
        order.setUser(user);
        // 4.返回
        return order;
    }

    @SentinelResource("goods")
    public void queryGoods(){
        System.err.println("查询商品");
    }

//    @Autowired
//    private RestTemplate restTemplate;
//
//    public Order queryOrderById(Long orderId) {
//        // 1.查询订单
//        Order order = orderMapper.findById(orderId);
//        // 2.利用RestTemplate发送http请求,查询用户
//        // 2.1.url路径
//        String url = "http://userservice/user/" + order.getUserId();
//        // 2.2.发送http请求,实现远程调用
//        User user = restTemplate.getForObject(url, User.class);//第一个参数是路径,第二个参数是返回的类=类型
//        // 3.封装user到order
//        order.setUser(user);
//        // 4.返回
//        return order;
//    }
}

第二步:?Sentinel默认会将Controller方法做context整合,导致链路模式的流控失效,需要修改application.yml添加配置

web-context-unify: false #关闭context整合
server:
  port: 8088
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 1234
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: orderservice #order的微服务名称
  cloud:
    nacos:
      server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080 #sentinel控制台地址
      web-context-unify: false #关闭context整合
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848 #nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow #规则类型。还可以是:degrade,authority,param-flow
#      discovery:
#        cluster-name: HZ #集群名称
##        namespace: 8279562b-ce89-420a-b765-f8b2adfdbe49 #命名空间id
#        ephemeral: false #是否为临时实例
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
#eureka:
#  client:
#    service-url: #eureka地址信息1
#      defaultZone: http://127.0.0.1:10086/eureka
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则:随机。范围:userservice服务
ribbon:
  eager-load:
    enabled: true #开启饥饿加载
    clients:  #指定饥饿加载的服务
      - userservice
#feign:
#  client:
#    config:
#      default: #这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
#        loggerLevel: FULL #日志级别
feign:
  httpclient:
    enable: true #支持httpClient的开关
    max-connections: 200 #最大连接数
    max-connections-per-route: 50 #单个路径的最大连接数
 

重启服务,再访问localhost:8088/order/query和localhost:8088/order/save,刷新sentinel控制台。

可以看到save和query变成了两个独立的资源名。这是因为刚才的第二步的原因

这两个goods其实是同一个,所以随便点一个,进行流控。

4、给queryGoods设置限流规则,从/order/query进入queryGoods的方法限制QPs必须小于2

监控从/order/query进来的goods,限制每秒2个。但是从/order/save进来的goods没有限制。

?

每秒给?/order/query和/order/save各访问4个。可以看到每秒有6个通过,2个拒绝

流控效果

流控效果是指请求达到流控阈值时应该采取的措施,包括三种:

1、快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
2、warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
3、排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长

流控效果-warm up

warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是threshold / coldFactor,持续指定时长后,逐渐提高到threshold值。而coldFactor的默认值是3

例如:给/order/{orderld}这个资源设置限流,最大QPs为10,利用warm up效果,预热时长为5秒

可以看到新增了一条规则

每秒访问10个,可以看到刚开始只能访问3个,其余的被拒绝,慢慢的可以访问到10个

流控模式-排队等待

当请求超过QPs阈值时,快速失败和warm up会拒绝新的请求并抛出异常。而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。

比如:QPS=5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待超过2000ms的请求会被拒绝并抛出异常

例如:给/order/{orderld}这个资源设置限流,最大QPs为10,利用排队的流控效果,超时时长设置为5s

给上次配的warm up进行编辑,使用排队等待

可以看到warm up的规则发生了改变,变成了排队等待

每秒访问15个,可以看到,并没有出现拒绝,最多10个,因为进行了排队等待,但是响应时间挺长的

热点参数限流

之前的限流是统计访问某个资源的所有请求,判断是否超过QPs阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPs阈值。

例如:给/order/{orderld}这个资源添加热点参数限流,规则如下:

1、默认的热点参数规则是每1秒请求量不超过2
2、给102这个参数设置例外:每1秒请求量不超过4

3、给103这个参数设置例外:每1秒请求量不超过10

注意:热点参数限流对默认的SpringMVC资源无效。需要加注解,起名为hot

@SentinelResource("hot")
package cn.itcast.order.web;

import cn.itcast.order.pojo.Order;
import cn.itcast.order.service.OrderService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("order")
public class OrderController {

   @Autowired
   private OrderService orderService;

   @SentinelResource("hot")
    @GetMapping("{orderId}")
    public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
        // 根据id查询订单并返回
        return orderService.queryOrderById(orderId);
    }

    @GetMapping("/query")
    public String queryOrder(){
        //查询商品
        orderService.queryGoods();
        //查询订单
        System.out.println("查询订单");
        return "查询订单成功";
    }

    @GetMapping("/save")
    public String saveOrder(){
        //查询商品
        orderService.queryGoods();
        //查询订单
        System.out.println("新增订单");
        return "新增订单成功";
    }

    @GetMapping("/update")
    public String updateOrder(){
        return "更新订单成功";
    }
}

重启服务,并访问localhost:8088/order/101,此时可以看到该资源名

按要求配置

每秒给三个请求发送5个,可以看到,最高的时候为11,因为一个限制了2,一个限制了4,一个限制了10,所以是2+4+5=11

隔离和降级

隔离和降级

虽然限流可以尽量避免因高并发而引起的服务故障,但服务还会因为其它原因而故障。而要将这些故障控制在一定范围,避免雪崩,就要靠线程隔离(舱壁模式)和熔断降级手段了。
不管是线程隔离还是熔断降级,都是对客户端(调用方)的保护。

Feign整合sentinel

SpringCloud中,微服务调用都是通过Feign来实现的,因此做客户端保护必须整合Feign和Sentinel。

1、修改OrderService的application.yml文件,开启Feign的Sentinel功能

feign:
  sentinel:
    enabled: true #开启Feign对sentinel的支持
server:
  port: 8088
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 1234
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: orderservice #order的微服务名称
  cloud:
    nacos:
      server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080 #sentinel控制台地址
      web-context-unify: false #关闭context整合
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848 #nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow #规则类型。还可以是:degrade,authority,param-flow
#      discovery:
#        cluster-name: HZ #集群名称
##        namespace: 8279562b-ce89-420a-b765-f8b2adfdbe49 #命名空间id
#        ephemeral: false #是否为临时实例
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
#eureka:
#  client:
#    service-url: #eureka地址信息1
#      defaultZone: http://127.0.0.1:10086/eureka
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则:随机。范围:userservice服务
ribbon:
  eager-load:
    enabled: true #开启饥饿加载
    clients:  #指定饥饿加载的服务
      - userservice
#feign:
#  client:
#    config:
#      default: #这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
#        loggerLevel: FULL #日志级别
feign:
  httpclient:
    enable: true #支持httpClient的开关
    max-connections: 200 #最大连接数
    max-connections-per-route: 50 #单个路径的最大连接数
  sentinel:
    enabled: true #开启Feign对sentinel的支持

2、给FeignClient编写失败后的降级逻辑
方式一:FallbackClass,无法对远程调用的异常做处理
方式二:FallbackFactory,可以对远程调用的异常做处理,我们选择这种

步骤一:在feing-api的模块中的cn.itcast.feign的clients新建fallback包,新建UserClientFallbackFactory.java

package cn.itcast.feign.clients.fallback;

import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable throwable) {
        return new UserClient() {
            @Override
            public User findById(Long id) {
                log.error("查询用户异常",throwable);
                return new User();
            }
        };
    }
}

步骤二:在feing-api模块中的DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean

@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
    return new UserClientFallbackFactory();
}
package cn.itcast.feign.config;

import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level loglevel(){
        return Logger.Level.BASIC;
    }

    @Bean
    public UserClientFallbackFactory userClientFallbackFactory(){
        return new UserClientFallbackFactory();
    }
}

步骤三:在feing-api模块中的UserClient接口中使用UserClientFallbackFactory

注意:在cloud-demo的pom文件中,找到<spring-cloud.version></spring-cloud.version>,把它写上<spring-cloud.version>Hoxton.SR8</spring-cloud.version>。不然会因为版本问题报错

package cn.itcast.feign.clients;

import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
import cn.itcast.feign.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

重启服务,并访问localhost:8088/order/101,再刷新sentinel控制台。可以看到GET的这个链路,这就是Feign的请求路径

线程隔离

线程隔离有两种方式实现:

线程池隔离

优点:支持主动超时、支持异步调用

缺点:线程的额外开销比较大

场景:低扇出
信号量隔离(Sentinel默认采用)

优点:轻量级、无额外开销

缺点:不支持主动超时、不支持异步调用

场景:高频调用、高扇出

在添加限流规则时,可以选择两种阈值类型:

QPS:就是每秒的请求数,在快速入门中已经演示过
线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现舱壁模式

例如:给UserClient的查询用户接口设置流控规则,线程数不能超过2。

对GET://http://userservice/user/{id}添加流控管理

?

一瞬间发送10次请求,可以看到前2个请求正常,但是后面的请求都是空而没有报错。因为UserClientFallbackFactory.java里已经写了降级逻辑,当访问被拒绝时,返回空的内容。

熔断降级

熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,
如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

断路器熔断策略有三种:慢调用、异常比例、异常数

熔断策略-慢调用

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。

例如:给UserClient的查询用户接口设置降级规则,慢调用的RT阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5

提示:为了触发慢调用规则,我们需要修改UserService中的业务,增加业务耗时

在user-service的模块中,UserController.java里,修改为该代码

@GetMapping("/{id}")                                  //请求头      变量名为Truth     可以不传
public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth",required = false) String truth) throws InterruptedException {
    if(id == 1){
        //休眠,触发熔断
        Thread.sleep(60);
    }
    return userService.queryById(id);
}
package cn.itcast.user.web;

import cn.itcast.user.config.PatternProperties;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

import java.text.DateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@RestController
@RequestMapping("/user")
//@RefreshScope
public class UserController {

    @Autowired
    private UserService userService;

//    @Value("${pattern.dateformat}")
//    //注解读取该配置
//    private String dateformat;

    @Autowired
    private PatternProperties properties;//注入新的java类

    @GetMapping("prop")
    public  PatternProperties properties(){
        return properties;
    }

    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformat()));//通过该配置改变时间格式
    }

    /**
     * 路径: /user/110
     *
     * @param id 用户id
     * @return 用户
     */
    @GetMapping("/{id}")                                  //请求头      变量名为Truth     可以不传
    public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth",required = false) String truth) throws InterruptedException {
        if(id == 1){
            //休眠,触发熔断
            Thread.sleep(60);
        }
        return userService.queryById(id);
    }
}

重启服务,访问localhost:8088/order/101,把以前的GET://http://userservice/user/{id}流控规则删掉。

给GET://http://userservice/user/{id}配置降级

可以看到新增的规则

一秒内请求5次localhost:8088/order/101,之后再请求localhost:8088/order/102会发现数据为空

熔断策略-异常比例、异常数

异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

例如:给UserClient的查询用户接口设置降级规则,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5S

提示:为了触发异常统计,我们需要修改UserService中的业务,抛出异常:

@GetMapping("/{id}")                                  //请求头      变量名为Truth     可以不传
public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth",required = false) String truth) throws InterruptedException {
    if(id == 1){
        //休眠,触发熔断
        Thread.sleep(60);
    }else if(id == 2){
        throw new RuntimeException("故意出错,触发熔断");
    }
    return userService.queryById(id);
}
package cn.itcast.user.web;

import cn.itcast.user.config.PatternProperties;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

import java.text.DateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@RestController
@RequestMapping("/user")
//@RefreshScope
public class UserController {

    @Autowired
    private UserService userService;

//    @Value("${pattern.dateformat}")
//    //注解读取该配置
//    private String dateformat;

    @Autowired
    private PatternProperties properties;//注入新的java类

    @GetMapping("prop")
    public  PatternProperties properties(){
        return properties;
    }

    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformat()));//通过该配置改变时间格式
    }

    /**
     * 路径: /user/110
     *
     * @param id 用户id
     * @return 用户
     */
    @GetMapping("/{id}")                                  //请求头      变量名为Truth     可以不传
    public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth",required = false) String truth) throws InterruptedException {
        if(id == 1){
            //休眠,触发熔断
            Thread.sleep(60);
        }else if(id == 2){
            throw new RuntimeException("故意出错,触发熔断");
        }
        return userService.queryById(id);
    }
}

重启服务,访问localhost:8088/order/101,把以前的GET://http://userservice/user/{id}流控规则删掉。

给GET://http://userservice/user/{id}配置降级

访问5次localhost:8088/order/102,触发熔断规则,再访问localhost:8088/order/103,会发现数据也为空

?

授权规则及规则持久化

授权规则

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。

1、白名单:来源(origin)在白名单内的调用者允许访问
2、黑名单:来源(origin)在黑名单内的调用者不允许访问

例如,我们尝试从request中获取一个名为origin的请求头,作为origin的值

在order-service模块中的cn.itcast.order中新增一个sentinel包,新增HeaderOriginParser.java

package cn.itcast.order.sentinel;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import io.netty.util.internal.StringUtil;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        //1、获取请求头
        String origin = request.getHeader("origin");
        //2、非空判断
        if(StringUtils.isEmpty(origin)){
            origin = "blank";
        }
        return origin;
    }
}

需要在gateway服务中,利用网关的过滤器添加名为gateway的origin头

- AddRequestHeader=origin,gateway #添加情求头
server:
  port: 10010 #网关端口
spring:
  application:
    name: gateway #服务名称
  cloud :
    nacos:
      server-addr: localhost:8848 #nacos地址
    gateway:
      routes: #网关路由配置
        - id: user-service #路由id,自定义,只要唯一即可
        # uri: http://127.0.0.1:8081 #路由的目标地址 http就是固定地址
          uri: lb://userservice #路由的目标地址lb就是负载均衡,后面跟服务名称
          predicates: #路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求
#          filters:
#            - AddRequestHeader=Truth,Itcast is freaking awesome! #添加请求头
        - id: order-service #路由id,自定义,只要唯一即可
          uri: lb://orderservice #路由的目标地址lb就是负载均衡,后面跟服务名称
          predicates: #路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/order/** #这个是按照路径匹配,只要以/user/开头就符合要求
#            - After=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] #要求访问时间在这个之后
            - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] #要求访问时间在这个之后
      default-filters: #默认过滤器,会对所有的路由请求都生效
        - AddRequestHeader=Truth,Itcast is freakina awesome! #添加情求头
        - AddRequestHeader=origin,gateway #添加情求头
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

重启服务,访问localhost:8088/order/101给/order/{orderld}配置授权规则

访问?localhost:8088/order/103报错,访问localhost:8088/order/103?authorization=admin成功

自定义异常结果

默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。如果要自定义异常时的返回结果,需要实现BlockExceptionHandler接口

在order-service模块中的cn.itcast.order中的sentinel包,新增SentinelExceptionHandler.java

package cn.itcast.order.sentinel;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;

        if (e instanceof FlowException) {
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有权限访问";
            status = 401;
        }

        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
    }
}

重启服务,?访问?localhost:8088/order/103,给/order/{orderId}添加流控规则

访问2次localhost:8088/order/103,出现限流

把刚配的限流规则删掉,新增授权规则

?

这次为没有权限?

规则管理模式

sentinel的控制台规则管理有三种模式:

1、原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失

2、pull模式
3、push模式

pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。

push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。

实现push模式

push模式实现最为复杂,依赖于nacos,并且需要修改Sentinel控制台源码。

1、引入依赖

在order-service中引入sentinel监听nacos的依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--eureka客户依赖-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!--        </dependency>-->
        <!-- nacos客户端依赖包 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--feign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--引入HttpClient依赖-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>
        <!--引入feign统一的api-->
        <dependency>
            <groupId>cn.itcast.demo</groupId>
            <artifactId>feign-api</artifactId>
            <version>1.0</version>
        </dependency>
        <!--引入sentinel依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

配置nacos地址

在order-service中的application.yml文件配置nacos地址及监听的配置信息

spring:
  cloud:
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848 #nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow #规则类型。还可以是:degrade,authority,param-flow
server:
  port: 8088
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 1234
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: orderservice #order的微服务名称
  cloud:
    nacos:
      server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080 #sentinel控制台地址
      web-context-unify: false #关闭context整合
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848 #nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow #规则类型。还可以是:degrade,authority,param-flow
#      discovery:
#        cluster-name: HZ #集群名称
##        namespace: 8279562b-ce89-420a-b765-f8b2adfdbe49 #命名空间id
#        ephemeral: false #是否为临时实例
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
#eureka:
#  client:
#    service-url: #eureka地址信息1
#      defaultZone: http://127.0.0.1:10086/eureka
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则:随机。范围:userservice服务
ribbon:
  eager-load:
    enabled: true #开启饥饿加载
    clients:  #指定饥饿加载的服务
      - userservice
#feign:
#  client:
#    config:
#      default: #这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
#        loggerLevel: FULL #日志级别
feign:
  httpclient:
    enable: true #支持httpClient的开关
    max-connections: 200 #最大连接数
    max-connections-per-route: 50 #单个路径的最大连接数
  sentinel:
    enabled: true #开启Feign对sentinel的支持

重启服务

下载该文件,并把它放到跟sentinel-dashboard-1.8.1同一目录下

sentinel-dashboard.jaricon-default.png?t=N7T8https://pan.baidu.com/s/1MLvwJiHHrntl9vals3yFNQ?pwd=egh4

在该目录下cmd进入控制台运行以下命令

java -jar -Dnacos.addr=localhost:8848 sentinel-dashboard.jar

访问loaclhost:8848,进入nacos,访问localhost:8088/order/103,?刷新sentinel控制台,可以看到新的列表

在这里新增的流控规则会作用到nacos,点新增

?

此时刷新nacos,可以看到新的配置

访问localhost:8088/order/103,发现成功限流

再重启服务,看sentinel控制台的规则是否被重置

可以看到规则还在,规则持久化完成?

代码文件,点击下载icon-default.png?t=N7T8https://pan.baidu.com/s/1sdroaa2GxZ_L3nq9BKqLzQ?pwd=i0et

上一篇:数据聚合、自动补全、数据同步、es集群

?

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