cloud微服务架构组件揭秘—为什么选cloud

一、架构演进

1、传统架构(单系统模式)
单系统模式架构
单系统模式:系统各个模块全部耦合在一起,单个war包包含了所有的功能。
优缺点:

  • 优点:
    易部署:因为是单个应用不需要其他操作直接成war包部署
    容易测试运行:也是因为是单个应用的原因,不需要启动其他服务
  • 缺点
    复杂性高:随着业务的不断迭代,项目的模块也会不断的增加,模块与模块直接的关系也会变得越来越复杂进而整个项目变得非常复杂,在后期新增功能时很有可能会影响到其他的模块。
    可靠性低:因为是单系统应用一旦某个模块某个环节出现问题,导致整个项目无法正常运行
    可拓展性差:由于是单体应用,模块与模块之间的耦合度高,很容易导致牵一发而动全身

最大负载量:几千到几万的访问量

2、集群模式
集群
集群:集群是一种物理形态,简单来讲集群就是复制,可以是节点集群,也可以单体应用集群

  • 优点:
        高可伸缩性:服务器集群具有很强的可伸缩性。随着需求和负荷的增长,可以向集群系统添加更多的服务器。在这样的配置中,可以有多台服务器执行相同的应用和数据库操作。

       高可用性:高可用性是指,在不需要操作者干预的情况下,防止系统发生故障或从故障中自动恢复的能力。通过把故障服务器上的应用程序转移到备份服务器上运行,集群系统能够把正常运行时间提高到大于99.9%,大大减少服务器和应用程序的停机时间。

       高可管理性:系统管理员可以从远程管理一个、甚至一组集群,就好像在单机系统中一样。

  • 缺点
       部署维护费用较高

最大负载量:几万到几十万的访问量

3、分布式架构
分布式架构
分布式:分布式服务顾名思义服务是分散部署在不同的机器上的,一个服务可能负责几个功能,是一种面向SOA架构的,服务之间也是通过rpc来交互或者是webservice来交互的。逻辑架构设计完后就该做物理架构设计,系统应用部署在超过一台服务器或虚拟机上,且各分开部署的部分彼此通过各种通讯协议交互信息,就可算作分布式部署,生产环境下的微服务肯定是分布式部署的,分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用。
优缺点:

  • 优点:
    系统高可用:因为分散式部署不同业务模块,并且拥有多个节点,具有更强的容错性,一旦某个节点出错,可以使用另外的替代节点
    高拓展性:由于系统被拆分成各个业务模块,模块与模块之间通过TCP方式交换数据,也就是说增加某个模块不管过程中该模块的业务是怎么实现的,最后只需要提供外部调用接口就行。
    高协作:由于被拆分成模块,业务可以划分成模块纬度分发给开发人员
    高效率:由于项目被拆分成不同模块分发给开发人员开发,开发速度并行。
  • 缺点:
    架构复杂:服务与服务之间的调用方式,服务的负载…
    问题多样性:分布式事务,高并发等等…
    学习成本高:由于高复杂度和问题多样性,学习起来肯定会碰到各种各样的问题。

最大负载量:百万+

4、微服务
微服务架构
微服务:简单来说微服务就是很小的服务,小到一个服务只对应一个单一的功能,只做一件事。这个服务可以单独部署运行,服务之间可以通过RPC来相互交互,每个微服务都是由独立的小团队开发,测试,部署,上线,负责它的整个生命周期。。微服务与分布式的细微差别是,微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器,微服务就是分布式的进一步细化。
优缺点

  • 优点:
    系统高可用:因为把单个功能划分成一个服务单独部署,并且同样可以进行多节点部署,容错性毋庸置疑,并且拥有自身的熔断降级等独特功能,更加的保障了服务的高可用。
    高拓展性:由于系统被拆分成各个功能模块,颗粒度较分布式更加的细微,服务与服务之间通过TCP方式交换数据,也就是说增加某个功能不管过程中该业务功能是怎么实现的,最后只需要提供外部调用接口就行。
    高协作:由于被拆分成模块,业务可以划分成模块纬度分发给开发人员
    高效率:由于项目被拆分成不同模块分发给开发人员开发,开发速度并行。
    生态齐全:微服务目前有spring于阿里体系,都提供了大量的微服务问题解决方案,例如服务访问、服务治理、服务熔断、服务安全、网关等等…
  • 缺点:
    架构复杂:服务与服务之间的调用方式,服务的负载…
    问题多样性:分布式事务,高并发等等…
    学习成本高:由于高复杂度和问题多样性,学习起来肯定会碰到各种各样的问题。
    架构部署成本高:支撑起一个微服务需要几十甚至成百上千台服务器
    维护成本大:因为服务器数量一旦上去了,维护服务的人工成本就必须上去

