【Springcloud】(00)认识Springcloud

(一)什么是Springcloud

在这里插入图片描述

【1】基本介绍

Springcloud就是微服务系统架构的一站式解决方案,在平时我们构建微服务的过程中需要做:服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等操作。Springcloud提供了一套简易的编程模型,帮助我们在springboot的基础上轻松完后才能微服务的构建

【2】什么是微服务?

(1)微服务
微服务是架构设计方式的一种,简单来说就是将传统的一站式应用,根据业务拆分成一个一个的服务,每一个微服务提供单个业务功能的服务,一个服务做一件事情。这个服务可以单独部署运行,拥有自己独立的数据库。服务之间可以通过RPC来相互交互,每个微服务都是由独立的小团队开发,测试,部署,上线,负责它的整个生命周期。

每个服务不再占用一台服务器,而是占用一个容器。

(2)分布式
分布式不是开发方式,而是一种部署方式,就是说,在分布式服务中,一个服务可能负责几个功能,系统应用部署在超过一台服务器或虚拟机上,服务之间通过rpc来交互或者是webservice来交互的。生产环境下的微服务肯定是分布式部署的,但是分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用。

(3)集群
集群是指在几个服务器上部署相同的应用程序来分担客户端的请求。它是同一个系统部署在不同的服务器上,集群主要的使用场景是为了分担请求的压力。

例如mysql集群实现主从复制、读写分离和分表分库。redis集群等等。

【3】业务场景介绍

假设咱们现在开发一个电商网站,要实现支付订单的功能,流程如下:
(1)创建一个订单后,如果用户立刻支付了这个订单,我们需要将订单状态更新为“已支付”
(2)扣减相应的商品库存
(3)通知仓储中心,进行发货
(4)给用户的这次购物增加相应的积分

针对上述流程,我们需要有订单服务、库存服务、仓储服务、积分服务。整个流程的大体思路如下:
(1)用户针对一个订单完成支付之后,就会去找订单服务,更新订单状态
(2)订单服务调用库存服务,完成相应功能
(3)订单服务调用仓储服务,完成相应功能
(4)订单服务调用积分服务,完成相应功能

在这里插入图片描述

【4】核心组件简介

(1)Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里
(2)Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
(3)Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
(4)Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
(5)Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务
在这里插入图片描述

(二)服务发现框架-Eureka

【1】Eureka作用

订单服务想要调用库存服务、仓储服务,或者积分服务,怎么调用?
(1)订单服务压根儿就不知道人家库存服务在哪台机器上啊!他就算想要发起一个请求,都不知道发送给谁
(2)Eureka就是微服务架构中的注册中心,专门负责服务的注册与发现。

在这里插入图片描述
如上图所示,库存服务、仓储服务、积分服务中都有一个Eureka Client组件,这个组件专门负责将这个服务的信息注册到Eureka Server中。说白了,就是告诉Eureka Server,自己在哪台机器上,监听着哪个端口。而Eureka Server是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口号

订单服务里也有一个Eureka Client组件,这个Eureka Client组件会找Eureka Server问一下:库存服务在哪台机器啊?监听着哪个端口啊?仓储服务呢?积分服务呢?然后就可以把这些相关信息从Eureka Server的注册表中拉取到自己本地缓存起来。

这时如果订单服务想要调用库存服务,不就可以找自己本地的Eureka Client问一下库存服务在哪台机器?监听哪个端口吗?收到响应后,紧接着就可以发送一个请求过去,调用库存服务扣减库存的那个接口!同理,如果订单服务要调用仓储服务、积分服务,也是如法炮制。

总结一下:
(1)Eureka Client:负责将这个服务的信息注册到Eureka Server中
(2)Eureka Server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号

【2】Eureka功能介绍

Eureka是基于REST(代表性状态转移)的服务,主要在 AWS 云中用于定位服务,以实现负载均衡和中间层服务器的故障转移。我们称此服务为Eureka服务器。Eureka还带有一个基于 Java 的客户端组件 Eureka Client,它使与服务的交互变得更加容易。客户端还具有一个内置的负载平衡器,可以执行基本的循环负载平衡。在 Netflix,更复杂的负载均衡器将 Eureka 包装起来,以基于流量,资源使用,错误条件等多种因素提供加权负载均衡,以提供出色的弹性。

