1.注册中心

技术实践 - 图1

zookeeper

Zookeeper可以充当一个服务注册表(Service Registry),让多个服务提供者形成一个集群,让服务消费者通过服务注册表获取具体的服务访问地址(ip+端口)去访问具体的服务提供者。
每当一个服务提供者部署后都要将自己的服务注册到zookeeper的某一路径上,在Zookeeper中,进行服务注册,实际上是在Zookeeper中创建了一个Znode节点,该节点存储了该服务的IP、端口、调用方式(协议、序列化方式)等。
当创建该临时节点的客户端会话因超时或发生异常而关闭时,该节点也相应在 ZooKeeper 服务器上被删除,剔除或者上线的时候会触发Zookeeper的Watch机制,会发送消息给消费者,因此就做到消费者信息的及时更新。
技术实践 - 图2
Zookeeper从设计上来说的话整体遵循的CP的原则,在任何时候对 Zookeeper 的访问请求能得到一致的数据结果,同时系统对网络分区具备容错性。在使用 Zookeeper 获取服务列表时,如果此时的 Zookeeper 集群中的 Leader 宕机了,该集群就要进行 Leader 的选举,又或者 Zookeeper 集群中半数以上服务器节点不可用,那么将无法处理该请求。Zookeeper 不能保证服务可用性。

Eureka

Nacos

Consul

Consul是HashiCorp公司推出的开源工具,Consul由Go语言开发,部署起来非常容易,只需要极少的可执行程序和配置文件,具有绿色、轻量级的特点。Consul是分布式的、高可用的、 可横向扩展的用于实现分布式系统的服务发现与配置。

  • Consul提供了通过DNS或者HTTP接口的方式来注册服务和发现服务。
  • Consul可以为服务生成和分发TLS证书,以建立相互的TLS连接。

  • Kubernetes

2.Gateway

断言

  • Predicate来自于java8的接口。Predicate接受一个输入参数,返回一个布尔值结果。
  • 该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑。
  • 可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。
  • org.springframework.cloud.gateway.handler.predicate

技术实践 - 图3
配置

  1. spring:
  2. cloud:
  3. gateway:
  4. ## 路由
  5. routes:
  6. ## id只要唯一即可,名称任意
  7. - id: gateway-provider_1
  8. uri: http://localhost:9024
  9. ## 配置断言
  10. predicates:
  11. ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
  12. - Path=/gateway/provider/**
  13. ## Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了80%
  14. ## 第一个group1是分组名,第二个参数是权重
  15. - Weight=group1, 8
  16. ## id必须唯一
  17. - id: gateway-provider_2
  18. ## 路由转发的uri
  19. uri: http://localhost:9025
  20. ## 配置断言
  21. predicates:
  22. ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9025这个uri中
  23. - Path=/gateway/provider/**
  24. ## Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了20%
  25. ## 第一个group1是分组名,第二个参数是权重
  26. - Weight=group1, 2

过滤器

Gateway的生命周期:

  • PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择 请求的微服务、记录调试信息等。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

Gateway 的Filter从作用范围可分为两种:

  • GatewayFilter:应用到单个路由或者一个分组的路由上(需要在配置文件中配置)。
  • GlobalFilter:应用到所有的路由上(无需配置,全局生效)

GatewayFilter(局部过滤器)
GlobalFilter(全局过滤器)

全局异常

3.OpenFeign

工作原理

  • 通过 @EnableFeignCleints 触发 Spring 应用程序对 classpath 中 @FeignClient 修饰类的扫描
  • 解析到 @FeignClient 修饰类后, Feign 框架通过扩展 Spring Bean Deifinition 的注册逻辑, 最终注册一个 FeignClientFacotoryBean 进入 Spring 容器
  • Spring 容器在初始化其他用到 @FeignClient 接口的类时, 获得的是 FeignClientFacotryBean 产生的一个代理对象 Proxy
  • 基于 java 原生的动态代理机制, 针对 Proxy 的调用, 都会被统一转发给 Feign 框架所定义的一个 InvocationHandler , 由该 Handler 完成后续的 HTTP 转换, 发送, 接收, 翻译HTTP响应的工作

    使用方式

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    在Spring boot 主启动类上添加一个注解@EnableFeignClients,开启openFeign功能。
    新建一个openFeign接口,使用@FeignClient注解标注,@FeignClient中的value属性指定了服务提供者在nacos注册中心的服务名
    @FeignClient(value = "openFeign-provider")
    public interface OpenFeignService {
    }
    

    参数传递

    ```java //JSON传参 @RestController @RequestMapping(“/openfeign/provider”) public class OpenFeignProviderController { @PostMapping(“/order2”) public Order createOrder2(@RequestBody Order order){
      return order;
    
    } }

@FeignClient(value = “openFeign-provider”) public interface OpenFeignService { /**

 * 参数默认是@RequestBody标注的,这里的@RequestBody可以不填
 * 方法名称任意
 */
