首先我给大家看一张图,如果大家对这张图有些中央不太了解的话,我心愿你们看完我这篇文章会豁然开朗。
什么是 Spring cloud
构建分布式系统不须要简单和容易出错。Spring Cloud 为最常见的分布式系统模式提供了一种简略且易于承受的编程模型,帮忙开发人员构建有弹性的、牢靠的、协调的应用程序。Spring Cloud 构建于 Spring Boot 之上,使得开发者很容易动手并疾速利用于生产中。
官网果然官网,介绍都这么一板一眼的。
我所了解的 Spring Cloud
就是微服务零碎架构的一站式解决方案,在平时咱们构建微服务的过程中须要做如 服务发现注册 、 配置核心 、 音讯总线 、 负载平衡 、 断路器 、 数据监控 等操作,而 Spring Cloud 为咱们提供了一套繁难的编程模型,使咱们能在 Spring Boot 的根底上轻松地实现微服务项目的构建。
Spring Cloud 的版本
当然这个只是个题外话。
Spring Cloud
的版本号并不是咱们通常见的数字版本号,而是一些很奇怪的单词。这些单词均为英国伦敦地铁站的站名。同时依据字母表的程序来对应版本工夫程序,比方:最早 的 Release
版本 Angel
,第二个 Release
版本 Brixton
(英国地名),而后是 Camden
、Dalston
、Edgware
、Finchley
、Greenwich
、Hoxton
。
Spring Cloud 的服务发现框架——Eureka
Eureka
是基于REST
(代表性状态转移)的服务,次要在AWS
云中用于定位服务,以实现负载平衡和中间层服务器的故障转移。咱们称此服务为Eureka
服务器。Eureka 还带有一个基于Java
的客户端组件Eureka Client
,它使与服务的交互变得更加容易。客户端还具备一个内置的负载平衡器,能够执行根本的循环负载平衡。在Netflix
,更简单的负载均衡器将Eureka
包装起来,以基于流量,资源应用,谬误条件等多种因素提供加权负载平衡,以提供杰出的弹性。
总的来说,Eureka
就是一个服务发现框架。何为服务,何又为发现呢?
举一个生存中的例子,就比方咱们平时租房子找中介的事件。
在没有中介的时候咱们须要一个一个去寻找是否有屋宇要出租的房东,这显然会十分的费劲,一你找凭一个人的能力是找不到很多房源供你抉择,再者你也懒得这么找上来 (找了这么久,没有适合的只能将就)。 这里的咱们就相当于微服务中的 Consumer
,而那些房东就相当于微服务中的 Provider
。消费者 Consumer
须要调用提供者 Provider
提供的一些服务,就像咱们当初须要租他们的房子一样。
然而如果只是租客和房东之间进行寻找的话,他们的效率是很低的,房东找不到租客赚不到钱,租客找不到房东住不了房。所以,起初房东必定就想到了播送本人的房源信息 (比方在街边贴贴小广告),这样对于房东来说曾经实现他的工作(将房源颁布进来),然而有两个问题就呈现了。第一、其余不是租客的都能收到这种租房音讯,这在事实世界没什么,然而在计算机的世界中就会呈现 资源耗费 的问题了。第二、租客这样还是很难找到你,试想一下我须要租房,我还须要东一个西一个地去找街边小广告,麻不麻烦?
那怎么办呢?咱们当然不会那么傻乎乎的,第一工夫就是去找 中介 呀,它为咱们提供了对立房源的中央,咱们消费者只须要跑到它那里去找就行了。而对于房东来说,他们也只须要把房源在中介那里公布就行了。
那么当初,咱们的模式就是这样的了。
然而,这个时候还会呈现一些问题。
- 房东注册之后如果不想卖房子了怎么办?咱们是不是须要让房东 定期续约 ?如果房东不进行续约是不是要将他们从中介那里的注册列表中 移除。
- 租客是不是也要进行 注册 呢?不然合同乙方怎么来呢?
- 中介可不可以做 连锁店 呢?如果这一个店因为某些不可抗力因素而无奈应用,那么咱们是否能够换一个连锁店呢?
针对下面的问题咱们来从新构建一下下面的模式图
好了,举完这个例子!
咱们就能够来看对于 Eureka
的一些根底概念了,你会发现这货色了解起来怎么这么简略。
服务发现 :其实就是一个“中介”,整个过程中有三个角色: 服务提供者(出租房子的)、服务消费者(租客)、服务中介(房屋中介)。
服务提供者:就是提供一些本人可能执行的一些服务给外界。
服务消费者:就是须要应用一些服务的“用户”。
服务中介 :其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者能够把本人注册到服务中介那里,而服务消费者如须要生产一些服务(应用一些性能) 就能够在服务中介中寻找注册在服务中介的服务提供者。
服务注册 Register:
官网解释:当 Eureka
客户端向 Eureka Server
注册时,它提供本身的 元数据,比方 IP 地址、端口,运行状况批示符 URL,主页等。
联合中介了解:房东 (提供者 Eureka Client Provider
)在中介 (服务器 Eureka Server
) 那里注销屋宇的信息,比方面积,价格,地段等等(元数据 metaData
)。
服务续约 Renew:
官网解释:Eureka
客户会每隔 30 秒 (默认状况下) 发送一次心跳来续约。通过续约来告知 Eureka Server
该 Eureka
客户依然存在,没有呈现问题。失常状况下,如果 Eureka Server
在 90 秒没有收到 Eureka
客户的续约,它会将实例从其注册表中删除。
联合中介了解:房东 (提供者 Eureka Client Provider
) 定期通知中介 (服务器 Eureka Server
) 我的房子还租(续约),中介 (服务器Eureka Server
) 收到之后持续保留屋宇的信息。
获取注册列表信息 Fetch Registries:
官网解释:Eureka
客户端从服务器获取注册表信息,并将其缓存在本地。客户端会应用该信息查找其余服务,从而进行近程调用。该注册列表信息定期(每 30 秒钟)更新一次。每次返回注册列表信息可能与 Eureka
客户端的缓存信息不同, Eureka
客户端主动解决。如果因为某种原因导致注册列表信息不能及时匹配,Eureka
客户端则会从新获取整个注册表信息。Eureka
服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka
客户端和 Eureka
服务器能够应用 JSON / XML 格局进行通信。在默认的状况下 Eureka
客户端应用压缩 JSON
格局来获取注册列表的信息。
联合中介了解:租客(消费者 Eureka Client Consumer
) 去中介 (服务器 Eureka Server
) 那里获取所有的房屋信息列表 (客户端列表 Eureka Client List
),而且租客为了获取最新的信息会定期向中介 (服务器 Eureka Server
) 那里获取并更新本地列表。
服务下线 Cancel:
官网解释:Eureka 客户端在程序敞开时向 Eureka 服务器发送勾销申请。发送申请后,该客户端实例信息将从服务器的实例注册表中删除。该下线申请不会主动实现,它须要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();
联合中介了解:房东 (提供者 Eureka Client Provider
) 通知中介 (服务器 Eureka Server
) 我的房子不租了,中介之后就将注册的房屋信息从列表中剔除。
服务剔除 Eviction:
官网解释:在默认的状况下,当 Eureka 客户端间断 90 秒 (3 个续约周期) 没有向 Eureka 服务器发送服务续约,即心跳,Eureka 服务器会将该服务实例从服务注册列表删除,即服务剔除。
联合中介了解:房东(提供者 Eureka Client Provider
) 会定期分割 中介 (服务器 Eureka Server
) 通知他我的房子还租(续约),如果中介 (服务器 Eureka Server
) 长时间没收到提供者的信息,那么中介会将他的房屋信息给下架(服务剔除)。
上面就是 Netflix
官网给出的 Eureka
架构图,你会发现和咱们后面画的中介图别无二致。
当然,能够充当服务发现的组件有很多:Zookeeper
,Consul
,Eureka
等。
更多对于 Eureka
的常识 (自我爱护,初始注册策略等等) 能够本人去官网查看,或者查看我的另一篇文章 深刻了解 Eureka。
负载平衡之 Ribbon
什么是 RestTemplate
?
不是讲 Ribbon
么?怎么扯到了 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);
}
如果你对源码感兴趣的话,你会发现下面咱们所讲的 Eureka
框架中的 注册 、 续约 等,底层都是应用的 RestTemplate
。
为什么须要 Ribbon?
Ribbon
是 Netflix
公司的一个开源的负载平衡 我的项目,是一个客户端 / 过程内负载均衡器,运行在消费者端。
咱们再举个例子,比方咱们设计了一个秒杀零碎,然而为了整个零碎的 高可用,咱们须要将这个零碎做一个集群,而这个时候咱们消费者就能够领有多个秒杀零碎的调用路径了,如下图。
如果这个时候咱们没有进行一些 平衡操作 ,如果咱们对 秒杀零碎 1
进行大量的调用,而另外两个根本不申请,就会导致 秒杀零碎 1
解体,而另外两个就变成了傀儡,那么咱们为什么还要做集群,咱们高可用体现的意义又在哪呢?
所以 Ribbon
呈现了,留神咱们下面加粗的几个字——运行在消费者端。指的是,Ribbon
是运行在消费者端的负载均衡器,如下图。
其工作原理就是 Consumer
端获取到了所有的服务列表之后,在其 外部 应用 负载平衡算法,进行对多个零碎的调用。
Nginx 和 Ribbon 的比照
提到 负载平衡 就不得不提到赫赫有名的 Nignx
了,而和 Ribbon
不同的是,它是一种 集中式 的负载均衡器。
何为集中式呢?简略了解就是 将所有申请都集中起来,而后再进行负载平衡。如下图。
咱们能够看到 Nginx
是接管了所有的申请进行负载平衡的,而对于 Ribbon
来说它是在消费者端进行的负载平衡。如下图。
请留神
Request
的地位,在Nginx
中申请是先进入负载均衡器,而在Ribbon
中是先在客户端进行负载平衡才进行申请的。
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
类。
什么是 Open Feign
有了 Eureka
,RestTemplate
,Ribbon
,咱们就能够欢快地进行服务间的调用了,然而应用 RestTemplate
还是不不便,咱们每次都要进行这样的调用。
@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";
// 是不是太麻烦了???每次都要 url、申请、返回类型的
return restTemplate.postForObject(url, request, Boolean.class);
}
这样每次都调用 RestRemplate
的 API
是否太麻烦,我能不能像 调用原来代码一样进行各个服务间的调用呢?
聪慧的小朋友必定想到了,那就用 映射 呀,就像域名和 IP 地址的映射。咱们能够将被调用的服务代码映射到消费者端,这样咱们就能够 “无缝开发” 啦。
OpenFeign
也是运行在消费者端的,应用Ribbon
进行负载平衡,所以OpenFeign
间接内置了Ribbon
。
在导入了 Open Feign
之后咱们就能够进行欢快编写 Consumer
端代码了。
// 应用 @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);
}
}
必不可少的 Hystrix
什么是 Hystrix 之熔断和降级
在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix 是一个库,可通过增加等待时间容限和容错逻辑来帮忙您管制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的拜访点,进行服务之间的级联故障并提供后备选项来实现此目标,所有这些都能够进步零碎的整体弹性。
总体来说 Hystrix
就是一个能进行 熔断 和 降级 的库,通过应用它能进步整个零碎的弹性。
那么什么是 熔断和降级 呢?再举个例子,此时咱们整个微服务零碎是这样的。服务 A 调用了服务 B,服务 B 再调用了服务 C,然而因为某些起因,服务 C 顶不住了,这个时候大量申请会在服务 C 阻塞。
服务 C 阻塞了还好,毕竟只是一个零碎解体了。然而请留神这个时候因为服务 C 不能返回响应,那么服务 B 调用服务 C 的的申请就会阻塞,同理服务 B 阻塞了,那么服务 A 也会阻塞解体。
请留神,为什么阻塞会解体。因为这些申请会耗费占用零碎的线程、IO 等资源,耗费完你这个零碎服务器不就崩了么。
这就叫 服务雪崩 。妈耶,下面两个 熔断 和 降级 你都没给我解释分明,你当初又给我扯什么 服务雪崩?
别急,听我缓缓道来。
不听我也得讲下去!
所谓 熔断 就是服务雪崩的一种无效解决方案。当指定工夫窗内的申请失败率达到设定阈值时,零碎将通过 断路器 间接将此申请链路断开。
也就是咱们下面服务 B 调用服务 C 在指定工夫窗内,调用的失败率达到了肯定的值,那么 Hystrix
则会主动将 服务 B 与 C 之间的申请都断了,免得导致服务雪崩景象。
其实这里所讲的 熔断 就是指的 Hystrix
中的 断路器模式 ,你能够应用简略的 @HystrixCommand
注解来标注某个办法,这样 Hystrix
就会应用 断路器 来“包装”这个办法,每当调用工夫超过指定工夫时(默认为 1000ms),断路器将会中断对这个办法的调用。
当然你能够对这个注解的很多属性进行设置,比方设置超时工夫,像这样。
@HystrixCommand(commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1200")}
)
public List<Xxx> getXxxx() {// ... 省略代码逻辑}
然而,我查阅了一些博客,发现他们都将 熔断 和 降级 的概念混同了,以我的了解, 降级是为了更好的用户体验,当一个办法调用异样时,通过执行另一种代码逻辑来给用户敌对的回复 。这也就对应着 Hystrix
的 后备解决 模式。你能够通过设置 fallbackMethod
来给一个办法设置备用的代码逻辑。比方这个时候有一个热点新闻呈现了,咱们会举荐给用户查看详情,而后用户会通过 id 去查问新闻的详情,然而因为这条新闻太火了(比方最近什么 * 易对吧),大量用户同时拜访可能会导致系统解体,那么咱们就进行 服务降级,一些申请会做一些降级解决比方以后人数太多请稍后查看等等。
// 指定了后备办法调用
@HystrixCommand(fallbackMethod = "getHystrixNews")
@GetMapping("/get/news")
public News getNews(@PathVariable("id") int id) {// 调用新闻零碎的获取新闻 api 代码逻辑省略}
//
public News getHystrixNews(@PathVariable("id") int id) {
// 做服务降级
// 返回以后人数太多,请稍后查看
}
什么是 Hystrix 之其余
我在浏览《Spring 微服务实战》这本书的时候还接触到了一个 舱壁模式 的概念。在不应用舱壁模式的状况下,服务 A 调用服务 B,这种调用默认的是 应用同一批线程来执行 的,而在一个服务呈现性能问题的时候,就会呈现所有线程被刷爆并期待解决工作,同时阻塞新申请,最终导致程序解体。而舱壁模式会将近程资源调用隔离在他们本人的线程池中,以便能够管制单个体现不佳的服务,而不会使该程序解体。
具体其原理我举荐大家本人去理解一下,本篇文章中对 舱壁模式 不做过多解释。当然还有 Hystrix
仪表盘,它是 用来实时监控 Hystrix
的各项指标信息的,这里我将这个问题也抛出去,心愿有不理解的能够本人去搜寻一下。
微服务网关——Zuul
ZUUL 是从设施和 web 站点到 Netflix 流利用后端的所有申请的前门。作为边界服务利用,ZUUL 是为了实现动静路由、监督、弹性和安全性而构建的。它还具备依据状况将申请路由到多个 Amazon Auto Scaling Groups(亚马逊主动缩放组,亚马逊的一种云计算形式)的能力
在下面咱们学习了 Eureka
之后咱们晓得了 服务提供者 是 消费者 通过 Eureka Server
进行拜访的,即 Eureka Server
是 服务提供者 的对立入口。那么整个利用中存在那么多 消费者 须要用户进行调用,这个时候用户该怎么拜访这些 消费者工程 呢?当然能够像之前那样间接拜访这些工程。但这种形式没有对立的消费者工程调用入口,不便于拜访与治理,而 Zuul 就是这样的一个对于 消费者 的对立入口。
如果学过前端的必定都晓得 Router 吧,比方 Flutter 中的路由,Vue,React 中的路由,用了 Zuul 你会发现在路由性能方面和前端配置路由根本是一个理。
我偶然撸撸 Flutter。
大家对网关应该很熟吧,简略来讲网关是零碎惟一对外的入口,介于客户端与服务器端之间,用于对申请进行 鉴权 、 限流 、 路由 、 监控 等性能。
没错,网关有的性能,Zuul
根本都有。而 Zuul
中最要害的就是 路由和过滤器 了,在官网文档中 Zuul
的题目就是
Router and Filter : Zuul
Zuul 的路由性能
简略配置
原本想给你们复制一些代码,然而想了想,因为各个代码配置比拟零散,看起来也比拟零散,我决定还是给你们画个图来解释吧。
比方这个时候咱们曾经向 Eureka Server
注册了两个 Consumer
、三个 Provicer
,这个时候咱们再加个 Zuul
网关应该变成这样子了。
emmm,信息量有点大,我来解释一下。对于后面的常识我就不解释了。
首先,Zuul
须要向 Eureka
进行注册,注册有啥益处呢?
你傻呀,Consumer
都向 Eureka Server
进行注册了,我网关是不是只有注册就能拿到所有 Consumer
的信息了?
拿到信息有什么益处呢?
我拿到信息我是不是能够获取所有的 Consumer
的元数据(名称,ip,端口)?
拿到这些元数据有什么益处呢?拿到了咱们是不是间接能够做 路由映射?比方原来用户调用 Consumer1
的接口 localhost:8001/studentInfo/update
这个申请,咱们是不是能够这样进行调用了呢?localhost:9000/consumer1/studentInfo/update
呢?你这样是不是豁然开朗了?
这里的 url 为了让更多人看懂所以没有应用 restful 格调。
下面的你了解了,那么就能了解对于 Zuul
最根本的配置了,看上面。
server:
port: 9000
eureka:
client:
service-url:
# 这里只有注册 Eureka 就行了
defaultZone: http://localhost:9997/eureka
而后在启动类上退出 @EnableZuulProxy
注解就行了。没错,就是那么简略。
对立前缀
这个很简略,就是咱们能够在后面加一个对立的前缀,比方咱们刚刚调用的是 localhost:9000/consumer1/studentInfo/update
,这个时候咱们在 yaml
配置文件中增加如下。
zuul:
prefix: /zuul
这样咱们就须要通过 localhost:9000/zuul/consumer1/studentInfo/update
来进行拜访了。
路由策略配置
你会发现后面的拜访形式(间接应用服务名),须要将微服务名称裸露给用户,会存在安全性问题。所以,能够自定义门路来代替微服务名称,即自定义路由策略。
zuul:
routes:
consumer1: /FrancisQ1/**
consumer2: /FrancisQ2/**
这个时候你就能够应用 localhost:9000/zuul/FrancisQ1/studentInfo/update
进行拜访了。
服务名屏蔽
这个时候你别以为你好了,你能够试试,在你配置完路由策略之后应用微服务名称还是能够拜访的,这个时候你须要将服务名屏蔽。
zuul:
ignore-services: "*"
门路屏蔽
Zuul
还能够指定屏蔽掉的门路 URI,即只有用户申请中蕴含指定的 URI 门路,那么该申请将无法访问到指定的服务。通过该形式能够限度用户的权限。
zuul:
ignore-patterns: **/auto/**
这样对于 auto 的申请咱们就能够过滤掉了。
- 代表匹配多级任意门路
- 代表匹配一级任意门路
敏感申请头屏蔽
默认状况下,像 Cookie
、Set-Cookie
等敏感申请头信息会被 zuul
屏蔽掉,咱们能够将这些默认屏蔽去掉,当然,也能够增加要屏蔽的申请头。
Zuul 的过滤性能
如果说,路由性能是 Zuul
的基操的话,那么 过滤器 就是 Zuul
的利器了。毕竟所有申请都通过网关 (Zuul),那么咱们能够进行各种过滤,这样咱们就能实现 限流 , 灰度公布 , 权限管制 等等。
简略实现一个申请工夫日志打印
要实现本人定义的 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;
}
}
下面就简略实现了申请工夫日志打印性能,你有没有感触到 Zuul
过滤性能的弱小了呢?
没有?好的、那咱们再来。
令牌桶限流
当然不仅仅是令牌桶限流形式,Zuul
只有是限流的活它都无能,这里我只是简略举个。
我先来解释一下什么是 令牌桶限流 吧。
首先咱们会有个桶,如果外面没有满那么就会以肯定 固定的速率 会往里面放令牌,一个申请过去首先要从桶中获取令牌,如果没有获取到,那么这个申请就回绝,如果获取到那么就放行。很简略吧,啊哈哈、
上面咱们就通过 Zuul
的前置过滤器来实现一下令牌桶限流。
package com.lgq.zuul.filter;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
@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;
}
}
这样咱们就能将申请数量管制在一秒两个,有没有感觉很酷?
对于 Zuul 的其余
Zuul
的过滤器的性能必定不止下面我所实现的两种,它还能够实现 权限校验 ,包含我下面提到的 灰度公布 等等。
当然,Zuul
作为网关必定也存在 单点问题,如果咱们要保障 Zuul
的高可用,咱们就须要进行 Zuul
的集群配置,这个时候能够借助额定的一些负载均衡器比方 Nginx
。
Spring Cloud 配置管理——Config
为什么要应用进行配置管理?
当咱们的微服务零碎开始缓缓地宏大起来,那么多 Consumer
、Provider
、Eureka Server
、Zuul
零碎都会持有本人的配置,这个时候咱们在我的项目运行的时候可能须要更改某些利用的配置,如果咱们不进行配置的对立治理,咱们只能 去每个利用下一个一个寻找配置文件而后批改配置文件再重启利用。
首先对于分布式系统而言咱们就不应该去每个利用上来别离批改配置文件,再者对于重启利用来说,服务无法访问所以间接摈弃了可用性,这是咱们更不愿见到的。
那么有没有一种办法 既能对配置文件对立地进行治理,又能在我的项目运行时动静批改配置文件呢?
那就是我明天所要介绍的 Spring Cloud Config
。
能进行配置管理的框架不止
Spring Cloud Config
一种,大家能够依据需要本人抉择(disconf
,阿波罗等等)。而且对于Config
来说有些中央实现的不是那么尽人意。
Config 是什么
Spring Cloud Config
为分布式系统中的内部化配置提供服务器和客户端反对。应用Config
服务器,能够在核心地位治理所有环境中应用程序的内部属性。
简略来说,Spring Cloud Config
就是能将各个 利用 / 零碎 / 模块 的配置文件寄存到 对立的中央而后进行治理(Git 或者 SVN)。
你想一下,咱们的利用是不是只有启动的时候才会进行配置文件的加载,那么咱们的 Spring Cloud Config
就暴露出一个接口给启动利用来获取它所想要的配置文件,利用获取到配置文件而后再进行它的初始化工作。就如下图。
当然这里你必定还会有一个疑难,如果我在利用运行时去更改近程配置仓库 (Git) 中的对应配置文件,那么依赖于这个配置文件的已启动的利用会不会进行其相应配置的更改呢?
答案是不会的。
什么?那怎么进行动静批改配置文件呢?这不是呈现了 配置漂移 吗?你个渣男,你又骗我!
别急嘛,你能够应用 Webhooks
,这是 github
提供的性能,它能确保近程库的配置文件更新后客户端中的配置信息也失去更新。
噢噢,这还差不多。我去查查怎么用。
慢着,听我说完,Webhooks
尽管能解决,然而你理解一下会发现它基本不适宜用于生产环境,所以根本不会应用它的。
而个别咱们会应用 Bus
音讯总线 + Spring Cloud Config
进行配置的动静刷新。
引出 Spring Cloud Bus
用于将服务和服务实例与分布式音讯零碎链接在一起的事件总线。在集群中流传状态更改很有用(例如配置更改事件)。
你能够简略了解为 Spring Cloud Bus
的作用就是 治理和播送分布式系统中的音讯 ,也就是音讯引擎零碎中的播送模式。当然作为 音讯总线 的 Spring Cloud Bus
能够做很多事而不仅仅是客户端的配置刷新性能。
而领有了 Spring Cloud Bus
之后,咱们只须要创立一个简略的申请,并且加上 @ResfreshScope
注解就能进行配置的动静批改了,上面我画了张图供你了解。
总结
这篇文章中我带大家初步理解了 Spring Cloud
的各个组件,他们有
Eureka
服务发现框架Ribbon
过程内负载均衡器Open Feign
服务调用映射Hystrix
服务降级熔断器Zuul
微服务网关Config
微服务对立配置核心Bus
音讯总线
如果你能这个时候能看懂文首那张图,也就阐明了你曾经对 Spring Cloud
微服务有了肯定的架构意识。