(1)服务发现

其实就是一个“中介”,整个过程中有三个角色:服务提供者(出租房子的)、服务消费者(租客)、服务中介(房屋中介)。

(2)服务提供者

就是提供一些自己能够执行的一些服务给外界,例如cloud-provider-payment8001服务负责从数据库里查询数值。

(3)服务消费者

就是需要使用一些服务的“用户”,例如cloud-consumer-order80服务从cloud-provider-payment8001服务中取数据,而不是自己到数据库中取。

(4)服务中介

其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把自己注册到服务中介那里,而服务消费者如需要消费一些服务(使用一些功能)就可以在服务中介中寻找注册在服务中介的服务提供者。

如果不用服务注册中心,在消费者服务中就要使用RestTemplate的postForObject方法发送请求到提供者,然后再返回的请求结果。

如果用了服务注册中心

(5)服务注册Register

官方解释:当 Eureka 客户端向 Eureka Server 注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。

结合中介理解:房东 (提供者 Eureka Client Provider)在中介 (服务器 Eureka Server) 那里登记房屋的信息,比如面积,价格,地段等等(元数据 metaData)。

(6)服务续约Renew

官方解释:Eureka 客户会每隔30秒(默认情况下)发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka 客户仍然存在,没有出现问题。 正常情况下,如果 Eureka Server 在90秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。

结合中介理解:房东 (提供者 Eureka Client Provider) 定期告诉中介 (服务器 Eureka Server) 我的房子还租(续约) ,中介 (服务器Eureka Server) 收到之后继续保留房屋的信息。

(7)获取注册列表信息 Fetch Registries

官方解释:Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同, Eureka 客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。 Eureka 服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka 客户端和 Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下 Eureka 客户端使用压缩 JSON 格式来获取注册列表的信息。

结合中介理解:租客(消费者 Eureka Client Consumer) 去中介 (服务器 Eureka Server) 那里获取所有的房屋信息列表 (客户端列表 Eureka Client List) ,而且租客为了获取最新的信息会定期向中介 (服务器 Eureka Server) 那里获取并更新本地列表。

(8)服务下线

官方解释:Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();

结合中介理解:房东 (提供者 Eureka Client Provider) 告诉中介 (服务器 Eureka Server) 我的房子不租了,中介之后就将注册的房屋信息从列表中剔除。

(9)服务剔除Eviction

官方解释:在默认的情况下,当Eureka客户端连续90秒(3个续约周期)没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。

结合中介理解:房东(提供者 Eureka Client Provider) 会定期联系 中介 (服务器 Eureka Server) 告诉他我的房子还租(续约),如果中介 (服务器 Eureka Server) 长时间没收到提供者的信息,那么中介会将他的房屋信息给下架(服务剔除)。

在这里插入图片描述

(三)Open Feign

【1】Feign作用

现在订单服务确实知道库存服务、积分服务、仓库服务在哪里了,同时也监听着哪些端口号了。但是新问题又来了:难道订单服务要自己写一大堆代码,跟其他服务建立网络连接,然后构造一个复杂的请求,接着发送请求过去,最后对返回的响应结果再写一大堆代码来处理吗?

使用feign可以方便快捷的实现服务间的调用
在这里插入图片描述没有底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 FeignClient接口,然后调用那个接口就可以了。Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等。

【2】Feign作原理:动态代理

(1)首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理
(2)接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心
(3)Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址
(4)最后针对这个地址,发起请求、解析响应
在这里插入图片描述

【3】Open Feign的其他案例

有了 Eureka ,RestTemplate ,Ribbon, 我们就可以愉快地进行服务间的调用了,但是使用 RestTemplate 还是不方便。每次都要传入多个参数:url、请求、返回类型

可以使用映射来解决,就像域名和IP地址的映射,我们可以把被调用的服务代码映射到消费者端。OpenFeign 也是运行在消费者端的,使用 Ribbon 进行负载均衡,所以 OpenFeign 直接内置了 Ribbon。

使用OpenFeign的注解就可以让消费者找到提供者了,不需要像使用RestTemplate时那样定向的往某个消费者发送请求了。