@PostMapping("/openfeign/provider/order2")
Order createOrder2(@RequestBody Order order);

}

```java
//参数使用POJO对象接收
@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
    @PostMapping("/order1")
    public Order createOrder1(Order order){
        return order;
    }
}


@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
    /**
     * 参数默认是@RequestBody标注的,如果通过POJO表单传参的,使用@SpringQueryMap标注
     */
    @PostMapping("/openfeign/provider/order1")
    Order createOrder1(@SpringQueryMap Order order);
}
//针对restful方式中的GET请求
@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {

    @GetMapping("/test/{id}")
    public String test(@PathVariable("id")Integer id){
        return "accept one msg id="+id;
}

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {

    @GetMapping("/openfeign/provider/test/{id}")
    String get(@PathVariable("id")Integer id);
}
//普通表单参数
@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
    @PostMapping("/test2")
    public String test2(String id,String name){
        return MessageFormat.format("accept on msg id={0},name={1}",id,name);
    }
}

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
    /**
     * 必须要@RequestParam注解标注,且value属性必须填上参数名
     * 方法参数名可以任意,但是@RequestParam注解中的value属性必须和provider中的参数名相同
     */
    @PostMapping("/openfeign/provider/test2")
    String test(@RequestParam("id") String arg1,@RequestParam("name") String arg2);
}

熔断升级

openFeign默认支持的就是Hystrix,阿里的Sentinel无论是功能特性、简单易上手等各方面都完全秒杀Hystrix,故此配置openFeign+Sentinel进行整合实现服务降级。
配置

<dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
#开启sentinel熔断降级
feign:
  sentinel:
    enabled: true

添加降级回调类OpenFeignFallbackService,一旦OpenFeignService中对应得接口出现了异常则会调用这个类中对应得方法进行降级处理。
技术实践 - 图4
在@FeignClient中添加fallback属性,属性值是降级回调的类

@FeignClient(value = "openFeign-provider",fallback = OpenFeignFallbackService.class)
public interface OpenFeignService {}

超时时间配置

openFeign有默认超时时间,默认分别是连接超时时间10秒、读超时时间60秒,源码在feign.Request.Options#Options()这个方法中
技术实践 - 图5
如果某个接口睡眠3秒钟,openFeign接口返回结果异常。原因是openFeign集成了Ribbon,Ribbon的默认超时连接时间、读超时时间都是是1秒。如果openFeign没有设置对应得超时时间,那么将会采用Ribbon的默认超时时间。源码在org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute()方法中
技术实践 - 图6
配置

  • 配置类中配置时间

    //ptions 的第一个参数是连接超时时间(ms),默认值是 10×1000;第二个是取超时时间(ms),默认值是 60×1000。
    @Configuration
    public class FeignConfiguration {
      @Bean
      public Request.Options options() {
          return new Request.Options(5000, 10000);
      }
    }
    
  • yaml文件配置 ```yaml feign: client: config:

    ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
    default:
      connectTimeout: 5000
      readTimeout: 5000
    

feign: client: config:

  ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
  default:
    connectTimeout: 5000
    readTimeout: 5000
  ## 为serviceC这个服务单独配置超时时间
  serviceC:
    connectTimeout: 30000
    readTimeout: 30000