最大负载量:百万+

二、集群、分布式、微服务之间的关系

让我们再来回顾下他们各自的定义:

  1. 集群:集群是一种物理部署形态,只是把同一服务,同一系统或者同一节点同时部署到不同机器上,单体应用可以集群,分布式节点可以集群,微服务也可以集群。

  2. 分布式:分布式是一种工作方式,把系统的不同模块分散式部署到不同的机器上,一个服务可能负责几个功能,是一种面向SOA架构的,服务之间也是通过rpc来交互或者是webservice来交互的。逻辑架构设计完后就该做物理架构设计,系统应用部署在超过一台服务器或虚拟机上,且各分开部署的部分彼此通过各种通讯协议交互信息,就可算作分布式部署,生产环境下的微服务肯定是分布式部署的,分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用。

  3. 微服务:微服务就是很小的服务,小到一个服务只对应一个单一的功能,只做一件事。

集群和分布式的区别:集群是个物理形态,分布式是个工作方式 分布式是将不同的业务分布在不同的机器上,而集群则相反,它是把几个服务器集中在一起实现同一业务。分布式的每一个节点都可以是集群,但是集群不一定是分布式。

  • 举个栗子:豆瓣网,每天访问人数超过上万,他想要集群,用户发送请求,通过Nginx来分发请求看哪台服务器负载较低就把请求给那台机器,而分布式从狭义来讲也算是一种集群但是组织较松散,它不像集群,万一有一天分布式的哪一个节点服务挂了服务就运行不起来了,而集群,一台服务器挂了另一台服务器可以顶替上去。

简单讲:分布式是以缩短单个任务的执行时间来提升效率的,而集群则是通过提高单位时间内执行的任务数来提升效率。 一个提高执行频率一个提高执行速度。

  • 例如:如果一个任务由 10 个子任务组成,每个子任务单独执行需 1 小时,则在一台服务器上执行该任务需 10 小时。 采用分布式方案,提供
    10 台服务器,每台服务器只负责处理一个子任务,不考虑子任务间的依赖关系,执行完这个任务只需一个小时。(这种工作模式的一个典型代表就是
    Hadoop 的 Map/Reduce 分布式计算模型) 而采用集群方案,同样提供 10 台服务器,每台服务器都能独立处理这个任务。假设有
    10 个任务同时到达,10 个服务器将同时工作,1 小时后,10 个任务同时完成,这样,整身来看,还是 1 小时内完成一个任务!

分布式和微服务的区别:前面我们说了,分布式是种工作方式,是将服务分散部署在不同的机器上,一个服务可能负责几个功能,是一种面向SOA架构的,服务之间也是通过rpc来交互或者是webservice来交互的。逻辑架构设计完后就该做物理架构设计,系统应用部署在超过一台服务器或虚拟机上,且各分开部署的部分彼此通过各种通讯协议交互信息,就可算作分布式部署,生产环境下的微服务肯定是分布式部署的,分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用。而微服务呢?微服务是一种架构设计方式 在做架构设计的时候,先做逻辑架构,再做物理架构,当你拿到需求后,估算过最大用户量和并发量后,计算单个应用服务器能否满足需求,如果用户量只有几百人的小应用,单体应用就能搞定,即所有应用部署在一个应用服务器里,如果是很大用户量,且某些功能会被频繁访问,或者某些功能计算量很大,建议将应用拆解为多个子系统,各自负责各自功能,这就是微服务架构。