// 使用 @FeignClient 注解来指定提供者的名字
@FeignClient(value = "eureka-client-provider")
public interface TestClient {
    // 这里一定要注意需要使用的是提供者那端的请求相对路径,这里就相当于映射了
    @RequestMapping(value = "/provider/xxx",
    method = RequestMethod.POST)
    CommonResponse<List<Plan>> getPlans(@RequestBody planGetRequest request);
}

在Controller中就可以像原来调用Service层代码一样调用它

@RestController
public class TestController {
    // 这里就相当于原来自动注入的 Service
    @Autowired
    private TestClient testClient;
    // controller 调用 service 层代码
    @RequestMapping(value = "/test", method = RequestMethod.POST)
    public CommonResponse<List<Plan>> get(@RequestBody planGetRequest request) {
        return testClient.getPlans(request);
    }
}

(四)负载均衡-Ribbon

【1】Ribbon作用

如果人家库存服务部署在了5台机器上,如下所示:

192.168.169:9000
192.168.170:9000
192.168.171:9000
192.168.172:9000
192.168.173:9000

Feign怎么知道该请求哪台机器呢?Ribbon就是专门解决这个问题的。它的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上。

Ribbon的负载均衡默认使用的最经典的Round Robin轮询算法。这是啥?简单来说,就是如果订单服务对库存服务发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器、第4台机器、第5台机器,接着再来—个循环,第1台机器、第2台机器。。。以此类推。

此外,Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体如下:
(1)首先Ribbon会从 Eureka Client里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号。
(2)然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器
(3)Feign就会针对这台机器,构造并发起请求。
在这里插入图片描述

【2】什么是 RestTemplate

RestTemplate是Spring提供的一个访问Http服务的客户端类,就是微服务之间的调用是使用的 RestTemplate 。比如这个时候我们 消费者B 需要调用 提供者A 所提供的服务我们就需要这么写。如我下面的伪代码

@Autowired
private RestTemplate restTemplate;
// 这里是提供者A的ip地址,但是如果使用了 Eureka 那么就应该是提供者A的名称
private static final String SERVICE_PROVIDER_A = "http://localhost:8081";

@PostMapping("/judge")
public boolean judge(@RequestBody Request request) {
    String url = SERVICE_PROVIDER_A + "/service1";
    return restTemplate.postForObject(url, request, Boolean.class);
}

【3】为什么用Ribbon

Ribbon 是 Netflix 公司的一个开源的负载均衡 项目,是一个客户端/进程内负载均衡器,运行在消费者端。

其工作原理就是 Consumer 端获取到了所有的服务列表之后,在其内部使用负载均衡算法,进行对多个系统的调用。

在这里插入图片描述

【4】Nginx 和 Ribbon 的对比

Nginx是一种集中式的负载均衡器,把所有请求都集中起来,然后再进行负载均衡
在这里插入图片描述Ribbon是在消费者端进行的负载均衡。Nginx中请求时先进入负载均衡器,而Ribbon中是先在客户端进行负载均衡才进行请求的。
在这里插入图片描述

【5】Ribbon 的集中负载均衡算法

负载均衡,不管Nginx还是Ribbon都需要算法的支持,Nginx中用的是轮询和加权轮询算法。而在Ribbon中有更多的负载均衡调度算法,默认用的是 RoundRobinRule 轮询策略。

  • RoundRobinRule:轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮。若最终还没有找到,则返回 null
  • RandomRule: 随机策略,从所有可用的 provider 中随机选择一个
  • RetryRule: 重试策略。先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒

使用的时候,只需要在配置文件中做出修改即可:

providerName:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

在Ribbon中还可以自定义负载均衡算法,只需要实现IRule接口,然后修改配置文件或者自定义Java Config类

(五)熔断降级Hystrix

【1】Hystrix作用

在微服务架构里,一个系统会有很多的服务。以本文的业务场景为例:订单服务在一个业务流程里需要调用三个服务。现在假设订单服务自己最多只有100个线程可以处理请求,然后呢,积分服务不幸的挂了,每次订单服务调用积分服务的时候,都会卡住几秒钟,然后抛出—个超时异常。

这样会导致什么问题?
(1)如果系统处于高并发的场景下,大量请求涌过来的时候,订单服务的100个线程都会卡在请求积分服务这块。导致订单服务没有一个线程可以处理请求
(2)然后就会导致别人请求订单服务的时候,发现订单服务也挂了,不响应任何请求了

