服务剔除 Eviction:
官方解释:在默认的情况下,当 Eureka 客户端连续 90 秒(3 个续约周期)没有向 Eureka 服务器发送服务续约,即心跳,Eureka 服务器会将该服务实例从服务注册列表删除,即服务剔除。
结合中介理解:房东(提供者 Eureka Client Provider) 会定期联系 中介 (服务器 Eureka Server) 告诉他我的房子还租(续约),如果中介 (服务器 Eureka Server) 长时间没收到提供者的信息,那么中介会将他的房屋信息给下架(服务剔除)。
下面就是 Netflix 官方给出的 Eureka 架构图,你会发现和我们前面画的中介图别无二致。
Eureka 架构图
当然,可以充当服务发现的组件有很多:Zookeeper ,Consul , 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> 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> 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 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() {
最后
作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料
加入社区:https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0
许通过则怎么进行处理
@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() {
最后
作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料
[外链图片转存中…(img-9CqJ3EeX-1725750122688)]
[外链图片转存中…(img-aWxt1umZ-1725750122689)]
加入社区:https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0