三、微服务解决方案——cloud架构

  1. 服务治理————euraka、Zookeeper、Consul
  2. 服务调用————feign
  3. 负载均衡————ribbon
  4. 服务熔断/降级————hystrix
  5. 网关权限————zuul
  6. 配置中心————springcloud config
  7. 链路追踪————Sleuth和ZipKin

在讲之前先了解下分布式核心理论——CAP定理

https://blog.csdn.net/weixin_42083036/article/details/105968452

1、服务治理

1.1、Eureka架构中的三个核心角色:
  • 服务注册中心
          Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的euraka-server。

  • 服务提供者
            提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。

  • 服务消费者
            消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。

euraka原理
Register: 服务注册
服务的提供者,将自身注册到注册中心,服务提供者也是一个 Eureka Client。当 Eureka Client 向 Eureka Server 注册时,它提供自身的元数据,比如 IP 地址、端口,运行状况指示符 URL,主页等。

Renew: 服务续约
Eureka Client 会每隔 30 秒发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka Client 运行正常,没有出现问题。 默认情况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除,此时间可配置,一般情况不建议更改。
服务续约中重要的属性

服务续约任务的调用间隔时间,默认为30秒
eureka.instance.lease-renewal-interval-in-seconds=30

服务失效的时间,默认为90秒。
eureka.instance.lease-expiration-duration-in-seconds=90

Eviction 服务剔除
当 Eureka Client 和 Eureka Server 不再有心跳时,Eureka Server 会将该服务实例从服务注册列表中删除,即服务剔除。

Cancel: 服务下线
Eureka Client 在程序关闭时向 Eureka Server 发送取消请求。 发送请求后,该客户端实例信息将从 Eureka Server 的实例注册表中删除。该下线请求不会自动完成,它需要通过调用“关机”命令:

DiscoveryManager.getInstance().shutdownComponent();

Remote Call: 远程调用
当 Eureka Client 从注册中心获取到服务提供者信息后,就可以通过 Http 请求调用对应的服务;服务提供者有多个时,Eureka Client 客户端会通过 Ribbon 自动进行负载均衡。

2、注册中心与客户端整合boot使用

首先找到boot对应的cloud版本
详细网址https://start.spring.io/actuator/info

cloudboot
Finchley.M2“Spring Boot >=2.0.0.M3 and <2.0.0.M5”
Finchley.M3“Spring Boot >=2.0.0.M5 and <=2.0.0.M5”
Finchley.M4“Spring Boot >=2.0.0.M6 and <=2.0.0.M6”
Finchley.M5“Spring Boot >=2.0.0.M7 and <=2.0.0.M7”
Finchley.M6“Spring Boot >=2.0.0.RC1 and <=2.0.0.RC1”
Finchley.M7“Spring Boot >=2.0.0.RC2 and <=2.0.0.RC2”
Finchley.M9“Spring Boot >=2.0.0.RELEASE and <=2.0.0.RELEASE”
Finchley.RC1“Spring Boot >=2.0.1.RELEASE and <2.0.2.RELEASE”
Finchley.RC2“Spring Boot >=2.0.2.RELEASE and <2.0.3.RELEASE”
Finchley.SR4“Spring Boot >=2.0.3.RELEASE and <2.0.999.BUILD-SNAPSHOT”
Finchley.BUILD-SNAPSHOT“Spring Boot >=2.0.999.BUILD-SNAPSHOT and <2.1.0.M3”
Greenwich.M1“Spring Boot >=2.1.0.M3 and <2.1.0.RELEASE”
Greenwich.SR2“Spring Boot >=2.1.0.RELEASE and <2.1.9.BUILD-SNAPSHOT”
Greenwich.BUILD-SNAPSHOT“Spring Boot >=2.1.9.BUILD-SNAPSHOT and <2.2.0.M4”
Hoxton.SR1Spring Boot >=2.2.0.M4 and <2.2.3.BUILD-SNAPSHOT
Hoxton.BUILD-SNAPSHOTSpring Boot >=2.2.3.BUILD-SNAPSHOT
2.1、注册中心