上面这个,就是微服务架构中恐怖的服务雪崩问题,如下图所示:
在这里插入图片描述
如上图,这么多服务互相调用,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。比如积分服务挂了,会导致订单服务的线程全部卡在请求积分服务这里,没有一个线程可以工作,瞬间导致订单服务也挂了,别人请求订单服务全部会卡住,无法响应。

就算积分服务挂了,订单服务也可以不用挂啊!为什么?
(1)我们结合业务来看:支付订单的时候,只要把库存扣减了,然后通知仓库发货就OK了
(2)如果积分服务挂了,大不了等他恢复之后,慢慢人肉手工恢复数据!为啥一定要因为一个积分服务挂了,就直接导致订单服务也挂了呢?不可以接受!

Hystrix来解决这个问题。Hystrix是隔离、熔断以及降级的一个框架。啥意思呢?说白了,Hystrix会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。

如果积分服务挂了,会怎么样?

当然会导致订单服务里那个用来调用积分服务的线程都卡死不能工作了啊!但由于订单服务调用库存服务、仓储服务的这两个线程池都是正常工作的,所以这两个服务不会受到任何影响。

这个时候如果别人请求订单服务,订单服务还是可以正常调用库存服务扣减库存,调用仓储服务通知发货。只不过调用积分服务的时候,每次都会报错。但是如果积分服务都挂了,每次调用都要去卡住几秒钟干啥呢?有意义吗?当然没有!所以我们直接对积分服务熔断不就得了,比如在5分钟内请求积分服务直接就返回了,不要去走网络请求卡住几秒钟,这个过程,就是所谓的熔断!

那人家又说,兄弟,积分服务挂了你就熔断,好歹你干点儿什么啊!别啥都不干就直接返回啊?没问题,咱们就来个降级:每次调用积分服务,你就在数据库里记录一条消息,说给某某用户增加了多少积分,因为积分服务挂了,导致没增加成功!这样等积分服务恢复了,你可以根据这些记录手工加一下积分。这个过程,就是所谓的降级。

接下来用一张图,梳理一下Hystrix隔离、熔断和降级的全流程:
在这里插入图片描述

【2】什么是熔断降级

(1)基本介绍
在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。

(2)服务雪崩
那么什么是 熔断和降级 呢?再举个例子,此时我们整个微服务系统是这样的。服务A调用了服务B,服务B再调用了服务C,但是因为某些原因,服务C顶不住了,这个时候大量请求会在服务C阻塞。

服务C阻塞了还好,毕竟只是一个系统崩溃了。但是请注意这个时候因为服务C不能返回响应,那么服务B调用服务C的的请求就会阻塞,同理服务B阻塞了,那么服务A也会阻塞崩溃。这就叫 服务雪崩

(3)熔断
所谓 熔断 就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过 断路器 直接将此请求链路断开。

也就是我们上面服务B调用服务C在指定时间窗内,调用的失败率到达了一定的值,那么 Hystrix 则会自动将 服务B与C 之间的请求都断了,以免导致服务雪崩现象。

其实这里所讲的 熔断 就是指的 Hystrix 中的 断路器模式 ,你可以使用简单的 @HystrixCommand 注解来标注某个方法,这样 Hystrix 就会使用 断路器 来“包装”这个方法,每当调用时间超过指定时间时(默认为1000ms),断路器将会中断对这个方法的调用。

当然你可以对这个注解的很多属性进行设置,比如设置超时时间,像这样。

@HystrixCommand(
    commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1200")}
)
public List<Xxx> getXxxx() {
    // ...省略代码逻辑
}

(4)降级
降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。这也就对应着 Hystrix 的 后备处理 模式。你可以通过设置 fallbackMethod 来给一个方法设置备用的代码逻辑。比如这个时候有一个热点新闻出现了,我们会推荐给用户查看详情,然后用户会通过id去查询新闻的详情,但是因为这条新闻太火了,大量用户同时访问可能会导致系统崩溃,那么我们就进行 服务降级 ,一些请求会做一些降级处理比如当前人数太多请稍后查看等等。