<a name="CqjaF"></a>
### 认证配置
通常调用的接口都是有权限控制的,很多时候可能认证值是通过参数去传递的,还有就是通过请求头去传递认证信息,比如 Basic 认证方式。<br />**Basic 认证**
```java
@Configuration
public class FeignConfiguration {
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

自定义配置

//实现 RequestInterceptor 接口来自定义认证方式
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
    public FeignBasicAuthRequestInterceptor() {
    }
    @Override
    public void apply(RequestTemplate template) {
        // 业务逻辑
    }
}
@Configuration
public class FeignConfiguration {
    @Bean
    public FeignBasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new FeignBasicAuthRequestInterceptor();
    }
}

日志增强

openFeign虽然提供了日志增强功能,但是默认是不显示任何日志的,不过开发者在调试阶段可以自己配置日志的级别。
openFeign的日志级别如下:

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

配置

  • 配置类中配置日志级别

技术实践 - 图7

  • yaml文件中设置接口日志级别

    logging:
    level:
      com.lymn.service: debug #openFeign接口所在的包名
    

    替换默认的httpclient

    Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection。
    在生产环境中,通常不使用默认的http client,通常有如下两种选择:

  • 使用ApacheHttpClient

  • 使用OkHttp

添加ApacheHttpClient

<!-- 使用Apache HttpClient替换Feign原生httpclient-->
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
    </dependency>

    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-httpclient</artifactId>
    </dependency>
#org.springframework.cloud.openfeign.FeignAutoConfiguration
#feign 使用 HttpClien
feign.httpclient.enabled=true

添加OkHttp

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>
#feign 使用 okhttp
feign.httpclient.enabled=false
feign.okhttp.enabled=true

验证是否成功
在feign.SynchronousMethodHandler#executeAndDecode()这个方法中可以清楚的看出调用哪个client
技术实践 - 图8

gzip压缩配置

gzip是一种流行的数据压缩算法,采用用deflate算法压缩数据。既可以加快网页加载的速度,改善用户的浏览体验,又可与搜索引擎的抓取工具有着更好的关系。
GZIP压缩传输的原理如下图:
技术实践 - 图9
步骤如下:

  • 客户端向服务器请求头中带有:Accept-Encoding:gzip,deflate 字段,向服务器表示,客户端支持的压缩格式(gzip或者deflate),如果不发送该消息头,服务器是不会压缩的。
  • 服务端在收到请求之后,如果发现请求头中含有Accept-Encoding字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带Content-Encoding:gzip消息头,表示响应报文是根据该格式压缩过的。
  • 客户端接收到响应之后,先判断是否有Content-Encoding消息头,如果有,按该格式解压报文。否则按正常报文处理。

openFeign支持请求/响应开启GZIP压缩,整体的流程如下图:
技术实践 - 图10
openFeign支持的GZIP仅仅是在openFeign接口的请求和响应,即是openFeign消费者调用服务提供者的接口。
image.png
配置

feign:
  ## 开启压缩
  compression:
    request:
      enabled: true
      ##开启压缩的阈值,单位字节,默认2048,即是2k,这里为了演示效果设置成10字节
      min-request-size: 10
      mime-types: text/xml,application/xml,application/json
    response:
      enabled: true

技术实践 - 图12

4.配置中心

5.Sentinel

搭建控制台

https://github.com/alibaba/Sentinel/tags
选择对应得版本下载即可,我这里选择1.7.1版本,下载的jar包如下图:
技术实践 - 图13

#-Dserver.port:指定启动的端口,默认8080  -Dproject.name:指定本服务的名称
#-Dcsp.sentinel.dashboard.server:指定sentinel控制台的地址,用于将自己注册进入实现监控自己
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar

启动成功之后,浏览器访问:http://localhost:8080,登录页面如下图:
技术实践 - 图14
默认的用户名和密码:sentinel/sentinel
登录成功之后页面如下:
技术实践 - 图15
可以看到目前只有一个服务sentinel-dashboard被监控了,这个服务就是自己
从 Sentinel 1.6.0 起sentinel已经支持自定义用户名和密码了,只需要在执行jar命令时指定即可,命令如下:

#如果省略这两个参数,默认用户和密码均为 sentinel
# -Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟
# 部署多台控制台时,session 默认不会在各实例之间共享
java -Dsentinel.dashboard.auth.username=admin -Dsentinel.dashboard.auth.password=123 -jar sentinel-dashboard-1.7.1.jar

其他可配置参数
技术实践 - 图16

微服务接入sentinel控制台

<!--sentinel的依赖-->
<dependency>
 <groupId>com.alibaba.cloud</groupId>
 <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
  cloud:
    sentinel:
      transport:
       ## 指定控制台的地址,默认端口8080
        dashboard: localhost:8080


spring:
    sentinel:
      # 取消控制台懒加载,项目启动即连接Sentinel
      eager: true

sentinel是懒加载机制,只有访问过一次的资源才会被监控。不过可以通过配置关闭懒加载,在项目启动时就连接sentinel控制台。

流量控制配置

流量控制(flow control)其原理是监控应用流量QPS并发线程数等指标,当达到指定阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性
QPS:每秒请求数,即在不断向服务器发送请求的情况下,服务器每秒能够处理的请求数量。
并发线程数:同时请求的线程数量。
同一个资源可以创建多条限流规则,一条限流规则由以下元素组成:

  • resource:资源名,即限流规则的作用对象。
  • count: 限流阈值
  • grade:限流阈值类型(1:QPS 0:并发线程数),默认值QPS
  • limitApp:流控针对的调用来源,若为 default 则不区分调用来源,默认值default
  • strategy:判断根据是资源自身(0),还是根据其它关联资源 (1),还是根据链路入口(2),默认值根据资源本身。
  • controlBehavior: 流控效果(直接拒绝(0) / 排队等待(2) / 预热冷启动(1)),默认值直接拒绝。

以上元素限流元素对应的类是com.alibaba.csp.sentinel.slots.block.flow.FlowRule
技术实践 - 图17

流控效果

快速失败
默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。
warm up
预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
预热底层是根据令牌桶算法实现,这一效果只针对QPS流控,并发线程数流控不支持。
算法中有一个冷却因子coldFactor,默认值是3,即请求 QPS 从 threshold(阈值) / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
技术实践 - 图18
QPS起初会从(3/3/=1)每秒通过一次请求开始预热直到5秒之后达到每秒通过3次请求。前几秒是频繁流控的,直到5秒QPS阈值达到了3。
技术实践 - 图19
排队等待
匀速排队方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。这一效果只针对QPS流控,并发线程数流控不支持。
不同的是sentinel有个超时等待时间,一旦超过这个预定设置的时间将会被限流。
技术实践 - 图20
连续点击刷新请求,虽然设置了QPS阈值为1,但是并没有被限流,而是在等待,因为设置了超时等待时间为10秒。
技术实践 - 图21

流控模式

直接拒绝
默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException
关联
A关联B,一旦B达到阈值,则A被限流。比如一个是支付接口,一个是下单接口,此时一旦支付接口达到了阈值,那么订单接口就应该被限流,不然这边还在下单,消费者等待或者直接被拒绝支付将会极大的影响用户体验。
技术实践 - 图22
利用POSTMAN不断向/sentinel/pay发出请求达到阈值限流,然后浏览器请求/sentinel/order,结果如下图:
技术实践 - 图23

链路
只记录指定链路上的流量,指定资源从入口资源进来的流量,如果达到阈值可以限流。

降级规则配置

技术实践 - 图24
熔断策略

  1. 平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
  2. 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  3. 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

    热点参数限流

    Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。注意:热点参数限流只针对QPS。
    规则对应得源码在com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule
    技术实践 - 图25
    技术实践 - 图26
    配置

    @Service
    @Slf4j
    public class FlowServiceImpl implements FlowService {
    
     /**
      * @SentinelResource的value属性指定了资源名,一定要唯一
      * blockHandler属性指定了兜底方法
      */
     @Override
     @SentinelResource(value = "OrderQuery",blockHandler = "handlerQuery")
     public String query(String p1, String p2) {
         log.info("查询商品,p1:{},p2:{}",p1,p2);
         return "查询商品:success";
     }
    
     /**
      * 对应得兜底方法,一旦被限流将会调用这个方法来处理
      */
     public String handlerQuery(@RequestParam(value = "p1",required = false) String p1,
                                @RequestParam(value = "p2",required = false)String p2,
                                BlockException exception){
         log.info("查询商品,p1:{},p2:{}",p1,p2);
         return "查询商品:熔断了......";
     }
    }
    

    ```java @RestController @RequestMapping(“/sentinel/provider”) @Slf4j public class FlowLimitController { @Autowired private FlowService flowService;

    @GetMapping(“/order/query”) public String query(@RequestParam(value = “p1”,required = false) String p1, @RequestParam(value = “p2”,required = false)String p2){

     return flowService.query(p1,p2);
    

    }

}