导入依赖

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

配置文件

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动类添加@EnableEurekaServer注解
启动 访问 localhost:8761
在这里插入图片描述
假如进入的界面是如下
在这里插入图片描述
出现上述情况可以在配置文件中加入 下面配置(生产环境建议打开)

eureka.server.enable-self-preservation=true      //开启自我保护

什么叫自我保护:Eureka在运行期间会统计心跳失败的比例,在15分钟内是否低于85%,如果出现了低于的情况,Eureka Server会将当前的实例注册信息保护起来,同时提示一个警告,一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据。也就是不会注销任何微服务。

注册中心集群

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://localhost:8762/eureka/
server:
  port: 8762

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

注册中心可以相互注册不过需要打开注册发现 如下

 eureka.client.registerWithEureka=true

在这里插入图片描述

2.2、客户端

依赖

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

配置文件 (这里默认取application.name作为服务发现名)

#网关名称
spring:
  application:
    name: api-gateway

#注册中心客户端
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka

启动服务 访问localhost:8761
在这里插入图片描述

2、服务调用

RPC与HTTP
在这里插入图片描述
spring自带服务调用工具RestTemplate
在这里插入图片描述

3、负载均衡

3.1、负载均衡是什么?

简单来讲就是原先任务量无脑分配给一个人做,现在有了一个专门管理任务的“管家”,他能观察每台机器的压力,根据机器压力的不同来分配任务。

3.2、负载均衡工具Ribbon

**核心是@Loadbalannce注解以及 LoadBalancerClient **
上面我们了解了spring自带的远程调用工具RestTemplate,restyemplate是没有管家的功能的,于是有人就想把他改造成管家类帮助实现负载均衡。
例子:创建一个order-service服务 配置如下 再创建product-service服务 8764和8765 统一注册到8761
在这里插入图片描述
此时订单服务需要调用商品服务根据id查询商品使用resttemplate类
在这里插入图片描述
实现负载
1、启动类下注入并添加@LoadBalanced注解

   @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

2、修改service方法

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;
    
 public String testLoadB(int id){
        //获取注册列表中名称为product-service的注册实例
        ServiceInstance instance = loadBalancerClient.choose("product-service");
        //使用占位符控制host和port
        String url = String.format("http://%s:%s/api/product/find?id="+id,instance.getHost(),instance.getPort(),Object.class);
       return restTemplate.getForObject(url,String.class);
    }

结果

冰箱1 I am from 8764
冰箱1 I  am from 8765

负载均衡策略选择(一般默认策略)
1、如果每个机器的配置差不多,不建议修改策略默认(轮询)就可以
2、如果有某台机器的配置比较号就可以改用 权重策略

3.3、ribbon的集成—feign

定义:Feign是一个声明性的web服务客户端,它使编写web服务客户机变得更容易。使用Feign创建接口并对其进行注释。它有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插入式的编码器和解码器。Spring Cloud增加了对Spring MVC注释的支持,并支持在Spring Web中使用默认的HttpMessageConverters。Spring Cloud集成了Ribbon和Eureka,在使用Feign时提供负载平衡的http客户端。(来自有道翻译)
总结2点:

  1. feign是接口加注解
  2. feign整合了ribbon

如何使用

  • 步骤:
    1、导入依赖
  <!--引入feign依赖  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

       2、启动类添加@EnableFeignClients
在这里插入图片描述
       3、书写feign客户端