// 指定了后备方法调用
@HystrixCommand(fallbackMethod = "getHystrixNews")
@GetMapping("/get/news")
public News getNews(@PathVariable("id") int id) {
    // 调用新闻系统的获取新闻api 代码逻辑省略
}
// 
public News getHystrixNews(@PathVariable("id") int id) {
    // 做服务降级
    // 返回当前人数太多,请稍后查看
}

(六)微服务网关-Zuul

【1】Zuul作用

Zuul,也就是微服务网关。这个组件是负责网络路由的。如果没有Zuul的日常工作会怎样?

假设你后台部署了几百个服务,现在有个前端兄弟,人家请求是直接从浏览器那儿发过来的。打个比方:人家要请求一下库存服务,你难道还让人家记着这服务的名字叫做inventory-service?部署在5台机器上?就算人家肯记住这一个,你后台可有几百个服务的名称和地址呢?难不成人家请求一个,就得记住一个?

上面这种情况,压根儿是不现实的。所以一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。

而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。

【2】Zuul基本介绍

ZUUL 是从设备和 web 站点到 Netflix 流应用后端的所有请求的前门。作为边界服务应用,ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。

服务提供者 是 消费者 通过 Eureka Server 进行访问的,即 Eureka Server 是 服务提供者 的统一入口。那么整个应用中存在那么多 消费者 需要用户进行调用,这个时候用户该**怎样访问这些 消费者工程 呢?**当然可以像之前那样直接访问这些工程。但这种方式没有统一的消费者工程调用入口,不便于访问与管理,而 Zuul 就是这样的一个对于 消费者 的统一入口。

网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权、限流、 路由、监控等功能。网关有的功能,Zuul 基本都有。而 Zuul 中最关键的就是 路由和过滤器 了(Router and Filter)

【3】Zuul的路由功能

(1)简单配置

比如这个时候我们已经向 Eureka Server 注册了两个 Consumer 、三个 Provicer ,这个时候我们再加个 Zuul 网关应该变成这样子了

在这里插入图片描述
首先,Zuul 需要向 Eureka 进行注册,网关只要注册就能拿到 Eureka 中的所有 Consumer 的信息,拿到信息就可以获取所有 Consumer 的元数据(名称,ip,端口),拿到元数据就可以直接做路由映射,比如原来用户调用 Consumer1 的接口 localhost:8001/studentInfo/update 这个请求,我们可以这样进行调用了:localhost:9000/consumer1/studentInfo/update

先是写好配置文件yml,然后在启动类上加入@EnableZuulProxy 注解就行了

(2)Zuul的统一前缀
zuul:
  prefix: /zuul

这样我们就需要通过 localhost:9000/zuul/consumer1/studentInfo/update 来进行访问了

(3)Zuul的路由策略配置

前面的访问方式(直接使用服务名),需要将微服务名称暴露给用户,会存在安全性问题。所以,可以自定义路径来替代微服务名称,即自定义路由策略