在sentinel控制台点击热点规则->新增热点限流规则,添加如下图规则:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657201792555-7cdd863b-0665-429c-abe6-6cc9c55d061b.png#clientId=ua082c3b9-6b0d-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u300917b6&margin=%5Bobject%20Object%5D&originHeight=500&originWidth=762&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u8504515c-3b57-4e87-a4cb-b1d2bac6651&title=)<br />上述配置的具体含义:当OrderQuery这个资源中的**第0个参数**QPS超过**1秒1次**将会被限流。这里参数索引是从0开始,第0个就是对应接口中的p1这个参数。<br />**第一个测试**:浏览器直接访问:http://localhost:9009/sentinel/provider/order/query?p1=22&p2=1222,连续点击将会看到这个接口被熔断降级了,如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657201885474-d1268ae9-d1d0-46a7-8da1-38bfdc786ca9.png#clientId=ua082c3b9-6b0d-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ue5d5be51&margin=%5Bobject%20Object%5D&originHeight=130&originWidth=805&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u21bcb54f-9178-46d7-9fc1-a7d3bd833d8&title=)<br />**第二个测试**:浏览器输入:http://localhost:9009/sentinel/provider/order/query?p2=1222,连续点击将会看到这个接口并没有被熔断降级,如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657201885512-8f174adc-c19f-413f-ba08-29935c8f33f3.png#clientId=ua082c3b9-6b0d-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uf47041b7&margin=%5Bobject%20Object%5D&originHeight=157&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ue1780752-081b-4abd-b5fd-9ec759634eb&title=)

 对于热点参数限流,只有包含指定索引的参数请求才会被限流,否则不影响。<br />此外提供了**参数例外项**这项配置,针对产品需求配置如下:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657202105702-b347bed7-740e-4bc2-bfcc-251c79239018.png#clientId=ua082c3b9-6b0d-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u27174c3c&margin=%5Bobject%20Object%5D&originHeight=746&originWidth=739&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ub5939d35-54c7-42db-baa1-1681b32804b&title=)<br />从上图配置中,我们将参数值**p1**这个参数值等于100的时候,限流阈值设置成了100,也就是说p1=100这个请求QPS放宽到1秒请求100次以上才会被限流。<br />**验证**:浏览器输入地址:http://localhost:9009/sentinel/provider/order/query?p1=100,无论点击多么快,都没有被熔断降级,显然是配置生效了,如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657202105714-5c8e72ba-0f37-45f9-96ce-1e37ee58f0e4.png#clientId=ua082c3b9-6b0d-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ubb846435&margin=%5Bobject%20Object%5D&originHeight=97&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ua8bb04f7-afe5-432e-b7dd-8a4dbf55ba6&title=)