在这里插入图片描述
商品服务方法
在这里插入图片描述
订单服务降级方法
在这里插入图片描述
演示
成功时调用返回:
在这里插入图片描述
此时product_service服务宕机/调用时间过长后返回:
在这里插入图片描述
在这里插入图片描述
订单服务降级后可选择调用方法(可做服务预警)
在这里插入图片描述

    /**
     *
     * @param userId
     * @param product_id
     * @return
     */
    //注方法签名一定要和api接口参数一致
    private RetResult saveOrderFail(int userId, int product_id) {
        //监控报警
        String saveOrderKey = "save-order";
        String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
        new Thread(()->{
            if(StringUtils.isBlank(sendValue)){
                System.out.println("通知开发人员,服务接口已经挂了");
                //TODO 调用一个短信服务接口   20秒过期
                redisTemplate.opsForValue().set(saveOrderKey,"save-order-fail",20, TimeUnit.SECONDS);
            }else{
                System.out.println( redisTemplate.opsForValue().get(saveOrderKey));
                System.out.println("已发送短信,20秒内不会重复发送");
            }
        }).start();
        Map<String, Object> msg = new HashMap<String, Object>();
        msg.put("code", -1);
        msg.put("msg", "下单人数过多,您被挤下线了,请稍后再试!");
        //如果product服务挂了 将模拟的商品数据传送给订单服务
        msg.put("data",productServiceFallback.findByid(product_id));
        return RetResponse.makeRsp(msg.get("code").toString(),msg.get("msg").toString(),msg.get("data"));
    }

注意问题https://blog.csdn.net/weixin_42083036/article/details/91982930

4、Hystrix(豪猪)断路器

在这里插入图片描述
   1、什么是Hystrix?
         1)hystrix对应的中文名字是“豪猪”
         2)hystrix 英[hɪst’rɪks] 美[hɪst’rɪks]
   2、为什么要用?
            在一个分布式系统里,一个服务依赖多个服务,可能存在某个服务调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,通过Hystrix就可以解决。
   3、提供了熔断、隔离、Fallback、cache、监控等功能
   4、熔断后怎么处理?
          1. 出现错误之后可以 fallback 错误的处理信息
          2. 兜底数据(服务降级)例子:淘宝秒杀

   作用: 保护机制 防止上游服务调用下游服务失败而导致整个微服务异常
   雪崩问题:当用户发送请求,由于某个服务响应速度慢/失败导致该请求无法快速应答,从而导致后续的所有请求无法正常响应堆积在内存中最终OOM 服务挂了

    图例:当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游。它们的调用链如下:
在这里插入图片描述
      一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮。
链路雪崩
解决手段:

  • 线程隔离
  • 服务熔断
1、 线程隔离:

在这里插入图片描述
示意图解读:

Hystrix通过命令模式,将每个类型的业务请求封装成对应的命令请求,比如查询订单->订单Command,查询商品->商品Command,查询用户->用户Command。每个类型的Command对应一个线程池。创建好的线程池是被放入到ConcurrentHashMap中,比如查询订单:

final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>();
threadPools.put(“hystrix-order”, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));

当第二次查询订单请求过来的时候,则可以直接从Map中获取该线程池。具体流程如下图:
在这里插入图片描述
        Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
        用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理,什么是服务降级?

  1. 服务降级:优先保证核心服务,而非核心服务不可用或弱可用。
  2. 一旦线程池满、请求超时 就会触发服务降级

总结:执行依赖代码的线程与请求线程(比如Tomcat线程)分离,请求线程可以自由控制离开的时间,这也是我们通常说的异步编程,Hystrix是结合RxJava来实现的异步编程。通过设置线程池大小来控制并发访问量,当线程饱和的时候可以拒绝服务,防止依赖问题扩散。
在这里插入图片描述

1.1、服务降级代码实现:

1.1.1、引入依赖

   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

1.1.2、加入注解 @EnableCircuitBreaker
如果闲注解太多可以使用 @SpringCloudApplication组合注解替换

1.1.3、编写降级接口 一般写在feign调用接口下方(服务提供方)

