分布式系统面试系列02-Spring Cloud 的底层架构原理

分布式系统面试系列02-Spring Cloud 的底层架构原理,前面我们讲了 SpringCloud 的核心架构,了解了有要构建一套分布式系统我们需要哪些组件。今天以SpringCloud 为例,讲解一下它的核心组件的原理。

前面我们讲了一个以Spring Cloud 技术栈实现的分布式系统,至少得包含 Eureka、Ribbon、Feign、Zuul 这么几个组件,你还能记得他们各自是干嘛的么。记不清了没关系,回去看一下这篇文章就好。

Eureka

首先,我们得说说服务注册中心 Eureka 了,它应该是SpringCloud 技术栈中最核心的东西。

服务注册与发现怎么实现的

服务注册与发现是 Eureka 中最核心的东西。

比如现在我们有一个服务消费者 服务A,和两个节点的服务提供者,服务B。服务A 和服务B 在启动的时候都会向注册中心进行服务注册。

服务A 也会定时从服务注册中心定时去拉取服务注册表信息到本地来,这个过程叫服务发现,默认是30S 一次,当然了可以自己去配置。

如下图:

实际上当服务在拉取服务注册表的时候,其实客户端不是直接从 Eureka 中的 服务注册表中获取数据的。

Eureka 做了二级缓存,第一级叫做 ReadOnly 缓存,二级叫做 ReadWrite 缓存。

客户端会直接从ReadOnly 缓存中读取注册表信息。

当服务在进行注册的时候,先往服务注册表中写入注册信息,服务注册表更新了,立马会同步一份数据到 ReadWrite 缓存中去。

那什么时候 ReadWrite 缓存中的数据会到 ReadOnly 缓存中去?

此时有一个定时任务会定时去检查 ReadWrite 是否跟  ReadOnly 不一致,不一致就把数据同步到 ReadOnly 中去。

这个定时任务也默认是 30S。也可以自己配置。

大家可以考虑一下,这么做的好处是什么,为什么要这么去做二级缓存?

这么做的好处在于,优化并发读写的冲突。

如果服务进行注册的时候,同时有服务来读去注册表信息,就会存在频繁的读写加锁的操作,写的时候就不能读,导致性能下降,所以我们需要避免大量的读写都去操作一个表。

那么有了这两层,其实大部分的读操作都会走 ReadOnly 缓存。只需要定时把 ReadWrite 缓存中的数据写入到 ReadOnly 就好了。

心跳与故障检测

服务注册中心还有一个很重要的功能就是 心跳与故障检查。心跳跟故障检测其实就是为了知道注册上来的这些服务是不是还活着的。

Eureka 还会开启一个定时任务定时去检查心跳,默认也是30秒,也可以自己设置。

当出现机器故障没有在约定的时间间隔内上报自己的状态,那么Eureka 就会把这台机器剔除注册表,同时更新到 ReadWrite 缓存中去。 如图:

但是把数据从ReadWrite 缓存同步到 ReadOnly 缓存是有时间间隔的。当服务消费者A 也只有等待下一次请求更新的时候才会把自己列表里面的服务给更新掉。

所以有时候会出现你注册上去的服务经过及时秒才被服务消费者发现,或者服务的某个节点出现故障,没有及时剔除掉。这里就是同步机制的时间差问题。

以上就是 Eureka 的核心运行原理了。

Feign & Ribbon

Feign,它其实就是对一个接口打了一个注解,它会针对这个注解标注的接口生成动态代理对象,然后针对你的 feign 的动态代理代理对象去调用他方法的时候,此时会在底层生成,http 协议格式的请求如:/order/create?productId=1

Feign底层的使用的HTTP 通信框架 HttpClient ,先会使用 Ribbon 从本地的 Eureka 注册表的缓存里面取出要调用服务的机器列表出来,然后根据负载均衡算法,选择一台机器出来,然后针对选择出来的机器发送 Http 请求过去。

Zuul

Zuul 配置请求路径与服务的对应关系,你的请求到网关,他就直接查找到匹配的服务,然后就直接把请求转发给那个服务的某台机器, Ribbon 从 Eureka 本地缓存列表里面获取一台机器,然后通过负载均衡算法选择一台,把请求直接用 http 通信框架发送到指定的机器上面去。

Hystrix

在微服务的架构中,会存在很多的服务调用,如果一个服务出现故障,就很容易导致整个调用链发生故障,发生服务雪崩的情况。

例如,当一个服务出现故障,或者超时的问题,但是服务调用方不知道,一直在发送请求过去,那么等待的请求越来越多,形成任务积压,最终导致服务崩溃,瘫痪。

Hystrix 的出现就是为了解决这种问题。它提供了服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。

Hystrix使用舱壁模式实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的依赖服务。

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。