2023年8月1日发(作者:)
全⽹最详细的⼀篇SpringCloud总结Java⾼级架构师 2020-03-04 13:38:25什么是Spring cloudSpring 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 的服务发现框架——EurekaEureka是基于REST(代表性状态转移)的服务,主要在AWS云中⽤于定位服务,以实现负载均衡和中间层服务器的故障转移。我们称此服务为Eureka服务器。Eureka还带有⼀个基于Java的客户端组件Eureka Client,它使与服务的交互变得更加容易。客户端还具有⼀个内置的负载平衡器,可以执⾏基本的循环负载平衡。在Netflix,更复杂的负载均衡器将Eureka包装起来,以基于流量,资源使⽤,错误条件等多种因素提供加权负载均衡,以提供出⾊的弹性。总的来说,Eureka 就是⼀个服务发现框架。何为服务,何⼜为发现呢?举⼀个⽣活中的例⼦,就⽐如我们平时租房⼦找中介的事情。在没有中介的时候我们需要⼀个⼀个去寻找是否有房屋要出租的房东,这显然会⾮常的费⼒,⼀你找凭⼀个⼈的能⼒是找不到很多房源供你选择,再者你也懒得这么找下去(找了这么久,没有合适的只能将就)。这⾥的我们就相当于微服务中的 Consumer ,⽽那些房东就相当于微服务中的 Provider 。消费者 Consumer 需要调⽤提供者 Provider 提供的⼀些服务,就像我们现在需要租他们的房⼦⼀样。但是如果只是租客和房东之间进⾏寻找的话,他们的效率是很低的,房东找不到租客赚不到钱,租客找不到房东住不了房。所以,后来房东肯定就想到了⼴播⾃⼰的房源信息(⽐如在街边贴贴⼩⼴告),这样对于房东来说已经完成他的任务(将房源公布出去),但是有两个问题就出现了。第⼀、其他不是租客的都能收到这种租房消息,这在现实世界没什么,但是在计算机的世界中就会出现资源消耗 的问题了。第⼆、租客这样还是很难找到你,试想⼀下我需要租房,我还需要东⼀个西⼀个地去找街边⼩⼴告,⿇不⿇烦? 那怎么办呢?我们当然不会那么傻乎乎的,第⼀时间就是去找 中介 呀,它为我们提供了统⼀房源的地⽅,我们消费者只需要跑到它那⾥去找就⾏了。⽽对于房东来说,他们也只需要把房源在中介那⾥发布就⾏了。
那么现在,我们的模式就是这样的了。
但是,这个时候还会出现⼀些问题。1. 房东注册之后如果不想卖房⼦了怎么办?我们是不是需要让房东定期续约 ?如果房东不进⾏续约是不是要将他们从中介那⾥的注册列表中移除 。2. 租客是不是也要进⾏注册 呢?不然合同⼄⽅怎么来呢?3. 中介可不可以做连锁店 呢?如果这⼀个店因为某些不可抗⼒因素⽽⽆法使⽤,那么我们是否可以换⼀个连锁店呢?针对上⾯的问题我们来重新构建⼀下上⾯的模式图 好了,举完这个我们就可以来看关于 Eureka 的⼀些基础概念了,你会发现这东西理解起来怎么这么简单。服务发现 :其实就是⼀个“中介”,整个过程中有三个⾓⾊:服务提供者(出租房⼦的)、服务消费者(租客)、服务中介(房屋中介) 。服务提供者 :就是提供⼀些⾃⼰能够执⾏的⼀些服务给外界。服务消费者 :就是需要使⽤⼀些服务的“⽤户”。服务中介 :其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把⾃⼰注册到服务中介那⾥,⽽服务消费者如需要消费⼀些服务(使⽤⼀些功能)就可以在服务中介中寻找注册在服务中介的服务提供者。服务注册 Register :服务续约 Renew :获取注册列表信息 Fetch Registries :官⽅解释:Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使⽤该信息查找其他服务,从⽽进⾏远程调⽤。该注册列表信息定期(每30秒钟)更新⼀次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同, Eureka 客户端⾃动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。Eureka 服务器缓存注册列表信息,整个注册表以及每个应⽤程序的信息进⾏了压缩,压缩内容和没有压缩的内容完全相同。Eureka 客户端和 Eureka 服务器可以使⽤JSON / XML格式进⾏通讯。在默认的情况下 Eureka 客户端使⽤压缩JSON 格式来获取注册列表的信息。服务下线 Cancel :官⽅解释:Eureka客户端在程序关闭时向Eureka服务器发送取消请求。发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会⾃动完成,它需要调⽤以下内容:tance().shutdownComponent();服务剔除 Eviction :官⽅解释:在默认的情况下,当Eureka客户端连续90秒(3个续约周期)没有向Eureka服务器发送服务续约,即⼼跳,Eureka服务器会将该服务实例从服务注册列表删除 ,即服务剔除。下⾯就是 Netflix 官⽅给出的 Eureka 架构图,你会发现和我们前⾯画的中介图别⽆⼆致。 当然,可以充当服务发现的组件有很多:Zookeeper ,Consul , Eureka 等。负载均衡之 Ribbon什么是 RestTemplate?不是讲 Ribbon 么?怎么扯到了 RestTemplate 了?你先别急,听我慢慢道来。我不听我不听我不听。我就说⼀句!RestTemplate是Spring提供的⼀个访问Http服务的客户端类 ,怎么说呢?就是微服务之间的调⽤是使⽤的 RestTemplate。⽐如这个时候我们 消费者B 需要调⽤ 提供者A 所提供的服务我们就需要这么写。如我下⾯的伪代码。@Autowiredprivate RestTemplate restTemplate;// 这⾥是提供者A的ip地址,但是如果使⽤了 Eureka 那么就应该是提供者A的名称private static final String SERVICE_PROVIDER_A = "localhost:8081";@PostMapping("/judge")public boolean judge(@RequestBody Request request) { String url = SERVICE_PROVIDER_A + "/service1"; return rObject(url, request, );}如果你对源码感兴趣的话,你会发现上⾯我们所讲的 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: Rule当然,在 Ribbon 中你还可以⾃定义负载均衡算法 ,你只需要实现 IRule 接⼝,然后修改配置⽂件或者⾃定义 Java Config 类。什么是 Open Feign有了 Eureka,RestTemplate,Ribbon 我们就可以愉快地进⾏服务间的调⽤了,但是使⽤ RestTemplate 还是不⽅便,我们每次都要进⾏这样的调⽤。@Autowiredprivate RestTemplate restTemplate;// 这⾥是提供者A的ip地址,但是如果使⽤了 Eureka 那么就应该是提供者A的名称private static final String SERVICE_PROVIDER_A = "localhost:8081";@PostMapping("/judge")public boolean judge(@RequestBody Request request) { String url = SERVICE_PROVIDER_A + "/service1"; // 是不是太⿇烦了每次都要 url、请求、返回类型的 return rObject(url, request, );}这样每次都调⽤ RestRemplate 的 API 是否太⿇烦,我能不能像调⽤原来代码⼀样进⾏各个服务间的调⽤呢?聪明的⼩朋友肯定想到了,那就⽤ 映射 呀,就像域名和IP地址的映射。我们可以将被调⽤的服务代码映射到消费者端,这样我们就可以 “⽆缝开发” 啦。OpenFeign 也是运⾏在消费者端的,使⽤ Ribbon 进⾏负载均衡,所以 OpenFeign 直接内置了 Ribbon。在导⼊了 Open Feign 之后我们就可以进⾏愉快编写 Consumer 端代码了。// 使⽤ @FeignClient 注解来指定提供者的名字@FeignClient(value = "eureka-client-provider")public interface TestClient { // 这⾥⼀定要注意需要使⽤的是提供者那端的请求相对路径,这⾥就相当于映射了 @RequestMapping(value = "/provider/xxx", method = ) CommonResponse> getPlans(@RequestBody planGetRequest request);}然后我们在 Controller 就可以像原来调⽤ Service 层代码⼀样调⽤它了。@RestControllerpublic class TestController { // 这⾥就相当于原来⾃动注⼊的 Service @Autowired private TestClient testClient; // controller 调⽤ service 层代码 @RequestMapping(value = "/test", method = ) public CommonResponse
> get(@RequestBody planGetRequest request) { return ns(request); }}必不可少的 Hystrix什么是 Hystrix之熔断和降级 在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是⼀个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停⽌服务之间的级联故障并提供后备选项来实现此⽬的,所有这些都可以提⾼系统的整体弹性。那么什么是 熔断和降级 呢?再举个,此时我们整个微服务系统是这样的。服务A调⽤了服务B,服务B再调⽤了服务C,但是因为某些原因,服务C顶不住了,这个时候⼤量请求会在服务C阻塞。
服务C阻塞了还好,毕竟只是⼀个系统崩溃了。但是请注意这个时候因为服务C不能返回响应,那么服务B调⽤服务C的的请求就会阻塞,同理服务B阻塞了,那么服务A也会阻塞崩溃。请注意,为什么阻塞会崩溃。因为这些请求会消耗占⽤系统的线程、IO 等资源,消耗完你这个系统服务器不就崩了么。
这就叫 服务雪崩 。妈耶,上⾯两个 熔断 和 降级 你都没给我解释清楚,你现在⼜给我扯什么 服务雪崩 ?别急,听我慢慢道来。
不听我也得讲下去!所谓 熔断 就是服务雪崩的⼀种有效解决⽅案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过 断路器 直接将此请求链路断开。当然你可以对这个注解的很多属性进⾏设置,⽐如设置超时时间,像这样。@HystrixCommand( commandProperties = {@HystrixProperty(name = "tInMilliseconds",value = "1200")})public List
ZUUL 是从设备和 web 站点到 Netflix 流应⽤后端的所有请求的前门。作为边界服务应⽤,ZUUL 是为了实现动态路由、监视、弹性和安全性⽽构建的。它还具有根据情况将请求路由到多个 Amazon Auto Scaling Groups(亚马逊⾃动缩放组,亚马逊的⼀种云计算⽅式) 的能⼒如果学过前端的肯定都知道 Router 吧,⽐如 Flutter 中的路由,Vue,React中的路由,⽤了 Zuul 你会发现在路由功能⽅⾯和前端配置路由基本是⼀个理。 我偶尔撸撸 Flutter。⼤家对⽹关应该很熟吧,简单来讲⽹关是系统唯⼀对外的⼊⼝,介于客户端与服务器端之间,⽤于对请求进⾏鉴权 、限流 、 路由 、监控 等功能。
没错,⽹关有的功能,Zuul 基本都有。⽽ Zuul 中最关键的就是 路由和过滤器 了,在官⽅⽂档中 Zuul 的标题就是Router and Filter : ZuulZuul 的路由功能简单配置本来想给你们复制⼀些代码,但是想了想,因为各个代码配置⽐较零散,看起来也⽐较零散,我决定还是给你们画个图来解释吧。请不要因为我这么好就给我点赞 。疯狂暗⽰。
emmm,信息量有点⼤,我来解释⼀下。关于前⾯的知识我就不解释了 。⾸先,Zuul 需要向 Eureka 进⾏注册,注册有啥好处呢?拿到信息有什么好处呢?我拿到信息我是不是可以获取所有的 Consumer 的元数据(名称,ip,端⼝)?拿到这些元数据有什么好处呢?拿到了我们是不是直接可以做路由映射 ?⽐如原来⽤户调⽤ Consumer1 的接⼝localhost:8001/studentInfo/update 这个请求,我们是不是可以这样进⾏调⽤了呢?localhost:9000/consumer1/studentInfo/update 呢?你这样是不是恍然⼤悟了?这⾥的url为了让更多⼈看懂所以没有使⽤ restful 风格。上⾯的你理解了,那么就能理解关于 Zuul 最基本的配置了,看下⾯。server: port: 9000eureka: client: service-url: # 这⾥只要注册 Eureka 就⾏了 defaultZone: localhost:9997/eureka然后在启动类上加⼊ @EnableZuulProxy 注解就⾏了。没错,就是那么简单。统⼀前缀这个很简单,就是我们可以在前⾯加⼀个统⼀的前缀,⽐如我们刚刚调⽤的是localhost:9000/consumer1/studentInfo/update,这个时候我们在 yml 配置⽂件中添加如下。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容器@Componentpublic class PreRequestFilter extends ZuulFilter { // 返回过滤器类型 这⾥是前置过滤器 @Override public String filterType() { return _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 = rentContext(); ("startTime", tTimeMillis()); return null; }}// lombok的⽇志@Slf4j// 加⼊ Spring 容器@Componentpublic class AccessLogFilter extends ZuulFilter { // 指定该过滤器的过滤类型 // 此时是后置过滤器 @Override public String filterType() { return _TYPE; } // SEND_RESPONSE_FILTER_ORDER 是最后⼀个过滤器 // 我们此过滤器在它之前执⾏ @Override public int filterOrder() { return _RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } // 过滤时执⾏的策略 @Override public Object run() throws ZuulException { RequestContext context = rentContext(); HttpServletRequest request = uest(); // 从RequestContext获取原先的开始时间 并通过它计算整个时间间隔 Long startTime = (Long) ("startTime"); // 这⾥我可以获取HttpServletRequest来获取URI并且打印出来 String uri = uestURI(); long duration = tTimeMillis() - startTime; ("uri: " + uri + ", duration: " + duration / 100 + "ms"); return null; }}上⾯就简单实现了请求时间⽇志打印功能,你有没有感受到 Zuul 过滤功能的强⼤了呢?没有?好的、那我们再来。令牌桶限流当然不仅仅是令牌桶限流⽅式,Zuul 只要是限流的活它都能⼲,这⾥我只是简单举个。 我先来解释⼀下什么是 令牌桶限流 吧。⾸先我们会有个桶,如果⾥⾯没有满那么就会以⼀定 固定的速率 会往⾥⾯放令牌,⼀个请求过来⾸先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放⾏。很简单吧,啊哈哈、下⾯我们就通过 Zuul 的前置过滤器来实现⼀下令牌桶限流。@Component@Slf4jpublic class RouteFilter extends ZuulFilter { // 定义⼀个令牌桶,每秒产⽣2个令牌,即每秒最多处理2个请求 private static final RateLimiter RATE_LIMITER = (2); @Override public String filterType() { return _TYPE; } @Override public int filterOrder() { return -5; } @Override public Object run() throws ZuulException { ("放⾏"); return null; } @Override public boolean shouldFilter() { RequestContext context = rentContext(); if(!RATE_uire()) { ("访问量超载"); // 指定当前请求未通过过滤 dZuulResponse(false); // 向客户端返回响应码429,请求数量过多 ponseStatusCode(429); return false; } return true; }}这样我们就能将请求数量控制在⼀秒两个,有没有觉得很酷?关于 Zuul 的其他Zuul 的过滤器的功能肯定不⽌上⾯我所实现的两种,它还可以实现 权限校验 ,包括我上⾯提到的 灰度发布 等等。当然,Zuul 作为⽹关肯定也存在 单点问题 ,如果我们要保证 Zuul 的⾼可⽤,我们就需要进⾏ Zuul 的集群配置,这个时候可以借助额外的⼀些负载均衡器⽐如 Nginx 。Spring Cloud配置管理——Config为什么要使⽤进⾏配置管理?⾸先对于分布式系统⽽⾔我们就不应该去每个应⽤下去分别修改配置⽂件,再者对于重启应⽤来说,服务⽆法访问所以直接抛弃了可⽤性,这是我们更不愿见到的。那么有没有⼀种⽅法既能对配置⽂件统⼀地进⾏管理,⼜能在项⽬运⾏时动态修改配置⽂件呢?那就是我今天所要介绍的 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 微服务有了⼀定的架构认识。 看完觉得不错可以关注⼀下⼩编,后续还会持续更新⼲货⽂章!!
2023年8月1日发(作者:)
全⽹最详细的⼀篇SpringCloud总结Java⾼级架构师 2020-03-04 13:38:25什么是Spring cloudSpring 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 的服务发现框架——EurekaEureka是基于REST(代表性状态转移)的服务,主要在AWS云中⽤于定位服务,以实现负载均衡和中间层服务器的故障转移。我们称此服务为Eureka服务器。Eureka还带有⼀个基于Java的客户端组件Eureka Client,它使与服务的交互变得更加容易。客户端还具有⼀个内置的负载平衡器,可以执⾏基本的循环负载平衡。在Netflix,更复杂的负载均衡器将Eureka包装起来,以基于流量,资源使⽤,错误条件等多种因素提供加权负载均衡,以提供出⾊的弹性。总的来说,Eureka 就是⼀个服务发现框架。何为服务,何⼜为发现呢?举⼀个⽣活中的例⼦,就⽐如我们平时租房⼦找中介的事情。在没有中介的时候我们需要⼀个⼀个去寻找是否有房屋要出租的房东,这显然会⾮常的费⼒,⼀你找凭⼀个⼈的能⼒是找不到很多房源供你选择,再者你也懒得这么找下去(找了这么久,没有合适的只能将就)。这⾥的我们就相当于微服务中的 Consumer ,⽽那些房东就相当于微服务中的 Provider 。消费者 Consumer 需要调⽤提供者 Provider 提供的⼀些服务,就像我们现在需要租他们的房⼦⼀样。但是如果只是租客和房东之间进⾏寻找的话,他们的效率是很低的,房东找不到租客赚不到钱,租客找不到房东住不了房。所以,后来房东肯定就想到了⼴播⾃⼰的房源信息(⽐如在街边贴贴⼩⼴告),这样对于房东来说已经完成他的任务(将房源公布出去),但是有两个问题就出现了。第⼀、其他不是租客的都能收到这种租房消息,这在现实世界没什么,但是在计算机的世界中就会出现资源消耗 的问题了。第⼆、租客这样还是很难找到你,试想⼀下我需要租房,我还需要东⼀个西⼀个地去找街边⼩⼴告,⿇不⿇烦? 那怎么办呢?我们当然不会那么傻乎乎的,第⼀时间就是去找 中介 呀,它为我们提供了统⼀房源的地⽅,我们消费者只需要跑到它那⾥去找就⾏了。⽽对于房东来说,他们也只需要把房源在中介那⾥发布就⾏了。
那么现在,我们的模式就是这样的了。
但是,这个时候还会出现⼀些问题。1. 房东注册之后如果不想卖房⼦了怎么办?我们是不是需要让房东定期续约 ?如果房东不进⾏续约是不是要将他们从中介那⾥的注册列表中移除 。2. 租客是不是也要进⾏注册 呢?不然合同⼄⽅怎么来呢?3. 中介可不可以做连锁店 呢?如果这⼀个店因为某些不可抗⼒因素⽽⽆法使⽤,那么我们是否可以换⼀个连锁店呢?针对上⾯的问题我们来重新构建⼀下上⾯的模式图 好了,举完这个我们就可以来看关于 Eureka 的⼀些基础概念了,你会发现这东西理解起来怎么这么简单。服务发现 :其实就是⼀个“中介”,整个过程中有三个⾓⾊:服务提供者(出租房⼦的)、服务消费者(租客)、服务中介(房屋中介) 。服务提供者 :就是提供⼀些⾃⼰能够执⾏的⼀些服务给外界。服务消费者 :就是需要使⽤⼀些服务的“⽤户”。服务中介 :其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把⾃⼰注册到服务中介那⾥,⽽服务消费者如需要消费⼀些服务(使⽤⼀些功能)就可以在服务中介中寻找注册在服务中介的服务提供者。服务注册 Register :服务续约 Renew :获取注册列表信息 Fetch Registries :官⽅解释:Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使⽤该信息查找其他服务,从⽽进⾏远程调⽤。该注册列表信息定期(每30秒钟)更新⼀次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同, Eureka 客户端⾃动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。Eureka 服务器缓存注册列表信息,整个注册表以及每个应⽤程序的信息进⾏了压缩,压缩内容和没有压缩的内容完全相同。Eureka 客户端和 Eureka 服务器可以使⽤JSON / XML格式进⾏通讯。在默认的情况下 Eureka 客户端使⽤压缩JSON 格式来获取注册列表的信息。服务下线 Cancel :官⽅解释:Eureka客户端在程序关闭时向Eureka服务器发送取消请求。发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会⾃动完成,它需要调⽤以下内容:tance().shutdownComponent();服务剔除 Eviction :官⽅解释:在默认的情况下,当Eureka客户端连续90秒(3个续约周期)没有向Eureka服务器发送服务续约,即⼼跳,Eureka服务器会将该服务实例从服务注册列表删除 ,即服务剔除。下⾯就是 Netflix 官⽅给出的 Eureka 架构图,你会发现和我们前⾯画的中介图别⽆⼆致。 当然,可以充当服务发现的组件有很多:Zookeeper ,Consul , Eureka 等。负载均衡之 Ribbon什么是 RestTemplate?不是讲 Ribbon 么?怎么扯到了 RestTemplate 了?你先别急,听我慢慢道来。我不听我不听我不听。我就说⼀句!RestTemplate是Spring提供的⼀个访问Http服务的客户端类 ,怎么说呢?就是微服务之间的调⽤是使⽤的 RestTemplate。⽐如这个时候我们 消费者B 需要调⽤ 提供者A 所提供的服务我们就需要这么写。如我下⾯的伪代码。@Autowiredprivate RestTemplate restTemplate;// 这⾥是提供者A的ip地址,但是如果使⽤了 Eureka 那么就应该是提供者A的名称private static final String SERVICE_PROVIDER_A = "localhost:8081";@PostMapping("/judge")public boolean judge(@RequestBody Request request) { String url = SERVICE_PROVIDER_A + "/service1"; return rObject(url, request, );}如果你对源码感兴趣的话,你会发现上⾯我们所讲的 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: Rule当然,在 Ribbon 中你还可以⾃定义负载均衡算法 ,你只需要实现 IRule 接⼝,然后修改配置⽂件或者⾃定义 Java Config 类。什么是 Open Feign有了 Eureka,RestTemplate,Ribbon 我们就可以愉快地进⾏服务间的调⽤了,但是使⽤ RestTemplate 还是不⽅便,我们每次都要进⾏这样的调⽤。@Autowiredprivate RestTemplate restTemplate;// 这⾥是提供者A的ip地址,但是如果使⽤了 Eureka 那么就应该是提供者A的名称private static final String SERVICE_PROVIDER_A = "localhost:8081";@PostMapping("/judge")public boolean judge(@RequestBody Request request) { String url = SERVICE_PROVIDER_A + "/service1"; // 是不是太⿇烦了每次都要 url、请求、返回类型的 return rObject(url, request, );}这样每次都调⽤ RestRemplate 的 API 是否太⿇烦,我能不能像调⽤原来代码⼀样进⾏各个服务间的调⽤呢?聪明的⼩朋友肯定想到了,那就⽤ 映射 呀,就像域名和IP地址的映射。我们可以将被调⽤的服务代码映射到消费者端,这样我们就可以 “⽆缝开发” 啦。OpenFeign 也是运⾏在消费者端的,使⽤ Ribbon 进⾏负载均衡,所以 OpenFeign 直接内置了 Ribbon。在导⼊了 Open Feign 之后我们就可以进⾏愉快编写 Consumer 端代码了。// 使⽤ @FeignClient 注解来指定提供者的名字@FeignClient(value = "eureka-client-provider")public interface TestClient { // 这⾥⼀定要注意需要使⽤的是提供者那端的请求相对路径,这⾥就相当于映射了 @RequestMapping(value = "/provider/xxx", method = ) CommonResponse> getPlans(@RequestBody planGetRequest request);}然后我们在 Controller 就可以像原来调⽤ Service 层代码⼀样调⽤它了。@RestControllerpublic class TestController { // 这⾥就相当于原来⾃动注⼊的 Service @Autowired private TestClient testClient; // controller 调⽤ service 层代码 @RequestMapping(value = "/test", method = ) public CommonResponse
> get(@RequestBody planGetRequest request) { return ns(request); }}必不可少的 Hystrix什么是 Hystrix之熔断和降级 在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是⼀个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停⽌服务之间的级联故障并提供后备选项来实现此⽬的,所有这些都可以提⾼系统的整体弹性。那么什么是 熔断和降级 呢?再举个,此时我们整个微服务系统是这样的。服务A调⽤了服务B,服务B再调⽤了服务C,但是因为某些原因,服务C顶不住了,这个时候⼤量请求会在服务C阻塞。
服务C阻塞了还好,毕竟只是⼀个系统崩溃了。但是请注意这个时候因为服务C不能返回响应,那么服务B调⽤服务C的的请求就会阻塞,同理服务B阻塞了,那么服务A也会阻塞崩溃。请注意,为什么阻塞会崩溃。因为这些请求会消耗占⽤系统的线程、IO 等资源,消耗完你这个系统服务器不就崩了么。
这就叫 服务雪崩 。妈耶,上⾯两个 熔断 和 降级 你都没给我解释清楚,你现在⼜给我扯什么 服务雪崩 ?别急,听我慢慢道来。
不听我也得讲下去!所谓 熔断 就是服务雪崩的⼀种有效解决⽅案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过 断路器 直接将此请求链路断开。当然你可以对这个注解的很多属性进⾏设置,⽐如设置超时时间,像这样。@HystrixCommand( commandProperties = {@HystrixProperty(name = "tInMilliseconds",value = "1200")})public List
ZUUL 是从设备和 web 站点到 Netflix 流应⽤后端的所有请求的前门。作为边界服务应⽤,ZUUL 是为了实现动态路由、监视、弹性和安全性⽽构建的。它还具有根据情况将请求路由到多个 Amazon Auto Scaling Groups(亚马逊⾃动缩放组,亚马逊的⼀种云计算⽅式) 的能⼒如果学过前端的肯定都知道 Router 吧,⽐如 Flutter 中的路由,Vue,React中的路由,⽤了 Zuul 你会发现在路由功能⽅⾯和前端配置路由基本是⼀个理。 我偶尔撸撸 Flutter。⼤家对⽹关应该很熟吧,简单来讲⽹关是系统唯⼀对外的⼊⼝,介于客户端与服务器端之间,⽤于对请求进⾏鉴权 、限流 、 路由 、监控 等功能。
没错,⽹关有的功能,Zuul 基本都有。⽽ Zuul 中最关键的就是 路由和过滤器 了,在官⽅⽂档中 Zuul 的标题就是Router and Filter : ZuulZuul 的路由功能简单配置本来想给你们复制⼀些代码,但是想了想,因为各个代码配置⽐较零散,看起来也⽐较零散,我决定还是给你们画个图来解释吧。请不要因为我这么好就给我点赞 。疯狂暗⽰。
emmm,信息量有点⼤,我来解释⼀下。关于前⾯的知识我就不解释了 。⾸先,Zuul 需要向 Eureka 进⾏注册,注册有啥好处呢?拿到信息有什么好处呢?我拿到信息我是不是可以获取所有的 Consumer 的元数据(名称,ip,端⼝)?拿到这些元数据有什么好处呢?拿到了我们是不是直接可以做路由映射 ?⽐如原来⽤户调⽤ Consumer1 的接⼝localhost:8001/studentInfo/update 这个请求,我们是不是可以这样进⾏调⽤了呢?localhost:9000/consumer1/studentInfo/update 呢?你这样是不是恍然⼤悟了?这⾥的url为了让更多⼈看懂所以没有使⽤ restful 风格。上⾯的你理解了,那么就能理解关于 Zuul 最基本的配置了,看下⾯。server: port: 9000eureka: client: service-url: # 这⾥只要注册 Eureka 就⾏了 defaultZone: localhost:9997/eureka然后在启动类上加⼊ @EnableZuulProxy 注解就⾏了。没错,就是那么简单。统⼀前缀这个很简单,就是我们可以在前⾯加⼀个统⼀的前缀,⽐如我们刚刚调⽤的是localhost:9000/consumer1/studentInfo/update,这个时候我们在 yml 配置⽂件中添加如下。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容器@Componentpublic class PreRequestFilter extends ZuulFilter { // 返回过滤器类型 这⾥是前置过滤器 @Override public String filterType() { return _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 = rentContext(); ("startTime", tTimeMillis()); return null; }}// lombok的⽇志@Slf4j// 加⼊ Spring 容器@Componentpublic class AccessLogFilter extends ZuulFilter { // 指定该过滤器的过滤类型 // 此时是后置过滤器 @Override public String filterType() { return _TYPE; } // SEND_RESPONSE_FILTER_ORDER 是最后⼀个过滤器 // 我们此过滤器在它之前执⾏ @Override public int filterOrder() { return _RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } // 过滤时执⾏的策略 @Override public Object run() throws ZuulException { RequestContext context = rentContext(); HttpServletRequest request = uest(); // 从RequestContext获取原先的开始时间 并通过它计算整个时间间隔 Long startTime = (Long) ("startTime"); // 这⾥我可以获取HttpServletRequest来获取URI并且打印出来 String uri = uestURI(); long duration = tTimeMillis() - startTime; ("uri: " + uri + ", duration: " + duration / 100 + "ms"); return null; }}上⾯就简单实现了请求时间⽇志打印功能,你有没有感受到 Zuul 过滤功能的强⼤了呢?没有?好的、那我们再来。令牌桶限流当然不仅仅是令牌桶限流⽅式,Zuul 只要是限流的活它都能⼲,这⾥我只是简单举个。 我先来解释⼀下什么是 令牌桶限流 吧。⾸先我们会有个桶,如果⾥⾯没有满那么就会以⼀定 固定的速率 会往⾥⾯放令牌,⼀个请求过来⾸先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放⾏。很简单吧,啊哈哈、下⾯我们就通过 Zuul 的前置过滤器来实现⼀下令牌桶限流。@Component@Slf4jpublic class RouteFilter extends ZuulFilter { // 定义⼀个令牌桶,每秒产⽣2个令牌,即每秒最多处理2个请求 private static final RateLimiter RATE_LIMITER = (2); @Override public String filterType() { return _TYPE; } @Override public int filterOrder() { return -5; } @Override public Object run() throws ZuulException { ("放⾏"); return null; } @Override public boolean shouldFilter() { RequestContext context = rentContext(); if(!RATE_uire()) { ("访问量超载"); // 指定当前请求未通过过滤 dZuulResponse(false); // 向客户端返回响应码429,请求数量过多 ponseStatusCode(429); return false; } return true; }}这样我们就能将请求数量控制在⼀秒两个,有没有觉得很酷?关于 Zuul 的其他Zuul 的过滤器的功能肯定不⽌上⾯我所实现的两种,它还可以实现 权限校验 ,包括我上⾯提到的 灰度发布 等等。当然,Zuul 作为⽹关肯定也存在 单点问题 ,如果我们要保证 Zuul 的⾼可⽤,我们就需要进⾏ Zuul 的集群配置,这个时候可以借助额外的⼀些负载均衡器⽐如 Nginx 。Spring Cloud配置管理——Config为什么要使⽤进⾏配置管理?⾸先对于分布式系统⽽⾔我们就不应该去每个应⽤下去分别修改配置⽂件,再者对于重启应⽤来说,服务⽆法访问所以直接抛弃了可⽤性,这是我们更不愿见到的。那么有没有⼀种⽅法既能对配置⽂件统⼀地进⾏管理,⼜能在项⽬运⾏时动态修改配置⽂件呢?那就是我今天所要介绍的 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 微服务有了⼀定的架构认识。 看完觉得不错可以关注⼀下⼩编,后续还会持续更新⼲货⽂章!!
发布评论