@Controller
@RequestMapping("consumer/user")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    @ResponseBody
    @HystrixCommand(fallbackMethod = "queryUserByIdFallBack")
    public String queryUserById(@RequestParam("id") Long id) {
        String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
        return user;
    }

    public String queryUserByIdFallBack(Long id){
        return "请求繁忙,请稍后再试!";
    }
}

        要注意,因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以我们把queryById的方法改造为返回String,反正也是Json数据。这样失败逻辑中返回一个错误说明,会比较方便。

        当然你会提问是不是所有的方法都必须添加注解 @HystrixCommand 非也非也 也可直接设置全局降级方法

@Controller
@RequestMapping("consumer/user")
@DefaultProperties(defaultFallback = "fallBackMethod") // 指定一个类的全局熔断方法
public class UserController {

   @Autowired
   private RestTemplate restTemplate;

   @GetMapping
   @ResponseBody
   @HystrixCommand // 标记该方法需要熔断
   public String queryUserById(@RequestParam("id") Long id) {
       String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
       return user;
   }

   /**
    * 熔断方法
    * 返回值要和被熔断的方法的返回值一致
    * 熔断方法不需要参数
    * @return
    */
   public String fallBackMethod(){
       return "请求繁忙,请稍后再试!";
   }
}

@DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统一的失败降级方法
@HystrixCommand:在方法上直接使用该注解,使用默认的降级方法。
defaultFallback:默认降级方法,不用任何参数,以匹配更多方法,但是返回值一定一致

注:局部降级方法需要入参和返回都必修和熔断方法一致,全局的返回参数必须和被熔断方法一致参数列表必须为空

        在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystix的默认超时时长为1,我们可以通过配置修改这个值:
        我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。如下

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms
2、 服务熔断(参考保险丝原理)

       正所谓刮骨疗毒,壮士断腕。在这种时候,就需要我们的熔断机制来挽救整个系统。熔断就是上游服务队下游服务的一种处理方式。

  • 2.1、熔断原理
         当服务正常,熔断处于闭合状态,所有的请求都可以通过。如果当请求失败次数比例超过50%开启熔断,后面的所有请求(无论是正常请求还是不正常请求)均被熔断,无法访问服务。进入休眠期,随后进入半开状态,释放一部分请求,假如此时的请求健康,断路器关闭,请求可以正常访问服务但是如果后续请求都是不健康则再次打开断路器,再次休眠计时如此进行循环。

熔断状态:

  • Closed:关闭状态,所有请求都正常访问。
  • Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
  • HalfOpen:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时
@GetMapping("{id}")
@HystrixCommand
public String queryUserById(@PathVariable("id") Long id){
    if(id == 1){
        throw new RuntimeException("太忙了");
    }
    String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
    return user;
}

我们准备两个请求窗口:
一个请求:http://localhost/consumer/user/1,注定失败 //如果请求1设置休眠超时
一个请求:http://localhost/consumer/user/2,肯定成功 //不做任何超时

当我们疯狂访问id为1的请求时(超过20次),就会触发熔断。断路器会断开,一切请求都会被降级处理。
此时你访问id为2的请求,会发现返回的也是失败,而且失败时间很短,只有几毫秒左右:
不过,默认的熔断触发要求较高,休眠时间窗较短,为了测试方便,我们可以通过配置修改熔断策略:

circuitBreaker.requestVolumeThreshold=10
circuitBreaker.sleepWindowInMilliseconds=10000
circuitBreaker.errorThresholdPercentage=50

解读:

  • requestVolumeThreshold:触发熔断的最小请求次数,默认20
  • errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%
  • sleepWindowInMilliseconds:休眠时长,默认是5000毫秒

5、zuul网关

1、架构图:

在这里插入图片描述

2、定义(大型拦截器)

        服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

  • 作用:

       1、统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
       2、鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
       3、动态路由:动态的将请求路由到不同的后端集群中。
       4、减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。

3、快速运用

导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

因为zuul的使用伴随着负载均衡 所以需要注册到eureka上使用才有意义

4、zuul网关的四种路由配置
  • 使用路径名直接访问 容易出错 需要把负载均衡关闭 因为此时没有负载均衡效果