zuul:
  routes:
    consumer1: /FrancisQ1/**
    consumer2: /FrancisQ2/**

这个时候你就可以使用 localhost:9000/zuul/FrancisQ1/studentInfo/update` 进行访问了

(4)Zuul的服务名屏蔽

你配置完路由策略之后使用微服务名称还是可以访问的,这个时候你需要将服务名屏蔽

zuul:
  ignore-services: "*"
(5)Zuul的路径屏蔽

Zuul 还可以指定屏蔽掉的路径 URI,即只要用户请求中包含指定的 URI 路径,那么该请求将无法访问到指定的服务。通过该方式可以限制用户的权限。

zuul:
  ignore-patterns: **/auto/**
(6)Zuul的敏感请求头屏蔽

默认情况下,像 Cookie、Set-Cookie 等敏感请求头信息会被 zuul 屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。

【4】Zuul的过滤功能

(1)基本介绍

路由功能是 Zuul 的基操,那么过滤器就是 Zuul的利器。毕竟所有请求都经过网关(Zuul),那么我们可以进行各种过滤,这样我们就能实现 限流,灰度发布,权限控制 等等。

(2)简单实现一个请求时间日志打印

要实现自己定义的 Filter 我们只需要继承 ZuulFilter 然后将这个过滤器类以 @Component 注解加入 Spring 容器中就行了
在这里插入图片描述过滤器类型:Pre、Routing、Post。前置Pre就是在请求之前进行过滤,Routing路由过滤器就是我们上面所讲的路由策略,而Post后置过滤器就是在 Response 之前进行过滤的过滤器。

// 加入Spring容器
@Component
public class PreRequestFilter extends ZuulFilter {
    // 返回过滤器类型 这里是前置过滤器
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
    // 指定过滤顺序 越小越先执行,这里第一个执行
    // 当然不是只真正第一个 在Zuul内置中有其他过滤器会先执行
    // 那是写死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3
    @Override
    public int filterOrder() {
        return 0;
    }
    // 什么时候该进行过滤
    // 这里我们可以进行一些判断,这样我们就可以过滤掉一些不符合规定的请求等等
    @Override
    public boolean shouldFilter() {
        return true;
    }
    // 如果过滤器允许通过则怎么进行处理
    @Override
    public Object run() throws ZuulException {
        // 这里我设置了全局的RequestContext并记录了请求开始时间
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.set("startTime", System.currentTimeMillis());
        return null;
    }
}
// lombok的日志
@Slf4j
// 加入 Spring 容器
@Component
public class AccessLogFilter extends ZuulFilter {
    // 指定该过滤器的过滤类型
    // 此时是后置过滤器
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }
    // SEND_RESPONSE_FILTER_ORDER 是最后一个过滤器
    // 我们此过滤器在它之前执行
    @Override
    public int filterOrder() {
        return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
    }
    @Override
    public boolean shouldFilter() {
        return true;
    }
    // 过滤时执行的策略
    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        // 从RequestContext获取原先的开始时间 并通过它计算整个时间间隔
        Long startTime = (Long) context.get("startTime");
        // 这里我可以获取HttpServletRequest来获取URI并且打印出来
        String uri = request.getRequestURI();
        long duration = System.currentTimeMillis() - startTime;
        log.info("uri: " + uri + ", duration: " + duration / 100 + "ms");
        return null;
    }
}
(3)令牌桶限流

(1)什么是令牌桶限流
首先我们会有个桶,如果里面没有满那么就会以一定 固定的速率 会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。
在这里插入图片描述
(2)通过 Zuul 的前置过滤器来实现一下令牌桶限流

@Component
@Slf4j
public class RouteFilter extends ZuulFilter {
    // 定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

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

    @Override
    public Object run() throws ZuulException {
        log.info("放行");
        return null;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        if(!RATE_LIMITER.tryAcquire()) {
            log.warn("访问量超载");
            // 指定当前请求未通过过滤
            context.setSendZuulResponse(false);
            // 向客户端返回响应码429,请求数量过多
            context.setResponseStatusCode(429);
            return false;
        }
        return true;
    }
}

【5】Zuul的其他

Zuul还可以实现权限校验、灰度发布等等

Zuul作为网关也存在单点问题,如果我们要保证 Zuul 的高可用,我们就需要进行 Zuul 的集群配置,这个时候可以借助额外的一些负载均衡器比如 Nginx

【6】为什么要使用进行配置管理

(1)配置文件的管理

那么多 Consumer 、Provider 、Eureka Server 、Zuul 系统都会持有自己的配置,这个时候我们在项目运行的时候可能需要更改某些应用的配置,如果我们不进行配置的统一管理,我们只能去每个应用下一个一个寻找配置文件然后修改配置文件再重启应用

有一种方法既能对配置文件统一地进行管理,又能在项目运行时动态修改配置文件。那就是 Spring Cloud Config

(2)Config是什么

Spring Cloud Config 就是能将各个 应用/系统/模块 的配置文件存放到 统一的地方然后进行管理(Git 或者 SVN)。

应用只有启动的时候才会进行配置文件的加载,那么我们的 Spring Cloud Config 就暴露出一个接口给启动应用来获取它所想要的配置文件,应用获取到配置文件然后再进行它的初始化工作。
在这里插入图片描述一般我们会使用 Bus 消息总线 + Spring Cloud Config 进行配置的动态刷新

(七)Spring Cloud Bus消息总线

可以简单理解为 Spring Cloud Bus 的作用就是管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式。当然作为 消息总线 的 Spring Cloud Bus 可以做很多事而不仅仅是客户端的配置刷新功能。

而拥有了 Spring Cloud Bus 之后,我们只需要创建一个简单的请求,并且加上 @ResfreshScope 注解就能进行配置的动态修改了。
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值