<a name="iWpMD"></a>
### 系统自适应限流
系统自适应限流针对是整个系统的入口流量,从单台机器的 **load**、**CPU 使用率**、**平均 RT**、**入口 QPS** 和**并发线程数**等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657073050797-8ed69890-a6c4-4538-a0df-31a45114d830.png#clientId=u3e391638-36de-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7cd34161&margin=%5Bobject%20Object%5D&originHeight=445&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=uf86bfd79-da5d-4747-b836-239d17ac538&title=)<br />阈值类型有五种,分别如下:

- **Load 自适应**(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
- **CPU usage**(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- **平均 RT**:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- **并发线程数**:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- **入口 QPS**:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
<a name="HgxW1"></a>
### 自定义异常
无论是熔断降级还是被限流返回的异常信息都是Blocked by Sentinel (flow limiting),这个是Sentinel默认的异常信息。因此我们需要根据前后端规则制定自己的异常返回信息。<br />@SentinelResource注解中有两个关于限流兜底方法的属性

- **blockHandler**: 对应处理 BlockException 的函数名称。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。
- **blockHandlerClass**:指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657072873311-c3405358-0d79-4b54-b52c-203bf157e011.png#clientId=u3e391638-36de-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u8296a07b&margin=%5Bobject%20Object%5D&originHeight=617&originWidth=970&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u4c6eda33-a57b-4fe9-a259-3772cbf2f41&title=)<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657072880146-35436d89-b9e3-4b6a-972c-33812799b144.png#clientId=u3e391638-36de-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u363a44a0&margin=%5Bobject%20Object%5D&originHeight=344&originWidth=1038&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u05f6a0fc-7b82-4959-a461-d1615245a44&title=)<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657072891397-e79aee26-91f1-489c-b888-bc25fbc69a71.png#clientId=u3e391638-36de-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u646b1f05&margin=%5Bobject%20Object%5D&originHeight=569&originWidth=766&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u943f2763-2c46-4935-b2c4-264a07f052f&title=)<br />@SentinelResource提供一个属性blockHandlerClass,完美的解决了**兜底方法必须要和业务方法放在同一个类中的问题。**<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657072940088-a217becb-1f1d-4742-9030-8c5e80e776aa.png#clientId=u3e391638-36de-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u8173fbdd&margin=%5Bobject%20Object%5D&originHeight=556&originWidth=1022&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ud59c835a-32ad-4e42-b9bf-0a105bd617b&title=)<br />在@SentinelResource注解中指定blockHandlerClass为上面的类,blockHandler指定兜底方法名<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657072952923-ec5854e5-2d4c-4aad-b75f-bb6c9570a92c.png#clientId=u3e391638-36de-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ue782e5c8&margin=%5Bobject%20Object%5D&originHeight=517&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u4bba1e23-ecca-42e7-b8f4-94a1e44dfa8&title=)<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657072965868-9f551a9c-7aec-4b45-8843-0153cd45de6b.png#clientId=u3e391638-36de-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7ed9029f&margin=%5Bobject%20Object%5D&originHeight=478&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=uf53f433d-d944-4ce5-a315-016c3d82689&title=)
<a name="RmJPm"></a>
### 异常降级
<a name="NsiPG"></a>
### 限流规则持久化

<a name="PcbBO"></a>
### 黑白名单
sentinel控制台对应得规则配置如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657202279889-19b68d06-eca5-4320-8105-40612401383d.png#clientId=ua082c3b9-6b0d-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u1caa371b&margin=%5Bobject%20Object%5D&originHeight=403&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ud036b488-e564-45ca-9472-6fa30fc2371&title=)<br />该规则对应得源码为com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule,几个属性如下:

- resource:资源名,即限流规则的作用对象。
- limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB。
- strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式。

Sentinel提供了一个接口RequestOriginParser,我们可以实现这个接口根据自己业务的规则解析出请求来源名称。<br />将127.0.0.1设置为黑名单,如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657202410624-cc7729d7-43fc-40c0-9ed8-6f10c85a8141.png#clientId=ua082c3b9-6b0d-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uc0c59999&margin=%5Bobject%20Object%5D&originHeight=351&originWidth=761&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u035b54db-6ec2-4d33-abdb-85490175a2f&title=)<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657202400892-9452c6ab-87a7-44c8-b8bc-db2a40f19d82.png#clientId=ua082c3b9-6b0d-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u165cb7b1&margin=%5Bobject%20Object%5D&originHeight=461&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ue836f26c-7563-4e4b-82e2-18c82223817&title=)
<a name="ecHeO"></a>
### 流控集群
实际生产中对于集群不推荐单体流控。

- 对于微服务要想保证高可用,必须是集群,假设有100个集群,那么想要设置流控规则,是不是每个微服务都要设置一遍?维护成本太高了
- 单体流控还会造成**流量不均匀**的问题,出现总流控阈值没有达到某些微服务已经被限流了

sentinel为我们提供了一个专门的server来统计调用的总量,其他的实例都与server保持通信。<br />集群流控可以精确地控制整个集群的**调用总量**,结合**单机限流兜底**,可以更好地发挥流量控制的效果。

<a name="f8mBA"></a>
## 7.SkyWalking
skywalking是一个优秀的**国产开源框架,**支持dubbo,SpringCloud,SpringBoot集成,**代码无侵入,通信方式采用GRPC,性能较好,实现方式是java探针,支持告警,支持JVM监控,支持全局调用统计**等。
<a name="eZQv4"></a>
### 架构
Agent负责收集日志传输数据,通过GRPC的方式传递给OAP进行分析并且存储到数据库中,最终通过UI界面将分析的统计报表、服务依赖、拓扑关系图展示出来。<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657289978372-0f729df0-cda2-48d5-8f7d-95364b2fc60a.png#clientId=ub28e8668-f30a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u4804f16a&margin=%5Bobject%20Object%5D&originHeight=338&originWidth=727&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ue58b32fa-0be9-4197-bc77-dc7db6a6925&title=)<br />上述架构图中主要分为四个部分,如下:

- **上面的Agent**:负责收集日志数据,并且传递给中间的OAP服务器
- **中间的OAP**:负责接收 Agent 发送的 Tracing 和Metric的数据信息,然后进行分析(Analysis Core) ,存储到外部存储器( Storage ),最终提供查询( Query )功能。
- **左面的UI**:负责提供web控制台,查看链路,查看各种指标,性能等等。
- **右面Storage**:负责数据的存储,支持多种存储类型。
<a name="MYil0"></a>
### 服务端搭建
[https://skywalking.apache.org/downloads/](https://skywalking.apache.org/downloads/)<br />选择某个版本,如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657290417436-1b4e7655-5991-46a1-b5b6-9c6d7a7b3d86.png#clientId=ub28e8668-f30a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u93ed6709&margin=%5Bobject%20Object%5D&originHeight=372&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u58b1ebd2-e57b-4582-8776-0fe8cf05ef1&title=)

 解压后

- **agent**:客户端需要指定的目录,其中有一个jar,就是负责和客户端整合收集日志
- **bin**:服务端启动的脚本
- **config**:一些配置文件的目录
- **logs**:oap服务的日志目录
- **oap-libs**:oap所需的依赖目录
- **webapp**:UI服务的目录

**config/application.yml**<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657290539308-829857e3-685b-4bbb-b471-50cc609ab640.png#clientId=ub28e8668-f30a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ucf299180&margin=%5Bobject%20Object%5D&originHeight=688&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ub3a25f7d-14a3-460c-be87-35df06f0b52&title=)<br />**webapp/webapp.yml**<br />UI服务的配置文件,其中有一个**server.port**配置,是UI服务的端口,默认**8080**,其改成**8888**,避免端口冲突,如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657290590314-af2ba8e9-ba67-4f43-878e-5772c4d5b05a.png#clientId=ub28e8668-f30a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ud5ba92e8&margin=%5Bobject%20Object%5D&originHeight=60&originWidth=323&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=uce215745-a9c6-496c-b3f2-802289c718f&title=)

 启动命令在/bin目录下,这里需要启动两个服务,如下:

- **oap服务**:对应的启动脚本oapService.bat,Linux下对应的后缀是sh
- **UI服务**:对应的启动脚本webappService.bat,Linux下对应的后缀是sh

当然还有一个**startup.bat**启动文件,可以直接启动上述两个服务,我们可以直接使用这个脚本,直接双击,将会弹出两个窗口则表示启动成功,如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657290727103-5f43dcb8-9aa5-4496-a035-ec21d3d2c342.png#clientId=ub28e8668-f30a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u5816d62d&margin=%5Bobject%20Object%5D&originHeight=541&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u19f6a0ca-a777-4da7-b88b-23c4bd1f1a2&title=)<br /> http://localhost:8888/,直接进入UI端,如下图:<br />![](https://cdn.nlark.com/yuque/0/2022/png/22211120/1657290803994-097fd781-5fe3-4413-bf0b-37c76b82ac3b.png#clientId=ub28e8668-f30a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u5f6c2819&margin=%5Bobject%20Object%5D&originHeight=504&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ucdd5dcd2-ab42-4487-b802-a49abb4ad7d&title=)
<a name="dfE2B"></a>
### 客户端搭建
客户端即单个微服务,由于Skywalking采用字节码增强技术,因此对于微服务无代码侵入,只要是普通的微服务即可,不需要引入什么依赖。<br />想要传输数据必须借助skywalking提供的agent,只需要每个服务在启动参数指定即可,命令如下:
```shell
#agent的jar包路径不能包含中文,不能有空格,否则运行不成功。
-javaagent:E:\springcloud\apache-skywalking-apm-es7-8.7.0\apache-skywalking-apm-bin-es7\agent\skywalking-agent.jar
-Dskywalking.agent.service_name=skywalking-product-service
-Dskywalking.collector.backend_service=127.0.0.1:11800

上述命令解析如下:

  • -javaagent:指定skywalking中的agent中的skywalking-agent.jar的路径
  • -Dskywalking.agent.service_name:指定在skywalking中的服务名称,一般是微服务的spring.application.name
  • -Dskywalking.collector.backend_service:指定oap服务绑定的地址,由于这里是本地,并且oap服务默认的端口是11800,因此只需要配置为127.0.0.1:11800

上述参数可以在命令行通过java -jar xxx指定,在IDEA中操作如下图:
技术实践 - 图27

成功启动后,访问某个微服务接口,此时查看skywalking的UI端,可以看到三个服务已经监控成功了,如下图:
技术实践 - 图28
服务之前的依赖关系也是可以很清楚的看到,如下图:
技术实践 - 图29
请求链路的信息也是能够很清楚的看到,比如请求的url,执行时间、调用的服务,如下图:
技术实践 - 图30

数据持久化