zuul:
  routes:
    service-provider:  #路由名称 随意取名 习惯取服务名
      path: /service-leyou/**
      url: http//localhost:8087
  • 通过服务ID(首先得注册eureka 由eureka中心的注册服务ID)
zuul:
  routes:
    service-provider: #路由名称 随意取名 习惯取服务名
      path: /service-leyou/**
      #url: http//localhost:8087
      #serviceID: service-leyou
  • 直接通过路由名称和eureka服务ID一致 (推荐)
zuul:
  routes:
    service-leyou: /service-leyou/**  #此时 前面的事服务ID 后面是路径前缀
  prefix: /api    #指定路由前缀 用以区分网关与服务  
  • 不配置路由 默认第三种服务ID寻找路径
5、zuul过滤器——ZuulFilter
public abstract ZuulFilter implements IZuulFilter{

    abstract public String filterType();

    abstract public int filterOrder();
    
    boolean shouldFilter();// 来自IZuulFilter

    Object run() throws ZuulException;// IZuulFilter
}
  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run:过滤器的具体业务逻辑。
  • filterType:返回字符串,代表过滤器的类型。包含以下4种:
    • pre:请求在被路由之前执行
    • route:在路由请求时调用
    • post:在route和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

ZuulFilter 的生命周期
在这里插入图片描述
周期图流程解读

  • 当 pre 出现异常 ——>error ——>post——>HTTP
  • 当 routing 出现异常——>error——>post——>HTTP
  • 当 error 出现异常——>post——>HTTP
  • 当 post 出现异常——>error——>HTTP

自定义filter

/**
 * @program: leyou
 * @description: //zuul网关
 * @author: wyy
 * @create: 2020-01-05 17:28
 **/
@Component
public class LoginFilter extends ZuulFilter {
    /**
     * 过滤器类型 pre、route、post、error
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 执行顺序 数字越小优先级越高
     * @return
     */
    @Override
    public int filterOrder() {
        return 10;
    }

    /**
     * 是否执行该过滤器 如果true则执行run方法 false不执行run方法
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 编写过滤器的业务逻辑
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        //获取zuul上下文
        RequestContext context = RequestContext.getCurrentContext();
        //获取request请求对象
        HttpServletRequest request = context.getRequest();
        //判断是否存在令牌
        String token = request.getHeader("token");
        if(StringUtils.isBlank(token)){
            String token1 = request.getParameter("token");
            if(StringUtils.isBlank(token1)){
                //false 拦截不转发请求
                context.setSendZuulResponse(false);
                //设置响应状态码  身份未认证
                context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
                //设置响应体
                context.setResponseBody("request error!");
            }
        }
        //返回值为null 表示该过滤器什么都不做
        return null;
    }
}
6、自定义熔断时间——zuul自动集成了负载均衡和熔断功能默认熔断时间是1S
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000 # 设置hystrix的超时时间为6000ms

使用zuul网关整合guava 框架实现接口令牌限流
实现原理图:
在这里插入图片描述
流程图剖析:
     使用zuul过滤器filter实现请求拦截,在filter设置一个全局变量令牌容器,创造令牌一般是1000个,每一次请求都会从容器中带走一个令牌,拥有令牌的请求是可以正常通过的,但是没有令牌的就必须等待容器重新生成令牌.
实现源码:

/**
 * 使用guava限流框架
 * @author wyy
 * @date
 */
public class OrderRateLimitFilter extends ZuulFilter {

    //每一秒生成1000个令牌   根据接口的压力来规定有多少个令牌
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return -4;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        /* System.out.println(request.getRequestURI());*/
        //需要对那个接口限流就把哪个接口写下去
        //ACL   拦截下面的接口 忽略大小写
        if ("/apigateway/order/api/v1/order/save".equalsIgnoreCase(request.getRequestURI())) {
            return true;
        }
        return false;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        if (!RATE_LIMITER.tryAcquire()) {
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
        }
        return null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值