📖 earlier posts 📖
Hystrix 是由 Netflix 开源的一个延迟和容错库,旨在隔离对远程系统、服务和第三方库的访问点,他主要通过:“隔离、降级、融断、缓存”,等方式来避免或解决雪崩效应。处理和预防这类问题的项目也很多,通常主流的是 Sentinel、Hystrix、Resillience4j 三类解决方案。
Sentinel
阿里巴巴于 2012年 发布的面向分布式服务架构的轻量级流量控制组件。
Resilience4j
他是一个受 Netfilx Hystrix 启发的轻量级容错库,专门为函数式编程而设计
雪崩效应 雪崩效应也被称之为 “服务雪崩”,而雪崩效应的造成就是 级联 的崩溃,假设服务A调用服务B,服务B调用服务C,服务C调用服务D,这其中任何一个节点无法使用,就会造成下面的服务无法使用,这就会产生级联故障。
如果这种请求很多,那么将会导致不可用的服务越来越多,之后占用的计算机资源也越多,而这些请求则会消耗系统资源,导致其他请求也不可用,这种现象也被称之为:“服务雪崩”。
也就是说当服务提供者导致了服务消费者不可使用,并将问题逐渐放大至微服务系统,进而造成系统崩溃。
造成雪崩原因 雪崩效应的造成原因可以是流量激增、硬件故障、bug、缓存、资源耗尽、线程同步等待、配套资源不可用等:
流量激增 在目前,造成流量激增的方式可以恶意攻击,或者说是爬虫造成的都有可能,这种主要体现在了京东 6·18 活动中。
硬件故障 假设一个集群中,有两个服务提供者,一个服务提供者的硬件损害。那么整个集群就成为了单点应用,导致服务的压力加大,从而出现了服务延迟,由于服务延迟的不断增加,最终导致了雪崩。
bug 通常程序中出现 bug 都是正常的事情,在 spring cloud 中,假设有一个不应该被类使用的接口被调用,则会造成服务之间的互相调用导致的循环逻辑问题,则会可能导致服务雪崩。
缓存问题 缓存问题主要可以分为缓存穿透、缓存击穿、缓存雪崩等三个问题:
缓存穿透 缓存穿透指的是用户不断请求数据库中没有的数据,这将会导致数据库的压力过大。至于这种问题的防范问题,可以通过下述方法解决:
在路由中增加权限校验
设置最大(max)与最小(min)的拦截请求(大于等于或小于等于)
缓存击穿 缓存击穿是指 缓存没有数据,但数据库中有数据(通常是缓存时间到期了) ,当用户在缓存中没有得到数据,就从数据库中获得数据,从而让数据库压力增大,就会造成缓存击穿。
至于预防缓存击穿,那么就就是将 热点数据 设置为永不过期,来解决问题。
缓存雪崩 缓存雪崩说直白一点,就是缓存大量过期,而此时很多用户都是查询缓存中的数据,从而将流量转向数据库,进一步导致数据库宕机。预防这种问题需要通过将缓存数据的过期时间为 随机,以此来防止数据缓存同一时间大量过期。 如果将缓存击穿的问题解决了,那么缓存雪崩的问题也可以适当的预防。
资源耗尽 资源耗尽主要由两种原因造成的原因可以是:“服务提供者不可用,导致服务消费者等待,进而造成资源消耗”,也可是用户的大量流量请求,以及重试流量加大导致的。
线程同步等待 假设系统采用的是服务调用模式,核心服务和非核心服务共用一个线程池以及消息队列,假设一个核心的业务线程调用了一个非核心进程 ,那么这个非核心进程一旦出现问题,则导致核心线程出现问题。如果核心线程断了,那么将会导致后续的线程也一一崩溃,最终导致雪崩。
配套资源不可用 配套资源不可用可能是数据中心不可用或者电信基础网络服务出现问题,欣慰的是这类事故出现的概率极低。
Hystrix 的解决方式
熔断机制 至于 Hystrix 熔断机制,他主要通过融断器来执行融断的工作,熔断器在现实生活中的运用主要是 “保险丝”。
保险丝(fuse)的在电器的主要作用就是 当电器的电流异常升高到一定热度的时候,那么保险丝自身熔断来切断电流,以此来保护电路的安全运行
快速失败及熔断机制 Hystrix 的熔断器可以实现快速失败,所谓快速失败就是当一个问题达到了某个规则的阀值,则该请求以后调用任何接口都会失败,最后导致不会访问远程服务器,以此来防止应用程序和资源的消耗。
熔断器能够诊断错误是否经过修正,如果已经修正那么熔断器则会关闭,让应用程序在此调用操作。熔断器能够记录最近调用所发生的错误次数,假设熔断器小于阀值,则可以关闭,否则继续开启。
熔断在正常状态下,一般处于关闭状态,如果调用的服务出错值达到莫个阀值,则会让其快速失效,以此来避免大量的无效请求影响系统。
一段时间过后熔断机制将会进入半尝试状态,允许少量请求进行尝试,如果成功则关闭熔断状态,否则将继续持续。
隔离机制 隔离机制主要分为线程池隔离以及信号量隔离模式,在病毒传播中为了防止病毒扩散,最好的方式就是隔离病毒携带者,从而来保护正常的个体。这种方式在分布式系统中也采用隔离的方式进行容错处理。
线程池隔离模式 在 Hystrix 的线程池隔离模式下,会为每一个 依赖建立一个线程池,以此来存储当前的依赖请求,线程池对请求进行处理。 线程池的主要作用就是隔离线程的依赖,以此来限制线程的并发访问和阻塞扩张。
当流量达到峰值的时候 ,如果不能及时处理的请求将会被存储到线程池后进行处理。运营环境被隔离时 ,会根据依赖划分多个线程池,以此来进行资源隔离,就算调用的服务代码存在 bug,也不会对系统的其他服务造成影响。
信号量隔离模式 信号量隔离模式主要用于记录当前请求的数量,他内在存在了一个类似 “规则” 的,当信号量小于等于规则内最大的数量时,将会丢弃请求,否则将会执行请求,且对当前的信号量加一。
服务降级 服务降级的主要机制就是 如果一个资源快不够了,则需要将某些服务先关掉,之后等到资源足够时在进行开启 ,和熔断的目的相差无几,以此来保证上游服务的稳定性。根据业务的不同,降级也分为两种模式,分别为:
Fallback Fallback 一词也被称之为 “倒退” 的意思,及如果服务失败,那么则会通过 fallback 的方式进行降级。
服务级联模式 服务级联模式则是如果服务失败,则需要调用备用服务,服务级联模式会尽可能的返回数据。
这样的做法如果考虑不充分会造成级联的崩溃,假设在缓存失败后将全部流量导到数据库请求中,那么数据库可能会直接崩溃,因此级联模式也考验开发者的能力
简单来讲服务降级的两种方式分别是分级,将故障的服务丢弃,而服务级联模式则是将请求转移在另一个集群上。
缓存机制 缓存机制就是将请求的所有结果进行缓存,如果有相同的请求发送过来,则直接从缓存中取出结果,以此来减少请求的开销。
Hystrix Fallback 实现 通常回退方法的实现有两种,一种是通过 feign 来进行实现,另一种解决方案则是根据 @HystrixCommand 注解进行实现,在此之前我们需要添加依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
之后在启动类中添加注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.example.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @EnableCircuitBreaker public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
尽管 @EnableCircuitBreaker 在 3.0.1 开始 Hystrix 就不在 Spring cloud Netfilx 中了,但是他是唯一一个实现断路器的注解
https://martinfowler.com/bliki/CircuitBreaker.html
application.properties 1 2 3 4 5 6 7 spring.application.name =Hystrix server.port =9900 spring.cloud.consul.host =localhost spring.cloud.consul.port =8500 spring.cloud.consul.discovery.service-name =service-provider spring.cloud.consul.discovery.register =false feign.hystrix.enabled =true # 开启 Hystrix
service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.example.demo.service;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;@FeignClient("service-provider") public interface MyFeignFallbackClient { @GetMapping("/hey") String hey () ; }
controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.example.demo.controller;import com.example.demo.service.MyFeignFallbackClient;import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class HeyController { @Autowired private MyFeignFallbackClient myFeignFallbackClient; @RequestMapping("/hey") @HystrixCommand(fallbackMethod = "defaultCities") public String index () { String fallback = myFeignFallbackClient.hey(); return fallback; } public String defaultCities () { return "No server!!!" ; } }
Hystrix Dashboard (consul) Hystrix Dashboard 即 “Hystrix 仪表盘”,也是除了容错之外还提供的实时监控功能,他会实时的累加记录所有关于 HystrixCommand 执行的信息,其中包含了每秒执行了多少请求,以及成功或失败等信息。
Hystrix Dashboard 是一款针对 Hystrix 进行实时监控的工具,可以实现出实时监控数据,并直观的显示 Hystrix Command 的请求响应时间、请求成功率等
因为 Hystrix Dashboard 仅仅实现了数据的监控,因此 我们需要在 Hystrix Fallback 的基础上进行扩展 ,其中启动类需要修改并添加依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.2.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix-dashboard --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> <version>2.2.7.RELEASE</version> </dependency>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.example.demo;import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.context.annotation.Bean;@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @EnableHystrixDashboard @EnableCircuitBreaker public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } @Bean public ServletRegistrationBean servletRegistrantionBean () { HystrixMetricsStreamServlet hystrixMetricsStreamServlet = new HystrixMetricsStreamServlet (); ServletRegistrationBean registrationBean = new ServletRegistrationBean (hystrixMetricsStreamServlet); registrationBean.setLoadOnStartup(1 ); registrationBean.addUrlMappings("/actuator/hystrix.stream" ); registrationBean.setName("HystrixMetricsStreamServlet" ); return registrationBean; } }
application.properties 1 2 3 4 5 6 7 spring.application.name =HystrixDashboard server.port =9502 spring.cloud.consul.host =localhost spring.cloud.consul.port =8500 spring.cloud.consul.discovery.service-name =service-provider feign.hystrix.enabled =true hystrix.dashboard.proxy-stream-allow-list =* # 设置仪表板代理流允许列表为所有
当一切完成之后访问 http://localhost:9502/hystrix 并在输入框输入 http://localhost:9502/actuator/hystrix.stream 即可。
Hystrix Turbine 与 Hystrix Dashboard 的区别就是一个只能看到单个应用程序内的服务信息,而另一个则是多个实例集群的状态形式展现,因此 Hystrix Turbine 由此而生。
强烈建议使用 Eureka
首先我们只需要知道在 Ribbon 和 Feign 或 RestTemplate 中,Ribbon 主要基于 HTTP和TCP出来进行客户端的负载均衡,他是由 Netfix 公司所开源项目 Netfix OSS 的一部分,可自动与 Netfix Server Discover 也就是 Eureka 进行交互。
Ribbon 是不可以独立部署的,Spring cloud Ribbon 是基于 Ribbon 的实现。基于了轮廓、随机等规则自动调用服务,当然除此之外还可以根据自身需要来定义均衡算法。
而 Feign 和 RestTemplate 都是用于实现服务发现, Feign 主要是声明试的 WebService 客户端,为我们提供了一个快捷、优雅的调用 HTTP API。后者则是需要服务的 IP地址等这些信息来实现各个服务之间的通信调用,区别就是 Feign 会 比 RestTemplate 简洁和优雅许多 。
负载均衡 在仔细讲解 Ribbon 与 Feign 或 RestTemplate 之前,他们都是涉及或实现出负载均衡这个功能,因此负载均衡在这里尤为重要。
负载均额(Load Balance)从字面意思上我们可以理解为当一个数据请求时可以分摊多个单元进行分流,通常负载均衡会分为服务器端负载均衡以及客户端负载均衡两种。
服务器端负载均衡 服务器端负载均衡主要是应对高并发和服务器端扩容的重要方法之一,负载均衡也通常讲的是服务器端负载均衡。服务器端的负载均衡主要通过在服务器与客户端之间添加负载均衡器进行实现,主要分为硬件和软件负载均衡,这里我们主要介绍软件的负载均衡。
无论是软件的负载均衡还是硬件的负载均衡均维护着一个正常服务清单,通过心跳机制来删除出现故障的服务节点,也可以通过他来恢复服务节点。
软件负载均衡主要是在普通的服务器上安装具有负载均衡的软件来实现请求的分发,进而实现负载均衡,需要注意的是服务器端的 “正常服务提供者清单” 是存储在负载均衡器中的。
客户端负载均衡 客户端负载均衡与服务器端负载均衡相差无几,他们的区别主要是客户端他本身拥有 “正常服务提供者清单”,在客户端负载均衡中。所有客户端管理都管理着一份自己需要访问的服务提供者清单,而这些清单大多数从服务中心进行获取。
Ribbon Ribbon 是 Netfix 公司所开源项目 Netfix OSS 的一部分,主要提供一个客户端负载均衡的云库。他主要基于 HTTP 和 TCP 的客户端负载均衡组件,之后的 Spring cloud Ribbon 是一个基于 Ribbon 实现,主要特点是可以根据需要自定义负载均衡算法。
Choose Server 是负载均衡策略中 “线性轮询策略(Round Robin Ruie)” 工作流程内的第三步骤,其作用是获取服务列表中取服务进行请求,如果连续十次都没有获取到服务则报错。
从上图中我们可以看到,Ribbon 本身所维护着 正常服务提供者清单的有效性,如果通过 ChooseServer 不可用,则会重新从服务中心获取有效的服务提供者清单来进行更新。
Ribbon 接口类型
Type
Type Info
Bean Name
Class Name
IClientConfig
用于读取配置 ,实现类是 DefaultClientConfigIcmpl,而默认值也是该类
ribbonClientConfig
DefaultClientConfigIcmpl
IRule
负责处理负载均衡规则 ,实现类是 ZoneAvoidanceRule 默认通过他来选择实例,步骤是:
ribbonRule
ZoneAvoidanceRule
1.ServerList 获取所有可用的服务提供者列表
2.ServerListFileter 过滤一部分服务提供者地址
3.最后在剩下的地址中通过 IRule 选择一台服务器
IPing
用来筛选掉无法访问的实例
ribbonPing
DummyPing
ServerList <server>
用于获取服务提供者地址列表 ,可以是一组固定地址也可是服务中心中定期查询服务提供者的地址列表
ribbonServerList
ConfigurationBasedServerList
ServerListFileter <server>
在原始服务提供者地址列表中,通过使用一定的策略过滤一部分不符合条件的地址 (当动态使用 ServerList时使用)
ribbonServerListFilter
ZonePrefernenceServerListFilter
ILoadBalancer
可通过负载均衡中选择一个服务器,并通过标记暂停服务的服务器,一可以获取所有已知的服务器提供者列表
ribbonLoadBalancer
ZoneAwareLoadBalancer or BaseLoadBalancer
ServerListUpdater
用于ServerList的更新 ,当服务中心的服务提供者发生变化的时候,ServerList 会根据 PollingServerListUpdater 来实现定时更新服务提供者列表
ribbonServerListUpdate
PollingServerListUpdater
负载均衡器 负载均衡器很多都是通过 Ribbon 接口类型来进行实现具体的负载均衡 Bean 如:
| Bean | Type | Class Name | | — | — | — | — | | LoadBalancerClient | ILoad Balancer | RibbonLoadBalancerClient | | AbstractLoadBalancer | ILoad Balancer | AbstractLoadBalancer |
LoadBalancer 在初始化时 execute() 方法会通过ILoadBalancer 来从服务中心获取服务提供者地址列表,并每 10s 来检测一下服务的可用性。如果服务端可用性发生改变,或者数量不一致,那么 RibbonLoadBanlancer 会从注册中心更新服务提供者地址列表,之后可以根据 IRule 来进行负载均衡。
LoadBanlancer主要的职责是添加服务器、选择服务器、获取所有的服务器列表、获取可用的服务提供者列表等。
Method Name
Method Info
addServers(List<Server> newServers)
向服务器初始列表中增加新的服务提供者地址列表
chooseServer(Object key)
从负载均衡器中选择一个服务器
markServerDown(Server server)
通知或标记已经暂停服务的服务器
getReachableServers()
返回获取到可用的服务提供者列表
getAllServers
获取所有看到服务提供者列表
Load Balancer 主要用于定义软件负载均衡的操作接口,一个典型的负载负载均衡实现需要一组服务器进行。通常一个方法来标记特定的服务器不循环,一个调用将现有的服务器列表中选择一个服务器来进行提供服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package com.netflix.loadbalancer;import java.util.List;public interface ILoadBalancer { public void addServers (List<Server> newServers) ; public Server chooseServer (Object key) ; public void markServerDown (Server server) ; @Deprecated public List<Server> getServerList (boolean availableOnly) ; public List<Server> getReachableServers () ; public List<Server> getAllServers () ; }
AbstractLoadBalancer Abstract Load Balancer 是 ILoad Balancer 的实现类,他包含了大多数负载均衡实现所需要的功能,典型的是 Load Balancer 的结构:
基于特定的标准可以分时段的服务提供者信息列表
通过规则的定义和实现负载均衡的策略类
定义并实现一种机制来确定服务提供者表单中的节点、可用性的类。
在这三类中,Abstract Load Balancer 类实现了服务提供者地址列表分组的作用,被 服务器组(ServerGroup) 来定义:
Method name
Method info
ServerGroup
ALL:所有服务
STATUS_UP:正常运行的服务
STATUS_NOT_UP:下线或崩溃的服务
chooseServer
从负载均衡中选择一个服务器
getServerList(ServerGroup serverGroup)
获取负载均衡中所有服务的实例列表
getLoadBalancerStats()
从 Load Balancer 来获取每个服务的所有细节统计信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.netflix.loadbalancer;import java.util.List;public abstract class AbstractLoadBalancer implements ILoadBalancer { public enum ServerGroup { ALL, STATUS_UP, STATUS_NOT_UP } public Server chooseServer () { return chooseServer(null ); } public abstract List<Server> getServerList (ServerGroup serverGroup) ; public abstract LoadBalancerStats getLoadBalancerStats () ; }
BaseLoadBalancer Base Load Balancer 类是 Abstract Load Balancer 的实现类或工具类,他可以用一个 List 集合(AllServerList)来保存所有服务实例,之后用另一个 List 保存(UpServerList)当前有效的服务实例:
1 2 3 4 5 6 @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> allServerList = Collections .synchronizedList(new ArrayList <Server>()); @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> upServerList = Collections .synchronizedList(new ArrayList <Server>());
Method name
Method info
IpingStrategy
用于定义检查服务的策略,将 ping 所有的服务器(如果执行的很慢可能是你有大量的服务器在此集群中)
chooseServer
调用 rule 类中的 choose 方法来选择服务器对象(如果没有则返回 null)
PingTask()
每 x 秒运行一次定时器任务,检查服务器列表中每个服务器/节点的状态(默认为 1000s)
markServerDown()
用于标注服务是否有效(mark Server Down called for server)
getReachableServer()
获取所有有效的服务实例列表
getAllServer()
拥有获取所有服务器实例列表
addServer()
向负载均衡器中添加一个新的服务实例列表
除此之外,Base Load Balancer 类中的子类 DynamicServerListLoadBalancer 以及 ZoneAwareLoadBalancer 作为 DynamicServerListLoadBalancer 的子类,都实现了了一些能力,分别为:
DynamicServerListLoadBalancer 在负载均衡器的基础上做了进一步的扩展,可以在服务实例清单在运行时的动态更新的实现,以及还提供了一个过滤器标准来过滤掉不符合所需标准的服务器。
Ribbon Ping 的实现 在上述介绍中,我们都设计到了 Ping 或 “心跳” 这个概念,负载均衡中 Ping 机制主要用于检测服务提供者的有效性。他会每隔一段时间执行 Ping 来判断服务器是否存活 ,而这些工作都将由 IPing 和他的实现类来负责(Ribbon 默认实现类是 DummyPing,但需要注意的是默认情况下不会激活 Ping 机制)。
Class Name
Class Info
DummyPing
虚拟的 Ping 实现,当确定服务器活着的时候会返回 true
NoOpPing
设么都不做直接返回 true
PingConstant
IPing 的实现类,他用于返回任何设置的内容,true 或 false(通常只要常量参数为 true 为服务实例存活,否则为失效)
DummyPing 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.netflix.loadbalancer;import com.netflix.client.config.IClientConfig;public class DummyPing extends AbstractLoadBalancerPing { public DummyPing () { } public boolean isAlive (Server server) { return true ; } @Override public void initWithNiwsConfig (IClientConfig clientConfig) { } }
NoOpPing 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.netflix.loadbalancer;public class NoOpPing implements IPing { @Override public boolean isAlive (Server server) { return true ; } }
PingConstant 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.netflix.loadbalancer;public class PingConstant implements IPing { boolean constant = true ; public void setConstant (String constantStr) { constant = (constantStr != null ) && (constantStr.toLowerCase().equals("true" )); } public void setConstant (boolean constant) { this .constant = constant; } public boolean getConstant () { return constant; } public boolean isAlive (Server server) { return constant; } }
负载均衡策略 AbstractLoadBalancerRule 是一个 IRule 的继承,他本身则是一个负载均衡策略的抽象类,而 IRule 主要定义了 ILoadBalancer ,其最为核心的方法为 choose(),这是用于选择服务器对象(服务实例),如过没有将会返回 null。
而定义 IRule 主要的目的第一就是为了辅助负载均衡器(ILoadBalancer)来通过负载均衡策略选择合适的服务实例 ,其默认使用的是 九大负载均衡 策略中的 线性轮询策略(RoundRobinRule)。
线性轮询策略(Round Robin Rule) 由于 Round Robin Rule 是 ILoadBalancer 默认采用的负载均衡策略,因此也和他的流程非常符合,与此同时他还是作为著名的负载均衡策略,他主要定义了 AVAILABLE_ONLY_SERVERS(仅可用服务器)、ALL_SERVERS(所有服务器) 两个状态。
choose(ILoadBalancer lb, Object key) 方法从负载均衡中选择一个服务器并计数,如果没有则会返回 :“no load balancer”
1 2 3 4 5 public Server choose (ILoadBalancer lb, Object key) { if (lb == null ) { log.warn("no load balancer" ); return null ; }
incrementAndGetModulo 方法会通过serverCount 函数来请求 allServers(所有服务器),而 incrementAndGetModulo 方法则会获取下一个索引,也就是说 获取所有服务器,并从 0 的基础上 +1
1 2 3 4 5 6 7 8 private int incrementAndGetModulo (int modulo) { for (;;) { int current = nextServerCyclicCounter.get(); int next = (current + 1 ) % modulo; if (nextServerCyclicCounter.compareAndSet(current, next)) return next; } }
最后通过 choose(Object key) 通过索引去服务列表获取服务,如果连续 10次没有获取到服务 ,则会返回: “No available alive servers after 10 tries from load balancer”。假设 可达服务器(reachableServers) 或 服务器总数(allServers) 为 0,则会输出: “No up servers available from load balancer” 错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Server server = null ; int count = 0 ; while (server == null && count++ < 10 ) { List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if ((upCount == 0 ) || (serverCount == 0 )) { log.warn("No up servers available from load balancer: " + lb); return null ; } int nextServerIndex = incrementAndGetModulo(serverCount); server = allServers.get(nextServerIndex); if (server == null ) { Thread.yield(); continue ; } if (server.isAlive() && (server.isReadyToServe())) { return (server); } server = null ; } if (count >= 10 ) { log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; }
重试策略(Retry Rule) 重试策略(Retry Rule)是 Rule 的级联,他采用 Round Robin Rule的 choose()方法来获取服务器实例,最大重试次数(maxRetryMillis) 是 500 ,因此他在选择实例和重试方法是:
级联(cascade)在计算机科学中指多个对象之间一对多的映射关系,可以理解为一张表 A 用于存放学生所在班级(姓名、性别、年龄)而姓名作为主键,而另一张表 B 存放着楼层住户信息(姓名、性别)他们之间通过 姓名、年龄 来作为级联
如果通过 choose() 方法获取服务器实例正常,则回答并返回数据
假设超过了 最大的重试次数(maxRetryMillis) 没有获取到 “活着的服务器”,则返回 null
1 2 3 4 5 if ((answer == null ) || (!answer.isAlive())) { return null ; } else { return answer; }
最后如果没有活着的服务回答在当前时间小于 500 ms 的情况下,则会不断的在这时间段重试。
1 2 3 4 5 6 7 if (((answer == null ) || (!answer.isAlive())) && (System.currentTimeMillis() < deadline)) { Thread.yield(); } else { break ; }
加权响应时间策略(WeightedResponseTimeRule) WeightedResponseTimeRule 是 RoundRobinRule 的延伸,对一些功能进行了扩展、可以根据服务实例的运行情况计算出服务实例的权重,之后进行服务实例的挑选。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public Server choose (ILoadBalancer lb, Object key) { if (lb == null ) { return null ; } Server server = null ; while (server == null ) { List<Double> currentWeights = accumulatedWeights; if (Thread.interrupted()) { return null ; } List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0 ) { return null ; } int serverIndex = 0 ; double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1 ); if (maxTotalWeight < 0.001d ) { server = super .choose(getLoadBalancer(), key); if (server == null ) { return server; } } else { double randomWeight = random.nextDouble() * maxTotalWeight; int n = 0 ; for (Double d : currentWeights) { if (d >= randomWeight) { serverIndex = n; break ; } else { n++; } } server = allList.get(serverIndex); } if (server == null ) { Thread.yield(); continue ; } if (server.isAlive()) { return (server); } server = null ; } return server; }
在 choose 方法获取服务实例时,如果服务负载均衡器和所有访问器为空,则返回 null
之后当前所有服务的索引的最后一位,则是所有权重的总和,之后生成 0~总和 的随机权重
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 try { logger.info("Weight adjusting job started" ); AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb; LoadBalancerStats stats = nlb.getLoadBalancerStats(); if (stats == null ) { return ; } double totalResponseTime = 0 ; for (Server server : nlb.getAllServers()) { ServerStats ss = stats.getSingleServerStat(server); totalResponseTime += ss.getResponseTimeAvg(); } Double weightSoFar = 0.0 ; List<Double> finalWeights = new ArrayList <Double>(); for (Server server : nlb.getAllServers()) { ServerStats ss = stats.getSingleServerStat(server); double weight = totalResponseTime - ss.getResponseTimeAvg(); weightSoFar += weight; finalWeights.add(weightSoFar); } setWeights(finalWeights); } catch (Exception e) { logger.error("Error calculating server weights" , e); } finally { serverWeightAssignmentInProgress.set(false ); } } }
在这其中作为最终要的角色则是 DynamicServerWeightTask 方法,主要通过 totalResponseTime(总响应时间) 来计算权重,其原理就是使用 ServerStats 在负载均衡器中捕获每个服务器的各种统计信息,那么总响应时间就是通过他的 getResponseTimeAvg 方法获取处理请求的平均总时间,以毫秒为单位,这个过程主要找到最大 95% 的响应时间
这样每个服务器的权重就是 所有服务器响应时间的总和减去响应时间 (totalResponseTime - getResponseTimeAvg) 这样得出的结果就是 响应时间越长则权重越小,则选中的几率就很小
随机策略(RandomRule) RandomRule 主要是一个在现有服务器之间随机分配流量的负载均衡,他主要的步骤是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public Server choose (ILoadBalancer lb, Object key) { if (lb == null ) { return null ; } Server server = null ; while (server == null ) { if (Thread.interrupted()) { return null ; } List<Server> upList = lb.getReachableServers(); List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0 ) { return null ; } int index = rand.nextInt(serverCount); server = upList.get(index); if (server == null ) { Thread.yield(); continue ; } if (server.isAlive()) { return (server); } server = null ; Thread.yield(); } return server; }
通过choose 方法获取到服务器实例,如果负载均衡器和服务器为0则返回 null
首先他会通过 upList 以及 allList() 分别获取存活服务器列表和所有服务器列表,之后通过 Random 来生成一个随机数生成器
如果服务器正常运行,则返回该服务,并对可以请求的服务器标注一个随机的值,这就让每次通过 choose 方法获取到的服务器实例都会有一个随机的标注。
客户端配置启用线性轮询策略(ClientConfigEnabledRoundRobinRule) 客户端配置启用线性轮询策略,从名字上可以看出他就是为了启用 RoundRobinRule 策略的,因此他的整个流程都是通过 RoundRobinRule 来使用负载均衡器。而 choose 方法则也是通过 RoundRobinRule 来实现的,如果没有使用他则会出现 :“This class has not been initialized with the RoundRobinRule class” 的报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule { RoundRobinRule roundRobinRule = new RoundRobinRule (); @Override public void initWithNiwsConfig (IClientConfig clientConfig) { roundRobinRule = new RoundRobinRule (); } @Override public void setLoadBalancer (ILoadBalancer lb) { super .setLoadBalancer(lb); roundRobinRule.setLoadBalancer(lb); } @Override public Server choose (Object key) { if (roundRobinRule != null ) { return roundRobinRule.choose(key); } else { throw new IllegalArgumentException ( "This class has not been initialized with the RoundRobinRule class" ); } } }
最大空闲策略(BestAvailableRule) 该规则主要用于选择并发请求最少的服务器实例来提供服务,延伸自 ClientConfigEnabledRoundRobinRule,通过 LoadBalancerStats 来统计每个服务器的特征和信息,以此来过滤失败的服务实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public Server choose (Object key) { if (loadBalancerStats == null ) { return super .choose(key); } List<Server> serverList = getLoadBalancer().getAllServers(); int minimalConcurrentConnections = Integer.MAX_VALUE; long currentTime = System.currentTimeMillis(); Server chosen = null ; for (Server server: serverList) { ServerStats serverStats = loadBalancerStats.getSingleServerStat(server); if (!serverStats.isCircuitBreakerTripped(currentTime)) { int concurrentConnections = serverStats.getActiveRequestsCount(currentTime); if (concurrentConnections < minimalConcurrentConnections) { minimalConcurrentConnections = concurrentConnections; chosen = server; } } } if (chosen == null ) { return super .choose(key); } else { return chosen; } }
如果 loadBalancerStats 不为 null,则找出最小的并发连接数(minimalConcurrentConnections)来使用。假设 loadBalancerStats 为 null,则通过 ClientConfigEnabledRoundRobinRule 类中的 choose 类来使用线性轮询策略。
过滤线性轮询策略(PredicateBasedRule)
过滤线性轮询策略主要在过滤给定的服务器列表和负载均衡器后,通过内部定义的一个过滤器来筛选出服务实例清单,之后通过线性轮询方式过滤服务实例,并从清单选取一个服务实例。
是服务器过滤逻辑的基本构建块,可用于规则和服务器列表过滤器。 谓词的输入对象是PredicateKey,里面有Server和负载均衡器的key信息。 1 2 3 4 5 6 7 8 9 10 11 ```java public Server choose(Object key) { ILoadBalancer lb = getLoadBalancer(); Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); if (server.isPresent()) { return server.get(); } else { return null; } }
AbstractServerPredicate 过滤线性轮询的主要功能主要通过 AbstractServerPredicate 进行实现,而 AbstractServerPredicate 是 Predicate 的实现。
主要是用于确定给定输入的 true 或 false,因此也被称之为 “谓词”。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 >> 在数学逻辑中,谓词通常使用大写罗马字母表示,如P、Q、R这些,根据其变量值来可能为 “真假(true or false)” 的陈述。 >> >> 也可以理解为一个运算符或函数(布尔值函数),根据输入来返回一个 false or true。 1. 在初始阶段,他主要会通过 ```LoadBalancerStats``` 来获取负载均衡器的统计信息 1. 如果为空则返回负载均衡器的信息。 2. 不为空的话获取负载均衡器(```ILoadBalancer```),在获取负载均衡器的统计信息,并返回设置的负载均衡器统计信息(```setLoadBalancerStats```)。 1. 如果未找到 LoadalancerStats 未找到则返回 null ```java protected LoadBalancerStats getLBStats() { if (lbStats != null) { return lbStats; } else if (rule != null) { ILoadBalancer lb = rule.getLoadBalancer(); if (lb instanceof AbstractLoadBalancer) { LoadBalancerStats stats = ((AbstractLoadBalancer) lb).getLoadBalancerStats(); setLoadBalancerStats(stats); return stats; } else { return null; } } else { return null; } }
之后通过 getServerOnlyPredicate 来获取布尔值函数(即谓词),并通过 getEligibleServers 筛选出合格的服务器。最后使用 chooseRandomlyAfterFiltering 方法筛选并随机选择服务器实例,最后也可以通过 chooseRoundRobinAfterFiltering 方法来创建循环选择过筛选后的服务器实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public Optional<Server> chooseRandomlyAfterFiltering (List<Server> servers) { List<Server> eligible = getEligibleServers(servers); if (eligible.size() == 0 ) { return Optional.absent(); } return Optional.of(eligible.get(random.nextInt(eligible.size()))); } public Optional<Server> chooseRoundRobinAfterFiltering (List<Server> servers) { List<Server> eligible = getEligibleServers(servers); if (eligible.size() == 0 ) { return Optional.absent(); } return Optional.of(eligible.get(nextIndex.getAndIncrement() % eligible.size())); } public Optional<Server> chooseRandomlyAfterFiltering (List<Server> servers, Object loadBalancerKey) { List<Server> eligible = getEligibleServers(servers, loadBalancerKey); if (eligible.size() == 0 ) { return Optional.absent(); } return Optional.of(eligible.get(random.nextInt(eligible.size()))); } public Optional<Server> chooseRoundRobinAfterFiltering (List<Server> servers, Object loadBalancerKey) { List<Server> eligible = getEligibleServers(servers, loadBalancerKey); if (eligible.size() == 0 ) { return Optional.absent(); } return Optional.of(eligible.get(nextIndex.getAndIncrement() % eligible.size())); } public static AbstractServerPredicate ofKeyPredicate (final Predicate<PredicateKey> p) { return new AbstractServerPredicate () { @Override @edu .umd.cs.findbugs.annotations.SuppressWarnings(value = "NP" ) public boolean apply (PredicateKey input) { return p.apply(input); } }; } public static AbstractServerPredicate ofServerPredicate (final Predicate<Server> p) { return new AbstractServerPredicate () { @Override @edu .umd.cs.findbugs.annotations.SuppressWarnings(value = "NP" ) public boolean apply (PredicateKey input) { return p.apply(input.getServer()); } }; }
区域感知轮询策略(ZoneAvoidanceRule) 该策略是 过滤线性轮询策略(PredicateBasedRule) 的实现类,主要以 区域和可用性过滤服务器的规则 为基础,也通过组合过滤条件和该策略本身的过滤条件相辅相成,而 AbstractServerPredicate 则为次过滤条件。
他的过滤条件也非常的简单,首先,需要通过 randomChooseZone 来随机选择服务器,之后 selectedZone 来选择区域并返回,之后 totalServerCount 统计服务器总数,以及获取实例数(getInstanceCount)。
在配合 index 和 sum,其中 index 及服务器总数,每发现一个 +1。而 sum 则是实例数的总和,当服务总数小于总和时,那么将会选择该区域并返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static String randomChooseZone (Map<String, ZoneSnapshot> snapshot, Set<String> chooseFrom) { if (chooseFrom == null || chooseFrom.size() == 0 ) { return null ; } String selectedZone = chooseFrom.iterator().next(); if (chooseFrom.size() == 1 ) { return selectedZone; } int totalServerCount = 0 ; for (String zone : chooseFrom) { totalServerCount += snapshot.get(zone).getInstanceCount(); } int index = random.nextInt(totalServerCount) + 1 ; int sum = 0 ; for (String zone : chooseFrom) { sum += snapshot.get(zone).getInstanceCount(); if (index <= sum) { selectedZone = zone; break ; } } return selectedZone; }
当选择完后,就是通过 availableZones 方法获取可用区域,如果可用区数量等于1,则返回可用区(availableZones),当实例数为0时,开始移出可用区。
1 2 3 if (availableZones.size() == 1 ) { return availableZones; }
移出可用区的方式有很多,其中就是 获取心跳次数除于实例总数大于等于服务无回应百分比或每台服务器负载 ,小于0的,也会被移出可用区。
1 2 3 4 5 6 if (((double ) zoneSnapshot.getCircuitTrippedCount()) / instanceCount >= triggeringBlackoutPercentage || loadPerServer < 0 ) { availableZones.remove(zone); limitedZoneAvailability = true ; }
或者说每台服务器负载(loadPerServer)减去每台服务器最大负载(maxLoadPerServer)小于 0.000001d,则会被添加至最差区域(worstZones)。
1 2 3 4 5 6 7 8 9 if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d ) { worstZones.add(zone); } else if (loadPerServer > maxLoadPerServer) { maxLoadPerServer = loadPerServer; worstZones.clear(); worstZones.add(zone); }
也可以通过每台服务器负载(loadPerServer)大于服务器最大负载(maxLoadPerServer)的方式让其加入到最差区域中。
至于随机选择区(randomChooseZon),如果不为0,也会被移出可用区(因为避免区域被你霸占了),否则返回可用区。
1 2 3 4 5 6 7 String zoneToAvoid = randomChooseZone(snapshot, worstZones); if (zoneToAvoid != null ) { availableZones.remove(zoneToAvoid); } return availableZones; }
最后,当一系列的区域过滤完成后,通过线性轮询的方式从过滤结果中选出一个服务实例。
可用性过滤策略(AvailabilityFilteringRule) 该策略根据宕机或超过请求时限的活动连接来分配权重,他是 PredicateBasedRule 的延伸,其主要还是通过 AbstractServerPredicate 来实现具体的功能。
Ribbon 负载均衡策略自定义 自定义负载均衡策略需要先运行 consul 除服务消费者之外的集群,之后新建项目添加依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-ribbon</artifactId> <version>2.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> <version>3.0.3</version> </dependency> <!-- https://mvnrepository.com/artifact/com.netflix.ribbon/ribbon-loadbalancer --> <dependency> <groupId>com.netflix.ribbon</groupId> <artifactId>ribbon-loadbalancer</artifactId> <version>2.7.18</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> </dependencies>
application.properties 1 2 3 4 5 spring.application.name =Ribbon Rule server.port =9003 spring.cloud.consul.host =localhost spring.cloud.consul.discovery.service-name =service-provider # 服务提供者名字 spring.cloud.consul.discovery.register =false # 是否注册服务
Application 在启动类中通过 @LoadBalancd 以及 @Bean 来实例化 restTemplate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@SpringBootApplication public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } @LoadBalanced @Bean RestTemplate restTemplate () { return new RestTemplate (); } }
RibbonConfig 在配置类中启用 Configuration 注解,并使用 RibbonClient 来配置服务提供者名称,最后通过 Bean 注解实例化负载均衡策略(RandomRule)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import com.netflix.loadbalancer.IRule;import com.netflix.loadbalancer.RandomRule;import org.springframework.cloud.netflix.ribbon.RibbonClient;import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration @RibbonClient(name = "service-provider", configuration = RibbonClientConfiguration.class) public class RibbonConfig { @Bean public IRule irule () { return new RandomRule (); } }
TestController 测试类主要用于通过负载均衡器来使用负载均衡策略实现选择服务实例的效果:
运行后浏览器打开 http://localhost:9003/hey 每次刷新会得到不同的服务提供者实例返回的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.example.demo.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.text.SimpleDateFormat;import java.util.Date;@RestController public class TestController { @Autowired private LoadBalancerClient loadBalancerClient; Date date = new Date (); SimpleDateFormat simpleDateFormat = new SimpleDateFormat ("yyy-MM-dd hh:mm:ss" ); @GetMapping("/hey") public String hey () { ServiceInstance serviceInstance = loadBalancerClient.choose("service-provider" ); String callService = "Host: " + serviceInstance.getHost() + " Port: " + serviceInstance.getPort() + " Date:" + simpleDateFormat.format(date); return callService; } }
Feign 需要注意的是 Feign 是除了 RestTemplate 客户端实现服务发现外 ,可以实现服务发现的另一种方式,通过整合 Ribbon 好后可以提供负载均衡的功能。
Feign 与 Ribbon 最为不同的就是调用方式,通常情况下 Ribbon 需要自己构建一个 HTTP 请求,然后 RestTemplate 将该请求发送给其他服务,RibbonClient(value = "serverName") 就印证了这一点。
而 Feign 是在 Ribbon 的基础上在进行一层封装,因为是采用接口的方式他并不需要自己构建 HTTP 请求。只需要将其他服务方法定义成抽象方法即可 ,@FeignClient("ServerName") 注解来调用服务。
属性值应与服务中心方法名一致 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #### Feign 工作流程 上述这些与 Feugn 的工作原理密不可分,在正常的情况下他有四个步骤,分别为:  在这个过程中,如果使用了 ```@FeignClient``` 注解,那么 Feign 客户端将会创建一个动态代理。 之后调用这个接口(即调用了 Feign 客户端所创建的动态代理),而 Feign 客户端的动态代理会根据接口上的 ```@RequestMapping``` 注解来构造出地址以及方法。 最后发起请求并解析响应  服务提供者 **调用了** 定义 ```@FeignClient``` 注解的接口时,Feign 会构建一个动态代理,之后构造地址 最终向接口发送请求(也就是服务提供者) #### Feign 负载均衡  Feign 是一个声明式的 Web Servce 客户端,Spring cloud 客户端添加了Spring MVC 的支持,Feign 在整合了 Ribbon 后即可一共负载均衡的功能,在此之前我们需要添加依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
以及在 application.properties 全局配置文件中配置服务端口以及名称等:
1 2 3 4 5 spring.application.name =Feign Configuration server.port =8780 spring.cloud.consul.host =localhost spring.cloud.consul.port =8500 spring.cloud.consul.discovery.register =false
然后在启动类中通过 EnableDiscoveryClient 来启用服务发现,并使用 @EnableFeignClients 注解来启动 Feign 客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.example.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.openfeign.EnableFeignClients;@EnableDiscoveryClient @EnableFeignClients @SpringBootApplication public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
Config 在配置类中我们主要用于实现使用默认契约,即 Spring cloud Netfilx MVC Controller 改为 feign.Contract.Defaull 原生契约。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.example.demo.config;import feign.Contract;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class FeignConfig { @Bean public Contract contract () { return new Contract .Default(); } }
Interface 为了通过 consul 服务发现提供者,因此需要通过 FeignClient 注解来进行连接,也就是 Feign 工作流程中的 “构造地址‘,以及 Feign 的配置等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.example.demo;import feign.RequestLine;import org.springframework.cloud.openfeign.FeignClient;import com.example.demo.config.FeignConfig;@FeignClient(contextId = "feignClient", name = "service-provider", configuration = FeignConfig.class) public interface FeignClientInterface { @RequestLine("GET /hey") public String hey () ; }
controller 最后的 Feign 控制器则是为了提供服务,因此只需要通过调用 Feigin 接口即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.example.demo.controller;import com.example.demo.FeignClientInterface;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class FeignController { @Autowired FeignClientInterface feignClientInterface; @GetMapping("/hey") public String index () { return feignClientInterface.hey(); } }
Feign 记录日志
在 Feign 中,记录日志的等级可以分为四个,分别为:
ID
DA
NONE
不记录(默认)
BASIC
只记录和请求方法、URL、相应状态码以及执行时间
HEADERS
只记录基本信息,请求和响应的标题
FULL
记录请求、响应的标题以及正文和元数据
在 Feigin 负载均衡的基础上,我们实际上只需要修改或添加 config 包下的 FeignConfig 类即可:
FeignConfig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.example.demo.config;import feign.Contract;import feign.Logger;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class FeignConfig { @Bean public Contract contract () { return new Contract .Default(); } @Bean Logger.Level level () { return Logger.Level.FULL; } }
Logger 是一个日志管理器,与 level 组合为 Logger.Level 即日志管理器等级
application.properties 之后在 applicatin.properties 全局配置文件中添加 logging.level.com.example.demo=DEBUG 来开启记录日志的包:
1 2 3 4 5 6 spring.application.name =Feign Configuration server.port =8780 spring.cloud.consul.host =localhost spring.cloud.consul.port =8500 spring.cloud.consul.discovery.register =false logging.level.com.example.demo =DEBUG
Spring cloud Consul 是由 HashiCorp 公司使用 Go lang 所开发的一个 服务治理 项目,主要包含了 服务治理、健康检查、Key-value 存储、多数据中心 的功能。与 其他服务中心相比,Consul 支持多数据中心,内外网服务采用不同的端口号进行监听,除此之外还可以避免单数据中心故障所导致的问题。
Consul 所使用的是 Raft(Reliable, Replicated, Redundant,d Fault An-Tolerant,可靠、可复制、可沉余、可容错) 算法来保证一致性,相比 Poxos,Raft 的目标则是提供更加清晰的逻辑分工使得算法本身可以被更好的理解。
原理流程 工作原理
服务提供者(Service Provider)在启动时向 Consul 发送一个POST 请求,来告诉自己的 IP和端口号等信息 。Consul 收到了服务提供者的注册后,默认每隔10s想服务提供者发送一个健康请求,来验证该提供者是否可以正常提供服务。
Consul 中的服务提供者临时表每隔 10s 更新一次,只包含了通过健康检查的服务提供提供者。
之后服务消费者(Service Consumer)在调用服务提供者之前会从 Consul 获取存储的服务提供者 IP 和地址的临时地址表,之后发送请求给 服务提供者。
集群原理
Consul 的常见黑话有很多,如 Agent、Client、Server、DataCenter、Consensus、Gossip、LAN Gossip、WAN Gossip、RPC…… 他们的意思如下:
Agent
Consul 集群中每个成员都会运行 Agent,他是一个守护进程。主要分为 Server 与 Client 模式,能运行 DNS 或 HTTP 接口,负责在运行时检查和保持服务同步 。
Client
Client 转发所有 RPC 到 Server 的代理,Client 想对于是无状态的,他唯一执行后台活动是加入 LAN Gossip 池。Client 只需要较低的资源开销,以及较少的网络流量带宽等。
RPC(Remote Procedure Call)远程过程调用 ,该协议允许一台计算机的程序调用另一台开放网络的计算机。在 Consul 集群中主要允许 Client 请求 Server 的请求/响应机制
LAN gooip 包含了所有与位于同一个局域网或同一个数据中心的所有节点
Gossip 主要用于实现基于 UDP 的随机点到点通信
Server
具有扩展功能的代理,功能包括Raft选举、维护集群状态、响应 RPC差需、与其他数据中心交互 WAN gossip、转发查询给 Leader\远程数据中心。 他主要是在局域网内与本地客户端进行通信,通过广域网与其他数据中心进行通信,通常 Server保存集群中的配置信息(每个数据中心的 Server 数量建议为 3~5个)
Wan gooip 只包含分布在不同数据中心的所有节点,数据中心间通常采用互联网或广域网进行通信。
DataCenter
一个私有且低延迟和高宽带的网络环境
Consensus
Consensus 用于代表复制机的状态一致性,即用于表明 Server 与 领袖选举的事务顺序达成一致。
Gossip and Wan and LAN 在 Consul 数据中心中,官方给我们最好的建议数量是三台到五台之间。这是经过深思熟虑思考之后所得到的结果,当很多台数据中心加入后,则达成共识的过程很慢,但只有1~2台数据中心可用性又不高,因此所得出的权衡利弊的数量为 3~5台之间。
Gossip (第六届ACM分布式计算原理年会论文集)
Demers, Alan; Greene, Dan; Hauser, Carl; Irish, Wes; Larson, John; Shenker, Scott; Sturgis, Howard; Swinehart, Dan; Terry, Doug (1987-01-01). Epidemic Algorithms for Replicated Database Maintenance. Proceedings of the Sixth Annual ACM Symposium on Principles of Distributed Computing. PODC ‘87. New York, NY, USA: ACM. pp. 1–12. doi:10.1145/41840.41841. ISBN 978-0897912396. S2CID 1889203.
Gossip protocol 又称 Epidemic Protocol(流行病协议),该协议早在1987年发表在 ACM 论文 《Epidemic Algorithms for Replicated Database Maintenance》中,主要用于在分布式数据库系统中各个副本节点间的同步数据。
八卦通信模型 (Gossip Protocol) 无论喜欢与否,八卦在人类社会中扮演了重要的角色。Dunbar(一位人类学家)在具有争议的一本书中声称,语言的出现原因是允许闲聊,而闲聊的整个过程是不需要仔细思考和梳理的。
因此无论什么情况,毫无疑问这些流言蜚语仍然是一种在社会活动中传播非常优秀的。特别是传播速度非常狂,而这个过程对组织信息传播的企图具有一些抵抗力。
Kimmel 在书中给出了许多关于人类八卦的例子和细节
虽然八卦通常会被认为是一种传播手段,但是实际上并不仅仅是机器上的传递,而个经过处理的。具体流程为一个人收集、处理信息并将处理后的信息传递给其他人。 在这个过程中信息至少会根据其兴趣进行过滤,这样一来最有趣的新闻在传播到每个人之前就不会停止传播。
更复杂的化来讲,信息是逐渐改变的,这增加了过程的复杂性并可能导致突发行为,其中这个平台则充当了 “集体智能”的信息处理媒介。
流行病传播模型 (Epidemics Protocol) 实际上这些八卦非常类似于流行病,病毒扮演着信息的角色,而感染者则扮演者了解信息的角色。在过去的几年当中 “病毒式营销” 的概念非常火爆,通过视频分享平台以及社交网络,广告商可以有意识的利用日益高效的传播八卦广告。
而令人震惊或非常有趣的广告,特别是设计会有极大限度的提高机会,阅读者通常会通知他们的朋友等等。
分布式系统设计灵感 第一原因 八卦对于大型分布式系统来说非常有意义,主要有两个原因。其中之一就是设计新协议的灵感来源: Gossip 有几个吸引人的特性,如简单、速度、健壮等,除此还包含了缺乏中央控制的瓶颈。
这些特性对于大型分布式系统关键组成部分的 信息分发和集体信息处理(聚合) 非常重要。
第二原因 随着当今互联网的稳步发展,病毒和蠕虫的传播策略越来越为之复杂。受感染的计算机通常会组成网络(被称为 “僵尸网络”),能够进行协作和执行攻击等手法。
这对互联网基础设施构成了非常重大的威胁,为应对这些网络的一个办法就是设防防止他们的传播,这需要对流行病有很好的了解才可预防。
在本篇论文中,我们主要关注流行病以及八卦作为设计高可用组织系统和服务的灵感作为来源。
Gossip in Consul 概述 Consul 使用 Gossip 协议来管理成员资格并向集群广播消息,通过 Serf 库所提供,所基于 “SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol” 论文并做了一些必要的修改。
WAN and Lan Consul 使用了两个不同的八卦池,分别称之为 Lan 与 Wan 池,每个数据中心都有一个 Lan 池,其中包含数据中心的所有成员(包括客户端和服务器)
Lan 池的主要作用是成员信息允许客户端自动发现服务器,以此来减少所需的配置量。在分布式故障检测中允许检测的工作由整个集群共享,而不是单个服务器进行, 他包含了所有位于同一个局域网或者同一个数据中心的所有节点
而 Wan 池是全局唯一的,无论数据中心如何,所有服务器都应参与WAN池。WAN 池提供成员信息允许服务器执行夸数据中心的请求。
故障检测允许 Consul 优雅的处理丢失连接的整个数据中心,或仅处理远程数据中心的单个服务器,他只包含了分布在不同数据中心的所有节点
集群实现原理 Leader and Client&Server 从上图可以得知,每个数据中心的 Client 以及 Server 是混合的,一般官方建议数据中心 Server 通常为 3~5 台。
这是经过深思熟虑之后所得出的结果,因为如果有太多的机器加入则达成共识会变慢。但只有 1~2台的数据中心可用性又不高,于是 3~5台是最好的选择
数据中心的领袖(Leader)是有一个额外工作的 Server,他是由所有 Server 选出的(Raft 协议),主要用于处理所有的查询和事务。由于需要遵一致性协议的要求,所以事物也必须被复制到其他所有节点中(CAP 协议)。
每个数据中心的 Server 都是 Raft 节点集合的一部分,在这期间内如果有一个非领袖服务器收到了RPC请求,将会将请求转发给集群中的领袖服务器中。
WAN and LAN 为了允许数据中心能够以 “低耦合(Low-touch)”的方式发现彼此。那么通过 WAN gossip Pool 来只包含服务节点的信息,用于优化网络延迟,那么一个新的数据中心就很容易加入到现存的 WAN gooip 池中。
无论数据中心如何,所有服务器都应参与WAN池的原因,所以服务也支持跨数据中心请求
这个时候一个服务在收到了来自另一个数据中心的请求后,会随即将该请求转发给正确的数据中心的服务中。之后该服务再将请求转发给本地的领袖服务(Leader),这使得服务之间有一个很低的耦合。
由于 Consul 自身的一些优势,如提所提供的健康检查、链接缓存、以及复用等功能,使得跨数据中心请求都是相对快速且可靠的。
Consul 集群实现
install 1 2 3 curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" sudo apt-get update && sudo apt-get install consul
至于第二条命令可能很多 debian er 无法使用,我们需要安装 sudo apt-get install software-properties-common 软件包来解决你 add-repository 无法使用的情况
当你执行第三条命令的时候,也就是 update 对于一些非常纯净的发行版,你可能还需要安装 sudo apt-get install apt-transport-https
当安装完成后我们可以执行 consul 来验证是否安装成功:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # consul Usage: consul [--version] [--help] <command> [<args>] Available commands are: agent Runs a Consul agent catalog Interact with the catalog event Fire a new event exec Executes a command on Consul nodes force-leave Forces a member of the cluster to enter the "left" state info Provides debugging information for operators. join Tell Consul agent to join cluster keygen Generates a new encryption key keyring Manages gossip layer encryption keys kv Interact with the key-value store leave Gracefully leaves the Consul cluster and shuts down lock Execute a command holding a lock maint Controls node or service maintenance mode members Lists the members of a Consul cluster monitor Stream logs from a Consul agent operator Provides cluster-level tools for Consul operators reload Triggers the agent to reload configuration files rtt Estimates network round trip time between nodes snapshot Saves, restores and inspects snapshots of Consul server state validate Validate config files/directories version Prints the Consul version watch Watch for changes in Consul
run 可以通过以开发者模式运行和服务器模式运行两种,可通过使用下述命令直接进行启动:
1 2 consul agent -dev # consul agent -server
or -server``` 的区别主要是开发者模式和服务器模式两种运行模式,而 **agent** 则是用于注册服务、运行健康检查的一个功能。每个数据中心都要求至少有一台 Agent (Server 模式) ,推荐于 3~5 台。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 他主要有 Client 模式和 Server 两种模式,其中 Client 模式主要是用于运行写上述的服务(注册服务、健康检查……)而 Server 则是主要接收这些功能查询后的数据。 > 当运行之后我们可以通过访问 [http://127.0.0.1:8500/](http://127.0.0.1:8500/) 来进行查看和管理 Consul 集群状态 **常用命令** | ID | DA | FA | | --- | --- | --- | | members | 列出 Consul 下集群成员 | | | | -datailed | 查看 Consul 集群成员详细信息 | | | monitor | 持续打印当前 Consul 代理流日志 | | | agent | 运行 Consul 代理 | | | | -dev | 开发者模式 | | | -server | 服务器模式 | | join | 加入某个集群 | | #### vagrant init Consul 集群的实现也非常简单,但多出了 ```join``` 参数,主要是加入集群。而在测试环境中,在正常且资金不允许的情况下我们是不会上服务器的,所以上也有很多文章出现了一些迷惑性文章。 比如,运行 ```xxx``` 命令,之后执行 ```join```,这就很无语。首先,我们只要运行一个单个 consul 集群就会发现,他 **并不是交互式的**,你这个终端就挂着这个,之后你在开个新终端,来加入他,这也合理。 > 不要问为什么是 Vagrant 问就是 HashiCorp 项目下的(与 Consul 一个公司出的) 但是最不合理的情况发现了,就是 ```-bind``` 参数下到底要填写什么。通常情况下我们会通过 vagrant 来部署测试环境进行实现。 但幸运的是我们不需要单个手动配置 Vagrant 环境,只需要下载 Consul 所为我们提供的集群测试环境即可(实际上就是两个 debian 装个 Consul):[https://github.com/hashicorp/consul/blob/master/demo/vagrant-cluster/Vagrantfile](https://github.com/hashicorp/consul/blob/master/demo/vagrant-cluster/Vagrantfile) 我们可以选择手动安装或自动安装,即执行 ```vagrant up``` 根据 **Vagrantfile** 文件内配置执行一些命令。如 Consul 所提供的则是自动安装 Consul,则可能会造成卡住的问题,读者可自行进行解决。 当配置好测试环境后,我们即拥有了三台测试环境,分别为 vagrant box#2 以及自己本机的环境,我们要保证这三台环境内可以正常启动 consul 以及其他等等,之后即可进入下一步。 #### localhost 我们首先需要将我们本机成为整个集群中的 **领导者(server/leader)**,之后剩下的 n1、n2 则扮演客户端的角色进行: ```shell consul agent -dev -node=consul-server -bind=172.20.20.1 -data-dir=data -client 0.0.0.0
通过上述命令,我们主要开启了 测试 模式下的 consul ui ,可通过访问 http://localhost:8500/ui 来直接进行通过可视化的方式观察集群状态。
参数为本机器的 IP 地址。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 在实际的生产环境中,我们时候使用的则是 ```consul agent -server -bootstrap-expect=2 -node=consul-server -bind=172.20.20.1 -data-dir=data -client 0.0.0.0``` 来通过 ```bootstrap-expect``` 限制集群数量等。 而生产环境是不需要可视化 ui的,因此我们可以通过下面几个 api 接口来进行查看: | ID | DA | | --- | --- | | ip:port/v1/status/leader | 显示当前集群的领袖服务器 | | ip:port/v1/agent/members | 显示集群中所有成员信息 | | ip:port/v1/status/peers | 显示当前集群中的 Server 成员 | | ip:port/v1/catalog/services | 显示所有服务 | | ip:port/v1/catalog/nodes | 显示集群节点的详细信息 | #### vagrant up  ##### n1 在 n1 中,我们可以通过使用 ```vagrant ssh n1``` 直接进入 n1 的 debian 环境,之后建立一个目录也可以之际运行: 需要注意的是当 ```vagrant init``` 完事之后,才可以使用 ```vagrant up``` 来进行启动,最后通过 ```vagrant shell n1``` 来选择进入多个环境下进行工作。 ```shell consul agent -node=client-one -bind=172.20.20.10 -enable-script-checks=true -data-dir=data
通过使用 enable-script-checks 来开启 consul 的健康检查,我们也可以在开一个 n1 交互式 shell 进行加入服务集群:
n2 n2 的流程和步骤也基本上和 n1 相差无几,我们也是通过 vagrant ssh n2 进入环境,之后根据自己喜好决定是否建立新的目录(主要存储文件):
1 consul agent -node=client-two -bind=172.20.20.11 -enable-script-checks=true -data-dir=data
之后你可以在建立一个 shell n2 连接来加入 consul-server 集群中:
服务提供者以及服务消费者 通过 Consul 实现服务提供者和服务消费者之前,我们首先需要在构建项目中选择 Spring web、Spring Boot Actuator、Consul Discovery 等项目的依赖,当然也可以在 pom.xml 文件下进行添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies >
之后我们的服务消费者不需要 spring-boot-starter-actuator 依赖。
服务提供者 我们首先模仿下真实环境下的三台服务器,分别运行着功能服务,因此我们主要通过 .properties 进行实现和模拟这个环境。在此之前,我们首先需要对启动类中添加 Consul 的服务注册注解 @EnableDiscoveryClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.example.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication @EnableDiscoveryClient public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
properties application.properties 1 2 3 4 5 6 spring.application.name =consul-provider server.port =8501 spring.cloud.consul.port =8500 spring.cloud.consul.discovery.service-name =service-provider provider.name =provider logging.level.ROOT =info
需要注意的是 spring cloud consul discovery service-name 是一个 spring cloud consul 发现服务时的名称,而 logging.level.ROOT 则是用于描述日志等级的。
application-consul-provider-one.properties 1 2 3 4 5 6 spring.application.name =consul-provider-one server.port =8502 spring.cloud.consul.host =localhost spring.cloud.consul.port =8500 spring.cloud.consul.discovery.service-name =service-provider provider.name =provider-one
application-consul-provider-two.properties 1 2 3 4 5 6 spring.application.name =consul-provider-two server.port =8503 spring.cloud.consul.host =localhost spring.cloud.consul.port =8500 spring.cloud.consul.discovery.service-name =service-provider provider.name =provider-two
Controller HeyController.java 当配置完启动类和全局文件之后,我们首先需要实现服务提供的接口信息,所提供的也非常简单,就是 .properties 文件的内容获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.example.demo.controller;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class HeyController { @Value("${provider.name}") private String name; @Value("${server.port}") private String port; @RequestMapping("/hey") public String hey () { String string = "提供者名称:" + name + " 端口号为:" + port; return string; } }
run 1 2 3 $ java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=consul-provider-one $ java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=consul-provider-two $ java -jar demo-0.0.1-SNAPSHOT.jar
读者可能非常疑惑 spring.profiles.active 的作用是什么,实际上 profiles 是 pro(perties 的缩写,与 file 相加所等于的 profiles。可以理解为当多个 properties 文件时,运行那个配置文件,之后访问 ip:8501/hey、8502/hey、8503/hey 都可以看到返回的信息。
服务消费者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > <version > 3.0.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies >
application.properties 1 2 3 4 5 6 spring.application.name =consul-consumer server.port =8504 spring.cloud.consul.host =localhost spring.cloud.consul.port =8500 spring.cloud.consul.discovery.register =false
因为服务消费者并不需要提供服务,所以我们可以选择通过 spring.cloud.consul.discovery.register=false,让其不注册服务到 consul 集群中
controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.example.demo.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import java.net.URI;@RestController public class HeyController { @Autowired() private LoadBalancerClient loadBalancerClient; @GetMapping("/hey") public String hey () { ServiceInstance serviceInstance = loadBalancerClient.choose("service-provider" ); URI uri = serviceInstance.getUri(); String callService = new RestTemplate ().getForObject(uri + "/hey" , String.class); return callService; } }
在这其中起到主要作用的就是 RestTemplate,他起到了服务提供者负载均衡的功能。假设我们有三台服务提供者,那么我们每次刷新服务消费者时,会出现不同的服务名称及端口号等信息。
拜占庭容错算法(Byzantine Generals Problem) 理想流程 拜占庭容错算法也叫拜占庭将军问题(Byzantine Generals Problem),由莱斯利-兰波特在论文中所提出的分布式对等网络通信容错问题。
假设一组罗马(拜占庭)将军各率领一直军队 共同 围攻一座城市,为简化问题,各种军队的行动策略分别为 进攻与撤退 两种。
每个支部队的将军必须通过投票来达成一致策略(所有军队一起进攻或撤退),因为没支部队位置不同,所以需要 信使 来进行没支部队的互相联系。
此时每支部队将军可以根据自己的投票和其他将军投票的信息来共同知道所决定的行动策略。
实际问题 一致性 假设在这些8个将军中出现了1叛徒,他们不仅可能向较为糟糕的策略进行投票,还可能选择性的传递投票信息(假设4位将军投的进攻,4位将军投的撤退,那么给进攻的将军说他们投的都是进攻,给投撤退的将军说投的都是撤退)。
那么这样各支军队的一直协调即一致性遭到了破坏
可靠性 由于各个将军需要通过 信使进行通讯 ,那么叛徒将军可能通过伪造的信件以及以其他将军的身份发送假的投票。
即使我们保证了所有将军都可靠的情况下,也不能排除信使被敌人截杀或叛变以及替换等情况,因此很闹保证人员的 可靠性 。:
拜占庭容错 假设整套体系都按照理想化正常情况下,将军们仍然可以通过 多数决定战略 ,而这将会被称之为 拜占庭容错
Raft(Reliable,Replicated,Redundant,And Fault-Tolerant) 概述 Raft 集群(Reaf cluster)中主要分为 领袖(leader)、追随者(follower)、候选人(candidate) 。
在一个 Raft 集群中,正常情况下都会有一个领袖,其他都是追随者,领袖会负责所有的外部请求(如果不是领袖机器收到,则请求将会被导到领袖中)。
领袖会有一个固定时间发送消息,即 “心跳(heartbeat)”,让追随者知道领袖还在正常运作。每个追随者都会设计一个计时机制(timeout),当超过一定(通常在 150ms、300ms)时间没有收到领袖的心跳,那么集群将会进入到选举状态。
领导选举 假设领袖机器死机,那么就需要选择新的领袖。此时进入一个新的任期开始选举,成功当选的领袖开始工作,知道他死了出现故障为止,此时将开启新的一轮选举。
选举是 候选人 所发动的,当领导心跳超时的时候追随者就会将自己的任期编号+1,来表示自己参加竞选并投自己一票,并向其他服务器拉票(每台服务器任期只会投一票,固定给最早拉票追随者 )
如果候选人收到了其他候选人的拉票,且拉票的任期编号大于自己的任期编号,就会人定位落选并成为追随者,以此认为来拉票的候选人为领袖,如果有候选人收到了过半的宣判就当选为新的领袖
假设在 REST 服务器超时期限过了还没有选出新的领袖时,那么任期将会自动终止,开启新的一场选举。
Raft 的每台服务器超时期限都是随机的,这也降低了同时竞选的几率,也降低了两个竞选人得票不过半的选举失败几率。
这场选举也许不是最为公平的,因为当领袖死机的时候,每个已存储指令必定在服务器中已经写入过半。而选举流程则是会让记录较为完整的候选人来胜选,因为在候选人拉票的时候会透漏自己记录的最新信息。
记录复写 记录复写主要的主角在领袖身上,在 Raft 集群中有一个复写的状态机(State machine)来执行外来的指令,而领袖接受指令来写入记录中。
之后将指令转发给追随者,如果追随者没有反应,领袖会不断的重新发送指令给每个追随者,直到每个追随者将新指令写入记录中为止。
最后领袖收到过半追随者确认写入的信息后,将会将指令视为已存储(committed),追随者发现状态变成已存储,将会在状态机上执行该命令。
当领袖死机的时候,领袖的某些新指令还没有到写入集群当中,因此会造成集群记录处于不一致的问题,因此为解决此问题:
新领袖会担有重返一致性的责任,让每个追随者记录都和他一致。他将每个追随者记录进行比较,找出两者一致的最后一个不一致的指令进行删除,将自己之后的指令拷贝个追随者。 假设追随者司机,那么给他转发的所有指令都会被回应失败,而发送端会持续重新发送。当这台追随者重新加入集群时,就会收到这些指令并重新回应(已经写入的指令不会被重新写入)。
布鲁尔定理(Brewer’s theorem)
概述 布鲁尔定理(Brewer’s theorem)也被称之为CAP定理(CAP theorem),主要指出咋分布式系统中的:
一致性(Consistency),执行同一个举动
可用性(Availability),每次请求都能获取到正确的响应
分区容错性(Partition tolerance),保证服务宕机时其他服务依然可以正常提供服务(系统中任意信息丢失或失败不会影响系统的继续运作)。
三个场景 通过 CAP 定理,我们无法同时满足一致性、可用性、分区容错性这三个特性,因此我们假设:
CP ( 一致性和分区容错性) CP 即 一致性、分区容错性,牺牲掉了可用性保证了一致性,可能会有几个节点不可用,通常适用与银行系统。
AP(可用性和分区容错性) AP 是可用性和分区容错性,舍弃掉了一致性,保障服务可用但可能会造成数据的冲突,可适用流量访问大对系统正常提供服务要求较高的系统;
CA(一致性和可用性) 一旦集群中一台服务无法正常提供服务则会造成当前集群完全崩溃,适用与挑战者。
MVC(Model-View-Controller) MVC(Model View Controller)是在软件工程中较为常见的一种架构模式,无论是 Spring cloud 以及 Spring、Spring boot 基本上我们都介绍过此模型。
如果要简单的理解 MVC 的话,我们可以从上图了解到,首先 Model 是拥有对数据直接访问的权利(例如数据库访问)。
而 Conttroller 则是可以控制应用程序的流程,并对事件作出响应,这也包括了对 Model 的操作。
最后则是 View ,他能够显示资料,通常为前端应用。在上图中 Controller 操纵 Model 进行数据的更新或插入,之后交给 View 进行显示给用户。
如果说前面我们介绍的都是 MVC 概述,那么通过上图我们可以详细的进行理解。首先浏览器一看是看到的并不是 View,而是 通过 DispathcherServlet 来调用视图渲染后返回给浏览器中。这里我们需要注意的是 DispathcheServlet 继承于 HttpServlet ,主要负责协调和组织不同组件来完成请求并返回响应的工作,他的流程如下:
浏览器发送请求,服务器接受请求并转交给 DispatcherServlet 进行处理
DispatcherServlet 匹配在控制器中配置的映射路径,进行下一步处理
View 请求将 Model 和 View 解析成 View,之后调用 render() 方法根据 Model 和 View 中的数据渲染出页面。
三层架构 这与 MVC 的三层架构有很大的关系,所谓三层架构即 表现层(UI)、业务逻辑层(Service)、数据访问层(Dao\Repository)三种,他们分别表示:
Responsitory 是存取和管理对象,而 Dao 则是存取对象
表现层:用于展示界面 ,接收用户请求并返回数据,为客户端提供应用程序的访问接口(View)
业务逻辑层:也称之为服务层,负责业务逻辑的处理,主要调用D哦曾对数据进行 CURD 的操作
数据访问层:与数据库进行交互的持久层,被 Service 调用,在 Spring Data JPA 中则由 Hibernate 进行实现。
WebFlux
反应式是关于异步和事件驱动的非堵塞应用程序,并且需要少量线程进行垂直扩展,而不是水平扩展(集群)。反应式系统具有某些特征,可以使其成为低延迟、高吞度量工作的理想选择。
WebFlux 是在 Spring Framework 5.0(5.0.0.M5)所引入的一种反应式 Web 框架。WebFlux 可以在资源有限的情况下提高系统的吞度量和垂直扩展,这意味着在同样情况下 WebFlux 的吞吐量明显优于 MVC。
响应式(反应式)与命令式 为了应对高并发环境下服务端开发,微软提出了一个实现异步编程方案,即 反应式编程(Reactive Programming) 。其他技术和社区为了跟上脚步,因此向 Netifix 等公司都也提供了类似的技术,这使得 Java 平台也有了能够实现反应式编程的框架
响应式编程或反应式编程(Reactive programming)是一个面向数据流和 变化传播 的声明式编程范式(Declarative programming)。
响应式编程与命令式编程对立,他的目标是让计算机明白,而非流程,声明式编程不需要告诉电脑问题,即告诉结果,让机器自行解决
而响应式编程则是是意味着式子 a=b+c,这意味着 a 是由 b 和 c 计算而出,如果后续有变化会影响到 a 的值,这也是 即告诉结果,让机器自行解决
命令式编程或指令式编程(Inperative programming),详细命令机器去处理一种事情而达到你想要的结果 。 变化传递我们可以理解为在命令式变成中,假设式子为 a=b+c 那么 a 的值就来自 b和c计算出的,如果后续有变化不会影响到a的值,这也是为什么他是 细命令机器去处理一种事情而达到你想要的结果
Spring WebFlux 是一个从头开始构建的非堵塞Web框架,可利用多核下一代处理器并处理大量并发。而 WebFlux 与 Spring MVC 数据流的选择也各不相同,前者是 Reactive Streames ,后者则是 Servlet API 。
自然而然,既然 WebFlux 是用的是响应式数据流,而Spring MVC 选择的是 命令式数据流,自然而然 WebFlux 会更快。我认为上图很好的诠释了这一点,Spring MVC 是一个 响应 -> 准备数据 -> 返回数据 的一个过程。而 WebFlux 则是 响应 -> 返回数据 -> 开启一个新的 Work 线程进行准备 -> Work 线程完成工作 -> 返回结果 从而让对方觉得 WebFlux 会更快的一点。
Reactor Peactor 框架由 Pivotal 基于 反应式编程(Reactive Programming) 思想进行实现,符合反应式编程的规范。
响应式编程 响应式编程(Reactive Programming)是一种非堵塞且事件驱动数据的开发方案,使用函数式编程的概念来操作数据流。
函数式编程是一种编程方式,将计算机的运算视为函数计算,如何直接和生动形象的表现出函数式编程的特点呢,那么下面的表达式就非常的清晰
过程式编程: var a = 1 + 2;函数式编程: var result = subtract(multiply(add(1,2), 3), 4);
其实函数式变成并没有什么特殊,他其实就是将一数复制给变量或者一个数组中,就这个意思。 函数变成最主要的是λ演算 (Iambda calculus),而λ演算可以 接受函数党组输出(参数)和输出(返回值) ,相比过程化编程相比,函数式编程里的函数可以随时调用
事件
方法
包含元素信息
onNext()
序列结束消息
onComplete()
序列出错消息
onError()
这是通知与订阅者对应的方法,但如果我们继续眼神可以了解到背压机制(与本文有关):
事件
Iterable 迭代模式 (拉\pull)
Observable 观察模式(推\push)
获取数据
T next()
onNext()
处理完成
hasNext()
onCompleted()
发现异常
throws Exception
onError()
1.事件发布者(Publisher) 主动推送数据给 订阅者(Subscriber) ,出发 onNext() 方法; 2.事件发布者 发生异常,则触发 订阅者 的 onError() 方法进行异常捕获处理 3.事件发布者 每次推送都触发一次 onNext() 方法,当所有推送无异常时, onCompleted() 方法在在最后触发一次。
我们可以从被订阅者和订阅者理解为服务提供者和服务消费者。
如果 被订阅者发布的消息太快了,超过了订阅者的处理速度,那么这个时候就需要通过 背压机制(Backpressure),使得订阅者能够控制消费消息的速度。
实际上除了 迭代模式 (lterable) 其他的都是我们 响应式编程API的使用方式 ,实际上激素和观察者模式(Observable)的扩展。
Reactive Streams
他由 Netifix,TypeSafe、Pivatol共同制定,这是 Java 平台上 RxJava、Scala、Akka、Spring、Reactor 等项目的主要维护者所共同发起的。
反应式流(Reactive Streams)是一项倡议,主要用于反应式编程相关的规范以及接口提供标准。
他主要有四个组件构成,分别为发布者、订阅者、订阅、处理器等。除此还有三个接口最为主要:
Publisher(事件发布者)
Subscriber(订阅者)
Subcription(订阅)
Mono 与 Flux Mono 与 Flux 都是事件的发布者,也是 Reactor 的核心类:
Mono
实现了 org.reactivestreams.Publisher 接口,用于返回单个数据
Flux
同样实现了 org.reactivestreams.Publisher 接口,用于返回多个数据。
如果根据 ID 查询某个 User 对象,那么返回的肯定是单个数据,那么就需要使用 Mono<User>
但是如果要获取所有 User,则返回的是多个数据,需要使用 Flux<User>。
需要注意的是 Mono 与 Flux 之间可以 互相转换 ,对一个 Flux 序列进行技术操作,得到的结果是一个 Mono<Long> 对象,将多个 Mono 序列合并在一起,则得到的是 Flux 对象。
本文将会通过 Eureka 来进行服务中心的治理,并实现出 一个 服务中心集群(Eureka Server ……) ,以及 服务提供者集群(Service Provider ……) 还有一个 服务提供者消费者(Service Consumer) 。之后通过 服务中心 内的 Eureka 来进行服务治理,让 服务消费者(Service Consumer) 调用 服务提供者(Service Provider) 来提供服务。
我们首先使用 Eureka 来实现出一个 服务中心(Eureka Server) 集群,之后通过 Eureaka 所 提供的服务注册(Service Register) 功能来注册 服务提供者(Service Provider) ,来实现服务提供者集群。
在此后服务消费者(Service Consumer) 使用 Eureka 所提供的 服务注册功能根据服务提供者(Service Provider) 所注册的名称来调用客户端 Feign 来消费 服务提供者所提供的接口 。
Eureka 是 Netflix 全家桶内项目,他在毕马威在2019年10月所发布的 《颠覆性公司和商业模式》报告中与阿里巴巴分别排名第三,而 Netflix 排名第六位,百度、腾讯分别排名十一和十二位。
虽然 Netlix 排名不如 Alibaba,但他对 Spring cloud 的贡献要早于 Alibaba,因此Netflix 公司所开源的各种项目所组成微服务的核心也不足为奇。
表现层状态转换(Representational State Transfer,REST)由 Roy Thomas Fielding 于 2000年在博士论文中所提出的WWW软件架构风格,目的是放百年在不同软件或程序在网络中相互传递信息,而 Eureka 则是这些项目内基于 REST 服务,主要包含了 服务注册与服务发现功能 。
构建
Spring Assistant
这从 IDEA 2018 就停止更新了
通过 Spring Assistant 根据自身环境进行选择,当进入选择版本和依赖时,我们需要选择 Spring Cloud Discover -> Eureka Server 。
Spring Initializr 或者说我们直接通过 IDEA 自带的 Spring Initializr ,之后选择 Spring Cloud Discovery -> Eureka Server 。当我们构建完成后,肯对会出现一大堆 maven 项目依赖的问题,因此我们需要在 Setting -> Build, Execution, Deployment -> Build Tools -> Maven 下修改 User settings file 所填写的 settings.xml 文件,并写入下述信息,来设置 Alibaba 镜像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?xml version="1.0" encoding="UTF-8" ?> <settings xmlns ="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd" > <mirrors > <mirror > <id > alimaven</id > <name > aliyun maven</name > <url > http://maven.aliyun.com/nexus/content/groups/public/</url > <mirrorOf > central</mirrorOf > </mirror > <mirror > <id > uk</id > <mirrorOf > central</mirrorOf > <name > Human Readable Name for this Mirror.</name > <url > http://uk.maven.org/maven2/</url > </mirror > <mirror > <id > CN</id > <name > OSChina Central</name > <url > http://maven.oschina.net/content/groups/public/</url > <mirrorOf > central</mirrorOf > </mirror > <mirror > <id > nexus</id > <name > internal nexus repository</name > <url > http://repo.maven.apache.org/maven2</url > <mirrorOf > central</mirrorOf > </mirror > </mirrors > </settings >
服务中心 我们所实现的是 服务中心集群 ,和普通的单点服务中心不一样的是,我们可以保障在系统崩溃时,可以维持系统的 更高的可用性 ,所以使用服务中心集群(虽然是服务中心集群,但依然可以通过本文来实现单点服务中心)。
Eureka Server demo 我们可以通过一个标准的 Eureka 模板来进行参考,并通过下述模板来进行修改来实现出我们的 服务中心集群 ,需要注意的是下述code不需要执行(因为你就算执行了实现的也是单点服务中心):
application.properties
src/main/resources/application.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 应用名称 spring.application.name=Discovery Server # Spring 应用端口 server.port=8000 # 节点名称 eureka.instance.hostname=eureka-server.com # 是否注册到 Eureka Server (默认 true) eureka.client.register-with-eureka=false # 是否注册到 Eureka Server 获取注册信息 (默认 true) eureka.client.fetch-registry=false # 设置 Eureka Server 交互地址,查询服务和注册服务进行使用(多个以 “,” 分割) eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
DemoApplication
src/main/java/com/example/demo/DemoApplication.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.example.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer @SpringBootApplication public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
hosts 为了让效果更加的接近真实环境下多台服务器内应用协同服务,所以我们可以通过添加几个 虚拟地址(DNS) 来创建一个只有本地可以访问的 URL,来进行演示。
在 Windows 环境下,我们需要修改 C:\Windows\System32\drivers\etc\hosts 文件,并添加:
而本文作者使用的是 debian 环境,即 Linux 系统下则需要简单的修改 /etc/hosts 文件即可,并添加:
1 2 3 4 127.0.0.1 eureka-server.com 127.0.0.1 eureka-server-1.com 127.0.0.1 eureka-server-2.com 127.0.0.1 eureka-server-3.com
为了验证其是否生效,可以直接通过 ping xxx.com 进行查看。
Jar Run
之后我们分别在 eureka-server-1、eureka-server-2、eureka-server-3 项目下按照流程点击 maven 侧栏下的 clean、package等命令,以此来代替 mvn 执行 mvn clean | mvn package命令。
当的包项目为 jar 后,分别进入 eureka-server-1、2、3 内该 jar 包所在位置,并指定根据之前所在 hosts 文件下设置的DNS名称运行项目(有多少服务集群搞多少个):
1 java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka-server.com
之后进入到http://eureka-server.com:8000/ 即可访问 Eureka 所提供的信息面板中。
eureka-server-1
如果你理解上述的 Eureka Server demo 的配置并结合下述 eureka-server-1~3 所部署的服务中心集群在 Eureka 控制面板和上图基本相似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring.application.name =server-clustered server.port =8001 eureka.instance.hostname =eureka-server-1.com eureka.client.register-with-eureka =true eureka.client.fetch-registry =true eureka.client.serviceUrl.defaultZone =http://eureka-server-2.com:8002/eureka/,http://eureka-server-3.com:8003/eureka/
eureka-server-2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring.application.name =server-clustered server.port =8002 eureka.instance.hostname =eureka-server-2.com eureka.client.register-with-eureka =true eureka.client.fetch-registry =true eureka.client.serviceUrl.defaultZone =http://eureka-server-1.com:8001/eureka/,http://eureka-server-3.com:8003/eureka/
eureka-server-3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 spring.application.name =server-clustered server.port =8003 eureka.instance.hostname =eureka-server-3.com eureka.client.register-with-eureka =true eureka.client.fetch-registry =true eureka.client.serviceUrl.defaultZone =http://eureka-server-1.com:8001/eureka/,http://eureka-server-2.com:8002/eureka/
服务提供者 服务提供者(Service provider),我们可以理解为是通过服务中心进行注册,之后为 服务消费者 来提供服务的。我们可以从服务中心,来控制、管理整个微服务的运行、连接状态等。
Eureka Provide demo DemoApplication 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.example.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication @EnableEurekaClient public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
我们通过根据之前一样的项目依赖,选择 Eureka Server 即可,进入项目后在启动类中加入 @EnableEurekaClient 注解,来启用其 Eureka 注册和发现功能。
application.properties 1 2 3 4 spring.application.name =server-provide server.port =8000 provider.name =provider0 eureka.client.serviceUrl.defaultZone =http://eureka-server-3.com:8003/eureka/,http://eureka-server-2.com:8002/eureka/,http://eureka-server-1.com:8001/eureka/
之后我们来设置名称以及控制器需要获取到的信息进行配置,之后通过 eureka.client.serviceUrl.defaultZone来加入服务中心。
HelloController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.example.demo.controller;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class HelloController { @Value("${provider.name}") private String name; @Value("${server.port}") private String port; @RequestMapping("/home") public String hello () { String string = "provider:" + name + "port:" + port; return string; } }
控制器的主要用途是来提供接口让 服务消费者 进行获取的,通过application.properties配置文件内相应内容并根据其@Vlue注解以及 @RestController处理HTTP请求,如果你不觉得麻烦可以用下述两个注解代替:
1 2 @Controller @ResponseBody
Actuator info Eureka 在其信息面板中 Instances currently registered with Eureka (目前在Eureka 注册的实例)一览中的 Status(状态)下。为开发者提供了一个便捷查询的入口,即 UP (1) - 192.168.43.65:server-provide:8000 => http://192.168.43.65:8000/actuator/info 这是服务提供者的当前在服务中心的链接状态。
当我们点击跳转时,是可以根据其 pom.xml 以及 application.properties 文件内的配置信息单独写入其 JSON 数据的,虽然 Eureka 默认的为 {}。
修改添加后结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 { "app" : { "name" : "eureka-cloud-8000" } , "company" : { "name" : "jiangxue.org.cn" } , "build" : { "application" : "server-provide" , "port" : "8000" } }
我们需要修改其 application.properties 即 Spring 的全局配置文件:
1 2 3 4 5 6 7 8 9 10 spring.application.name =server-provide server.port =8000 provider.name =localhost info.app.name =eureka-cloud-8000 info.company.name =jiangxue.org.cn info.build.application =${spring.application.name} info.build.port =${server.port} eureka.client.serviceUrl.defaultZone =http://eureka-server-3.com:8003/eureka/
之后我们还需要修改 pom.xml 配置文件信息,在 <build></build>元素中的 <plugins> 上方,加入 <finalName>信息:
1 2 3 4 5 6 7 <finalName > ${project.artifactId}</finalName > <resources > <resource > <directory > src/main/resources</directory > <filtering > true</filtering > </resource > </resources >
之后我们可以选择直接在 IDEA 里面运行或通过 jar 包方式运行(虽然是本地环境但还是要整出几十台服务器协同的效果 )
Jar run 当全部运行完之后,我们将会有三台服务中心集群,以及两台服务提供集群,通过访问我们服务提供集群的控制器即 http://192.168.43.65:8010/hey 都可以正确访问控制器内容时,即可以正常提供服务。
eureka-provide-1 1 2 3 4 5 6 7 8 9 10 spring.application.name =server-provide server.port =8010 provider.name =localhost info.app.name =eureka-cloud-${server.port} info.company.name =jiangxue.org.cn info.build.application =${spring.application.name} info.build.port =${server.port} eureka.client.serviceUrl.defaultZone =http://eureka-server-3.com:8003/eureka/,http://eureka-server-2.com:8002/eureka/,http://eureka-server-1.com:8001/eureka/
eureka-provide-2 1 2 3 4 5 6 7 8 9 10 spring.application.name =server-provide server.port =8011 provider.name =localhost info.app.name =eureka-cloud-${server.port} info.company.name =jiangxue.org.cn info.build.application =${spring.application.name} info.build.port =${server.port} eureka.client.serviceUrl.defaultZone =http://eureka-server-3.com:8003/eureka/,http://eureka-server-2.com:8002/eureka/,http://eureka-server-1.com:8001/eureka/
服务消费者
服务消费者主要通过使用 Feign 来进行实现,Feign 由 Retrofit、JAXRS-2.0 和 WebSocet 所启发,主要用于 Java 到 HTPP 客户端绑定器。因此我们将会通过引入 Feign 依赖来实现服务消费者。
在新建项目时,我们需要注意,需要在 选择依存关系内选择 Spring Web、Eureka Discovery Client。当然之前的服务提供者也可以这样选择,但为了更加方便我们通过选择 Eureka Server 单个依存关系也可以实现。
pom.xml
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
添加 spring-cloud-starter-openfeign maven 依赖后可能会报错,此时我们只需要通过点击 Reload ALL Maven Projects 按钮来 重新加载所有 Maven 项目 。
application.properties 1 2 3 4 spring.application.name =service-consumer server.port =9000 eureka.client.register-with-eureka =false eureka.client.serviceUrl.defaultZone =http://eureka-server-3.com:8003/eureka/,http://eureka-server-2.com:8002/eureka/,http://eureka-server-1.com:8001/eureka/
DemoApplication 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.example.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
通过使用 @EnableDiscoveryClients来启动客户端的服务注册和发现功能,并以 @enableFeignClients 注解来实现远程服务调用。
MyFeignClient interface 1 2 3 4 5 6 7 8 9 10 package com.example.demo;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.RequestMapping;@FeignClient(name = "server-provide") public interface MyFeignClient { @RequestMapping(value = "/hey") public String hello () ; }
需要注意的是,我们在此处的方法必须与服务提供者(即服务提供者 Controller)的方法一致
之后我们需要定义一个接口,并使用 @FeignClient 注解来通过 name 值在服务中心进行寻找并连接。在这里我们的服务提供者集群在服务中心所注册的名称是 server-provide 。
ConsumerController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.example.demo.controller;import com.example.demo.MyFeignClient;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class ConsumerController { @Autowired MyFeignClient myFeignClient; @RequestMapping("/hey") public String index () { return myFeignClient.hello(); } }
Run 最终我们将服务中心集群、服务提供者集群开起来,以及在 IDEA 中启动服务消费者。之后通过访问 http://localhost:9000/hey 刷新两次看看。
如果端口值从 8010~8011 之间不断调换。那么我们通过 Eureka 来进行服务治理所实现的微服务架构完成。
Spring cloud 是一个基于 Spring boot 的一个快速开发的微服务框架,有一句话是 小项目用 spring boot,大项目用 spring cloud ,他主要九个非常重要且显著的特征。
REST 在介绍Spring cloud 各类特性和主要项目以及开源生态之前,我们需要了解表现层状态转换(REST,Representational State Transfer),由 Roy T. Fielding博士于 2000年在博士论文中提出。其提供出来的主要是一种互联网软件 设计风格 ,目的是方便软件或程序在互联网中互相传递信息。
在 Roy 的博士论文中我们可以看到其清楚的定义了下列六种知道原则,也就是我们将满足 REST 六种指导原则的风格称之为 RESTful:
客户端-服务器(Client-Server)
无状态(Stateless)
缓存 (Cache)
统一接口(Uniform Interface)
分层系统(Layered System)
按需编码(Code On Demand)
https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
在这之后根据这篇论文进行了详细和系统的完善,使得更加具有了可读性:https://restfulapi.net/
客户端-服务器(Client-Server) 在REST中,被第一个添加的则是客户端和服务器之间的架构风格约束,而他的关注点是客户端-服务器约束背后的原则。通过将用户界面与存数据存储问题进行分开, 这提高了用户界面跨平台的移植性以及简化了服务器组建,提高了扩展性。
无状态(Stateless) 根据客户端和-服务器通信中,我们假设通信本质是无状态(Stateless)的,即客户端到服务器的每个请求都不会存储上下文(会话完全保留在客户机上)。
而这些约束改善了 可见性、可靠性、可扩展性等,其中最为明显的则是可扩展性。
This constraint induces the properties of visibility, reliability, and scalability. Visibility is improved because a monitoring system does not have to look beyond a single request datum in order to determine the full nature of the request. Reliability is improved because it eases the task of recovering from partial failures. Scalability is improved because not having to store state between requests allows the server component to quickly free resources, and further simplifies implementation because the server doesn’t have to manage resource usage across requests.
他的缺点是可能会增加一系列请求中发送的重复数据,从而降低网络性能等方面。
缓存 (Cache) 缓存存在的意义则是在 REST 中提高网络效率,缓存主要被约束为请求的响应中内的数据隐式(或显示)标记为 可缓存或不可缓存 。
如果是可缓存的,则在客户端缓存有权为以后同样效果的请求重新应用该响应数据。
在 REST 架构风格中与其他类似架构风格有明显区分的就是组件之间的统一接口,他简化了整个系统架构并提高了交互的可见性。
为了实现和获得统一接口,需要多个架构约束来指导组件的行为,而 REST 则是由四个接口约束定义:
资源识别 (identification of resources) 请求中包含了各种独立资源的标识,如 Url等。除此之外还可以将自身的数据库信息以 HTML、XML、JSON等方式发送给客户端。
操纵资源 ( manipulation of resources through representations) 当客户端拥有资源标识后,就可以根据足够的信息删除这个资源
描述信息 (self-descriptive messages) 每个消息都包含了可以描述来如何处理这个信息的方式,如媒体类型(media type)以及最后修改时间 (modified time)
分层系统(Layered System) 分层系统的样式允许通过约束组件行为为使得每个组件 ”不能“ 看到与其直接交互的直接层之外的其他层,将架构由分层组成。2
需要注意的是很多文档都将 用超媒体驱动应用状态,“hypermedia as the engine of application state” 夹在里面,实际上他的意思为 ”超媒体 作为 应用程序状态引擎“
按需编码(Code On Demand) 按照 Roy 论文来理解,按需编码在 REST 其实是一个可选约束,并提供了一个非常不错的例子:
例如,如果已知组织内的所有客户端软件都支持 Java,则可以构建该组织内的服务,以便通过下载的Java 类来获得增强功能的好处。
但问题在于组织的防火墙可能会组织外部资源传输到 Java 小程序,对于 Web 的其他部分,某些客户端可能不支持该代码。
而按需编码所提供的 可选约束就允许我们设计一个在一般情况下支持所需行为的架构。
当然这是一种理解,而第二种理解就偏向与实际,大多数形式下返回的资源都以 XML或JSON表示,但是需要的时候,我们可以自由的返回可执行代码。
来支持自己应用程序的部分,根据按需编码的约束这是允许的,因为这属于 ”可选约束“。
应用与 Web 服务
我们可以根据 REST 的设计风格为 Web API 进行设计,符合其统一接口约束即可以被称之为 ”RESTful API“,他主要就是以统一接口的三个约束原则而定义的。
资源识别 我们可以理解为 资源地址的 URL,如 http://example.com/resources传输的资源(描述信息) Web服务接受与返回的媒体类型,如 JSON、XML等 而对 操纵资源 则是所支持的一系列请求方法,如 POST、GET、PUT、DELETE 等。
如果你说你要实现一个 REST设计风格的 RESTful API,则可以参照下述例子:
1 2 3 4 5 6 7 8 1. 所有文章 GET http://www.example.com/list 2. 阅读某篇文章 GET http://www.example.com/list/{id} 3. 发布文章 POST http://www.example.com/list/release
推荐阅读 https://restfulapi.net/resource-naming/
特征
Spring cloud 很早就提供了下述特征的实现,但由于早期相关代码和介绍存在与 Spring cloud 的子项目 Spring Cloud Cluster 中,到最后被迁移至 Spring Integration 中。
这也是为什么 Spring cloud 在官方文档中说了自己的 xx 特征,但又很难找到相关文档的原因,这是因为人家早就把代码和文档迁移到了 Spring Integration 中了。
分布式\版本化管理(Distributed/versioned configuration)
Spring cloud Config 与 Spring cloud Bus 经常一起搭配使用,这可以让他无需重新启动服务可以刷新配置文件的信息。
通过 Spring Cloud Config 来统一管理服务配置,可以将所有服务的配置文件 放置在本地仓库或者远程仓库中 。让Spring cloud config 配置中心来负责读取仓库的配置文件,而客户端(服务)从配置中心读取配置。
服务注册和发现(Service registration and discovery) 服务治理组件主要由 Eurake、Consul 等进行,他在此相当于交易的信息员,可以 发现 服务提供者(Service Provider),还可以将自己注册到 服务中心(Eureka Server)中
路由(Routing) 通过智能路由网管组件 Zuul、Spring Cloud Gateway 来实现智能路由和请求过滤功能。内部服务接口通过网关统一对外暴露,来避免内部服务敏感信息信息在未经授权情况下对外暴露。
服务到服务的通信(Service to service calls)
Service to service calls 在官方文档中并没有进行概述,所以本文仅是个人观点。
Spring cloud 中服务到服务的通信组要通过 Eureka 组件来调用微服务的顺序。
负载均衡(Load balancing) 负载均衡主要可以通过 Feign、Ribbon来实现负载均衡。
断路器(Circuit Breakers) 断路器使用服务容错组件 Hystrix、Resilience4j来控制服务的API接口荣段,以实现故障转移、服务限流、服务降级等功能。防止微服务系统发生 雪崩效应 ,可以使用 Hystrix Dashboard 组件监控 单个融断状态 ,使用 Hystrix Turbine 组件监控 多个服务的融断状态 。
全局锁(Global locks) 也许可以通过 https://github.com/spring-cloud-samples/locks#readme 来进行了解 Spring cloud 全局锁的作用。
全局锁(Global locks)是对某部分进行加锁,当你只需要将某个部分只处于 只读状态 时,可以使用,之后这些部分进行更新、删除等将会造成阻塞。
领导选举和集群状态(Leadership election and cluster state) 领导选举和集群状态(Leadership election and cluster state)由 Spring Cloud Cluster 所提供分布式系统集群功能,以及包含了领导选举、集群状态以及全局锁。
这些都被 Spring Integration 中的代码取代。
分布式消息传递(Distributed messaging) 通过消息总线(Bus)组件,数据流操作组件可以 Redis、RabbitMQ、Kafka等进行封装,来实现消息的接收和发送 。
主要项目
下述有些并不完全是 Spring cloud 自己的,而是通过根据 Spring boot 风格对这些组件进行封装,屏蔽了复杂的配置和原理,最终给开发人员提供了一个简单易懂和易部署以及平滑的的微服务框架。
Spring Cloud Config Spring cloud config 是 spring cloud 的配置管理工具,通过此项目可以将配置信息放到远程服务器中,从而集中管理集群配置。除此之外还可支持:本地存储、git、subversion 三种方式,这些资源还可直接映射到 Spring 环境 。
Spring Cloud Netflix 各种 Netifix OSS组件,由 Eureka,Hystrix ,Zuul,Archaius 等集成。
Spring Coud Bus 将 服务实例与分布式消息传递 连接在一起的 事件总线 ,可以在集群中传播状态更改(如配置更改),还可与 Spring Cloud Config 联合实现热部署。
Spring Cloud Cloudfoundry Spring cloud 集成了 Foundry ,他是由 VMware 第一个业界推出的开源 Paas(Platform as a Service,平台即服务)的云平台。支持多种框架、语言、运行环境、平台及应用服务,基于这些可让开发人员快速在几秒中能进行部署和扩展,提供了服务发现,还可以轻松的实现 SSO、OAuth2保护资源。
Spring Cloud Open Service Broker 为实现构建 Open Service Broker API(开放服务器代理接口),可以为开发人员能够为开发人员对平台(如Spring Foundry、Kubernetes)中运行的应用提供服务。
Spring Cloud Cluster 提供了分布式系统集群所需要的基础功能支持,如选举、集群状态一致性、全局锁、Tokens 等常见状态模式的抽象和实现。
Spring Cloud Consul
Consul 是由 HashiCorp 公司所使用 Go 所开发的服务治理项目
Spring cloud Consul 主要封装了 Consul 来实现服务治理、健康检查、key-Values 存储、数据中心集群的呢个。
Spring Cloud Security 提供对 Zuul 代理中复杂均衡的 OAuth2、REST 客户端和身份验证头中继的支持。
Spring Cloud Sleuth Spring cloud sleuth 为 spring cloud 服务之间的调用提供了链路追踪,封装了 Dapper、Log-based 追踪(ELK)、Zipkin 和 HTrace 操作。
Spring Cloud Data Flow 针对现代运行时可组合的微服务应用程序的原生编排服务,易用于 DSL、拖放 GUI 和 restAPI 一起简化了基于微服务的数据管道整体编排。即提供了统一变成模型和托管服务,用于开发和执行 ETL、一种用于处理大规模数据的模式。
Spring Cloud Stream 一个轻量级事件驱动的微服务框架,用于快速构建可以链接到外部系统的应用程序。基于 Spring boot 创建,通过事件触发任务,封装了 Redis、Rabbit、Kafka等发送和接收消息,用来创建工业级的 Spring 应用。
Spring Cloud Stream Application Spring cloud Stream 应用程序是现成的 Spring 引导应用程序,使用 spring cloud stream 中的 binder 抽象提供与外部中间件系统(ApacheKafka、RabbitMQ……)的集成。
Spring Cloud Task 一个短暂的微服务框架,主要用于进行微服务的任务管理和调度等。
Spring Cloud Task App Starters Spring cloud 任务应用启动程序是 spring boot 应用程序,可以是任何进程,包括不会永远运行的 Spring 批处理作业,他们可以在有限的数据处理周期后 结束或停止
Spring Cloud Zookeeper Spring cloud 集成了 Zookeeper,他是 Hadoop 和 Hbase 的重要组件,提供了配置维护、域名服务、分布式同步、组服务、服务治理等功能 。
Spring Cloud Connectors 使各种平台上的PaaS(Platform as a Service,平台即服务)应用程序能够轻松地连接到后端服务。也就是说可以通过他来链接到服务和从云平台获取操作的过程,拥有很强的扩展性,可用于构建云平台。
Spring Cloud Starters 一个 Spring boot 风格的启动应用项目,可以简化 Spring Cloud 使用者的依赖管理关系(在 Angel.SR2之后停止为一个项目,并与其他项目合并),为 Spring cloud 提供了一个 开箱即用 的依赖管理。
Spring Cloud CLI Spring boot CLI 插件,用于在 Groovy 中快速创建 SPring Clound 组件应用程序,即通过 Spring cloud CLI 可以 快速构建云组件 。
Spring Clound Contract Spring cloud 通过 CDC (Customer Driven Contracts,客户驱动合同)开发,基于JVM(Java 虚拟机,Java virtual machine)的应用提供了支持,为TDD(测试驱动开发)提供一种新的基于接口的测试方法。
Spring Cloud Gateway Spring cloud Gateway 是一个基于Project Reactor 项目,为 Spring cloud 提供了一种简单和有效的方式来对 为服务的 API 进行路由 。
Spring Cloud OpenFeign Spring cloud OpenFeign 由于封装声明试 Web Service 客户端的 Feign,可以让编写 Web Service client 变得容易。支持 Feign 注解和 JAX-RS 注解,支持热插拔的编码器和解码器,并支持 Feign 在 Spring MVC 中使用,由于整合了 Ribbon 和 Eureka 可以在使用 Feighn 时提供负载均衡。
Spring Cloud Pipelines Spring Cloud Pipelines 提供了一个可靠的部署管道,其中包含了一些步骤,来确保应用程序可以零停机的方式部署,并且在出现问题时可以轻松的回滚。
Spring Cloud Function Spring Cloud Function 提供了一通用的模型,在类似与 AWS Lambda 这种 FaaS(Function as a Service,函数驱动服务)平台上部署函数软件。
Netfix 项目 除了 Spring cloud 项目本身,其他厂商也对其微服务等开源产品作出巨大的公司,如 Netfix 和 Alibaba 等公司,但 Netfix 要早于 Alibaba 对微服务及其相关产品或项目的贡献。
Spring Cloud 集成了 Netfix 公司所开源的各种项目和组建其中主要以 Eureka、Hystrix、Zuul、Archaius 作为为代表项目。
Eureka Eureka 是一个基于 REST 服务的服务治理组件,主要包含了服务注册和服务发现两个最为核心的功能。
Hystrix Spring cloud 可以整合 Hystrix 来对微服务进行容错管理,即使将服务做成集群也不 100% 保证安全可用。为了防止服务与服务之间的依赖性,单个服务出现故障 从而导致整个为服务系统崩溃,则需要进行容错管理。
Zuul Zuul 为 Spring cloud 提供了路由和过滤的功能,他是一种服务的统一入口,除此还提供了动态路由、监控、授权、安全、调度等功能。
Archaius Spring cloud 整合了 Archaius 来进行 管理和配置 API,Archaius 提供了动态类型化属性、线程安全配置操作,轮询框架,回调机制等功能。
Archaius 可以实现出动态获取配置,如默认每隔60s从配置源读取一次内容,这样在修改配置文件后不需要重新启动服务,即可使得修改的内容生效
Alibaba 项目 在 2019年10月所发布的《颠覆性公司和商业模式》报告中,Alibaba 以 15% 成功位居第三位,百度、腾讯等均位列第十和第十一位。
Sentinel Sentinel 是一个流控制组件,可以将流量作为切入点,从流量控制、熔断降级、系统负载保护等多个阶段保证服务的稳定性。
Nacos Nacos 是一个服务治理的项目,与 Eureka、Consul 一样,提供了动态服务发现、配置管理和服务管理平台。
RocketMQ RocketMQ 是基于分布式高可用集群提供的低延迟、高可靠的消息发布/订阅服务,类似与 RabbitMQ 的一种消息中间件。
Dubbo Dubbo 是一个高性能、轻量级的开源 Java 服务框架,提供了面向接口代理的高性能RPC(Romote Procedure Call,远程过程调用),智能容错、负载均衡、服务自动注册和发现、运行期流量调整、可视化服务治理与运维等。
Seata Seata是一款分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
Alibaba Cloud ACM Alibaba Cloud ACM 是一个应用配置中心,用于分布式架构环境中对 应用配置进行集中的管理和推送。
Alibaba Cloud SchedulerX
Cron 是一个基于时间的任务管理系统,可通过 cron 在固定时间、日期、间隔的情况下运行任务。
除此之外他的表达式特别的不具有可读性:
1 2 3 4 5 6 7 8 # 文件格式說明 # ┌──分鐘(0 - 59) # │ ┌──小時(0 - 23) # │ │ ┌──日(1 - 31) # │ │ │ ┌─月(1 - 12) # │ │ │ │ ┌─星期(0 - 6,表示从周日到周六) # │ │ │ │ │ # * * * * * 被執行的命令
如 1-6 * * * * echo hello world 及每小时的 1~6分钟内,都会都会打印 Hello,world符号。
更多可参考 https://zh.wikipedia.org/wiki/Cron
基于 Cron 表达式的任务调度服务,可以提供秒级、精准、高可靠、高可用的定时任务调度功能,可以支持 cron、fixed_rate、second_delay 定时调度,能精确到秒级别。
架构对比 Spring cloud
Dubbo
Dubbo 比 Spring cloud 少了很多配套项目(因为不是 spring 全家桶内的) ,所以 Dubbo 专注与 RPC 领域,未来将会成为微服务生态体系中一个重要的 组件 ,而不是 微服务的全面解决方案 。
lstio lstio 主要使用网格服务 Service Mesh 架构来实现,因此在 lstio 中,服务之间的通信主要通过 Envoy 代理来使用 HTTP/1.1、HTTP2、gRPC、TCP 来进行,Pilot、Mixer、Citadel 来组成的控制平台。
pilot 为 Envoy 提供服务发现、流量管理、智能路由、错误处理(含超时、重试、融断)的功能,因此用户可以通过 Pilot 的 API 管理网络相关的资源对象。Pilot 会根据用户的配置和服务的信息将网络流量管理信息分发到各个 Envoy 中。
Mixer 主要为整个 集群执行访问控制、跨服务网格使用策略(Policy)、管理(Rate Limit、Quota)bing souji cong Envoy 代理和其他服务自动探测到的数据,Mixer 包含了一个非常灵活的模型插件。可以与各种主机环境和后端基础架构进行交互,他是一个独立于平台的组件 。
Citadel 提供了服务之间的认证和证书管理,能够自动升级到 TLS 协议
Sidecar 在原有的客户端和服务器端之间添加了一个代理程序。
Envoy 提供了服务发现,负载均衡和路由表动态更新的 API这些分别解决了 lstio 和 Envoy 的问题,Proxy 作为 SideCar 会和控制中心动心,来获取需要的服务之间的信息,以及回报服务调用的 Metrics(指标) 数据
Sprng cloud 优缺点
微服务优点 模块化开发 以上图作为参考,我们可以首先得知微服务是以 分布式 作为必要技术。有多个微小服务所组成的一个应用,由于他将一个功能划分为一个很小的服务,可以将一个微服务交给不同的开发人员或团队进行开发。
也就是说不同的开发团队可以同时完成多个属于自己任务范围内的 “微服务”,进行项目开发,因此可以 易于开发和维护 ,而不是向单一应用一样一个项目几百个文件夹,都涉及不同的功能和服务。
代码量 与单个服务相比,微服务由于功能只针对一个功能所进行提供服务。因此代码量不会像正常的单一应用较多,所以项目的启动速度是比单一应用较快的。(但这也不算是优点,因为启动时还很麻烦,一个一个启动多个微服务)
易于更新迭代 如果只是小型的项目迭代的话,那么微服务由于是多个服务应用组成的一个庞大服务,所以我们可以根据目标 功能对应的微服务 模块直接进行修改后上传。不需要向单体应用一样直接暂停项目并进行修改后发布。
技术栈 由于微服务是以业务单一性为原则的开发模式,因此他的开发也不会是统一一种语言,他可以是多个,且不冲突。
解耦 解耦是在设计模式中较为常见的名词之一,各类书籍中经常出现但就是不给你解释。解耦通俗来说就是两个东西 原来互相影响 ,现在然他们 分别发展 ,但不是完全的分别发展,而是在原有的基础上降低两者互相的依赖关系。其核心思想是最小职责,每个地方做一件事情。
通常情况下 松耦合 也可以被称之为 解藕 ,瞬间让此变得高端大气上档次。因此除了松耦合外,
他还有一个反意为 紧耦合 ,指的是两个模块之间相互影响且关系紧密,存在着互相关系调用。
具有弹性 由于微服务非常可以简单的实现 服务集群 ,通过 服务治理组件 来实现服务的治理,通常都是两三个集群一起工作。一台蹦了还有另一台,另一台蹦了又换下一台的局面,可以保证服务的正常运行。除此之外还具有水平扩展独立以及扩展性高等优点。
微服务缺点 分布式复杂性 对微服务来说分布式架构是一个非常必要的技术,但因为分布式依赖服务较多,一旦其中某个 依赖服务 以及系统的容错能力都会带来挑战。
分布式事务问题 所谓分布式事务问题可以分为 可靠事件模式(Reliable Event Mode)、补偿模式(Confirm Cancel)、TCC(Try Confirm Cancel) 三种,用于解决分布式的 事务一致性问题 。
首先我们需要知道为什么要有服务一致性,假设你在 Wechat 中将 100RMB 转账给 B,那么 B 的 Wechat 钱包就会多 100RMB。但如果这个过程中 Wechat 服务崩了,那你将会少 100RMB,而B的钱包数额不变。
可靠事件模式(Reliable Event Mode) 他也被称之为 可靠事件记录协议(RELP,Reliable Event Logging Protocol),是一种用于计算机网络中记录 计算机数据的网络协议 。
他可以提供消息的可靠传递,常用与不能让数据丢失的环境,如转账。在这里我们需要认清 可靠事件记录协议和可靠事件模式 ,在通常的情况下,可靠事件模式是可靠事件记录协议的实现
一般情况下事件操作可以被分为三种可能,分别为:
操作成功,投递事件
操作失败,投递事件失败
操作成功,投递失败,抛出异常 | 回滚
本地事件表 因此可靠事件模式主要通过 本地事件表和外部事件表 两者来就进行可靠事件投递。
本地事件表通过将事件和业务数据保存在同一个数据库中,来以一个额外的 事件恢复服务 来恢复事件。
原子性指一个操作不可中断,要么全部成功,要么全部失败的原则即称之为 原子性。
这些都将会由本地事务来保证更新业务的发布事件中的不可中断,要么全部成功,要么全部失败,即原子性。
外部事件表 外部事件表是针对本地事件表出现的问题,单个提出外部事件表的方法。将 事件持久话到外部事件系统服务中 ,事件系统需要提供一个实时的事件服务以用于接收 为服务发布的事件 。与此同时还需要提供一个恢复服务来确定和恢复事件。
事物提交前,业务系统会通过实时事件服务向系统请求发送事件,事件系统 只记录事件但并不发送 。
之后业务服务提交后,通过实时事件服务向 =>事件系统 确认发布,事件得到确认后 事件系统 进行发布到消息代理。
当业务系统回滚时,通过实时事件向时间系统去取消 事件。假设事件系统的事件恢复服务定期找到未确定发送事件向业务服务查询状态时,根据业务服务返回的状态来决定事件是否需要发布。
补偿模式(Confirm Cancel) 补偿模式主要有 业务异常和技术异常 两个概念,是自身业务所造成的。而技术异常则是非业务逻辑产生的一系列问题,如网络异常等。
因此补偿模式需要使用一个 额外的协调服务来协调微服务保证一致性 ,这时候协调服务通过按照顺序调用服务,如果某个服务出现异常则直接取消之前所有已经成功调用的服务(所以他也具有原子性)。
也就是说当数据文件为空时提交,那么之前所填写的端口信息以及提交数据都将会被取消,而这所取消的就是一个 补偿
TCC 模式(Try Confirm Cancel) TCC 模式由 Try、Confirm、Cancel 三个接口,一个完整的 TCC应用由 主业务、若干个业务服务 组成和发起,并最后通过主业务完成整个活动。
TCC 三个接口分别意思为:
try:完成所有业务检查,预留必须存在的业务资源
Confirm:执行业务,不做任何业务的检查和分析,只是用 try 阶段所预留的业务资源,Confirm 操作满足幂等性。
幂等性,就是说一个系统,在同样情况下一次请求和 重复 多次请求对资源造成一致的就是幂等性。
Cancel 释放 try 阶段预留资源(满足幂等性?)
主业务服务调用所有的业务的 try 操作,之后在活动管理器中登记所有业务服务,当所有业务服务的 try 操作都调用成够后或从某个业务服务的 try 操作失败进入 confirm 阶段。
活动管理器根据 try 执行结果来执行 confirm or cencel 操作,如果 try 操作所有都成功则从活动管理器中调用所有 confirm 操作,否则将调用 cancel 来释放 try 预留资源。
分布式同步调用 分布式的同步调用及主要是在不确定环境中无法保证所依赖服务,但可以保证自己可以正常提供服务的情况。
SEDA 阶段式服务器模型(SEDA,Staged event-driven architencure)他是一个软件架构模型,可以将复杂的事件驱动的应用分解为一系列通过队列链接的阶段(Stage)。
在上述中 “分布式的同步调用及主要是在不确定环境中无法保证所依赖服务,但可以保证自己可以正常提供服务的情况。”就可以通过使用 SEDA 进行解决。
拥用一句话概括就是 将请求出来过程划分为多个阶段,不同资源消耗阶段,并使用不同数量的线程进行处理 。
接口调整 由于微服务数量可能会有很多,因此如果需要调整接口时则需要话费很大的时间。除了调整外还需要写其接口文档,来进行管理。
而接口文档可以从中心点引入很多下游问题,如接口文档的管理和更新等。
Spring cloud 微服务设计原则 AKF 可扩展性原则 Spring cloud 的单一设计原则可以让微服务更加优雅。在这个原则之下所开发的微服务项目通常 只关注单一的功能,有限的业务逻辑 最终完成一个有限的业务逻辑。
就比如 AKF 拆分原则即 扩展立方体(Scalability Cube)或 AKF Scale Cube ,需要注意的是 AKF 并不是什么单词缩写,而是一个公司的名称。
我们可以直接概括下这三个轴的主要左右,其中Y轴是功能划分以及基于不同业务之间的划分。而 X轴则是水平扩展,北其名曰水平复制,来砸钱解决问题(加服务器)。最后就是Z轴,Z轴主要左右就是数据区分,来关注服务和数据的优先级进行划分。
Y Axis Y 轴会会根据基于不同业务之间的整体应用拆分为多个服务,每个服务主要会实现一个单个功能。之后以 Eureka 服务治理为例,通过服务中心来进行治理,最终架构如上所示。
X Axis X 轴主要是水平扩展,通过复制服务与数据来解决可用性及微服务集群的问题,也就是说将微服务运行多个实例(集群),来通过集群负载均衡的模式来提升服务整体的可用性。
Z Axis Z轴主要就是区分数据,通常Z轴扩展有两种方案分别为 单元化架构以及数据分区 两种,以大数据时代的今天可能两者都会被使用,
单元化架构 单元化架构的选择主要根据服务的类型以及对象单元是否符合来进行区分,如客户端对服务端节点的选择都将是单元自成一体。
数据分区 数据区分也叫分区(Shard),他其实就是区分数据的,比如说我们可以根据年龄、注册时间来区分数据。通常会包含一下几个数据划分的方式,这也是最为常见的:
数据类型
数据范围
数据热度
数据分区
这四种最为常见的我们可以在很多大型应用中看到,如 bliblibli 弹幕网,因为咱们也没有看过他们的源代码,但是我们可以随便以肉眼可见的观察下,就会发现他们做了一个非常细致的数据区分。
服务自治 说到 Spring cloud,我们从第三节到现在就一直说他多么多么服务自治,看上去非常高端且难懂。实际上服务自治简单来讲就是 每个微服务都具有独立性质,及运行、开发、测试、构建、部署 且包含数据库都可独立运行的服务。而过多或依赖其他微服务,并与其高度解耦,除此之外我们将此称之为 服务自治。
服务自治也是名副其实的微服务设计原则,因为他可以打来很多的好处,如团队协作、技术栈、后续迭代、持续集成等多种优势。
轻量级通信 轻量级接口通信主要对其要求就是体量较轻,可以跨语言和跨平台之间的通信,以及可以不受技术限制,实现不同通信协议之间的通信,如:REST、RPC、AMQP、STOMP、MQTT等。
接口明确原则 而接口明确原则,则是为了确保之后微服务之间不断的完善,导致因为某个接口的变化而其他服务集群需要进行调整,为了避免此类令人烦恼的事情再次发生,则需要让这些接口在设计开发的时候更加的具有通用性。
容错性设计原则 容错性设计原则也被称之为弹性设计,即依赖服务宕机、网络或硬件出现问题时可以暂停使用服务,切换下一个资源,在后续我们将会以集群的方式解决这个问题。除此之外也有很多成熟可靠的方案进行选择,如 Hystrix 延迟和容错库。
自动化原则 自动化测试 自动化原则主要会分为自动化测试、自动化监控和自动化部署等三种非常主要的自动化方案。其中自动化测试可以减轻测试负担,可以保障微服务的模块在高流量的情况下可访问。
自动化监控 而自动化监控则是监控微服务状态,发现问题以及错误计数等来便于解决和发现问题,也可以根据监控状态来调整负载,来保证服务的稳定性和高可用等。在 Spring cloud 项目中,提供了一个为服务可用性监控 Spring Boot Admin 组件。
自动化部署 自动化部署指的是将代码自动部署到生产环境中,来减少项目发布的时间,目前可以通过 Jenkins + Fastlane 来搭配使用。
Spring cloud 业务拆分与管理模式 Domain Driver Design(DDD) 在微服务设计中,我们通常会存在一种不知道如何进行划分的问题,到底是根据功能、组织结构进行划分还是其他方式。
而为了解决这种问题,领域驱动设计(Domain Driver Design)提供了一种划分方法论,说白了就是可以解决微服务划分时遇到的问题。
首先,DDD 主要结构为四层,分别为 基础设施层 (Infrastructure)、领域层 (Domain)、应用层 (Application)、用户界面层 (Interfaces)(也被称为表示层、展现层、接口层)
领域 首先,我们需要通过 DDD 来指导我们的划分方法,而不是让他主导我们,从而让我们更加困惑。因此我们需要知道一件事情,就是 分清问题和方案 ,问题是需要解决的,而方案是用来解决问题的。
当谈论领域的时候(含子域、通用子域)实际上都是在讨论问题域 ,而问题域就是 我们需要解决什么问题
而到 限界上下文、聚合、实体、仓库 这些实际上是在 谈论解决方案 来解决领域时提出的问题。
在搜索领域中,我们可以从其主页面中来看出遇到的一些非常典型的问题:
如何保持知名度
如何让搜素变得更精准
如何找到一个合适的度进行营销
而这些问题的解决问题的方案可以是:
与浏览器之间进行绑定
加大数据爬取和关键词的定位
与搜索结果中布局一致
设计 在设计中,主要解决的就是让领域专家和技术专家深入合作,并建立一个软件问题提模型,而这个模型主要可以解决:
可以表达领域概念和业务流程
每个领域都有自己的解决方案
这就是设计在 DDD 主要的作用,DDD 是一种基于领域为中心来驱动的软件的开发思想,可以通过此来帮助我们解决复杂的业务问题,因此需要对每个 领域提出问题来对应一个领域模型,来解决问题 。
模型 在 DDD 中,主要将模型划分为四层,分别为基础层(Infrastructure)、领域层(Domain)、应用层(Application)、用户界面层(Interfaces),这些被简称为 IDAI。
通俗来讲模型就是一个解决方案空间,是一个为了准确定义需要问题而构造的一个抽象模型。而模型主要可以解决类似与业务领域问题,以及分析满足系统的功能性要求即 IDAI。
专家 通过领域提出的问题我们可以得知,这些问题的提出基本上不是开发人员非常擅长的,所以此时需要 领域专家 来提出问题,由 技术专家 解决问题,这也是 DDD 成功实施的主要条件。
事务管理模式 事物管理模式(ACID,Atomicity Consistency Isolation Durability)要比 DDD 更好理解,他在上一章就以一小部分的形式出现了,当时我们所介绍的是原子性(Atomicity)。
一个逻辑工作单元需要成为事物,需要满足 ACID属性,分别为:
A:原子性(Atomicity),事物操作中,要么全做,要么都不做
C:一致性(Consistency),事务执行完后,从同样状态,转向另一个同样的状态(可以理解为要么是全部执行中,要么是全部暂停中)
I :隔离性(Isolation),事务执行时不被打扰
D:持久性(Durability),事务提交后,对数据的改变就是永久性的。
2/3PC 2/3PC 算法模式(Two / Three Phase Commit,二/三阶段提交)主要是为了让分布式系统在事务处理时 可以实现 ACID属性 所设计的算法。
2PC 首先我们可以将他看成两个阶段,一个是 欲提交 ,而另一个则是 提交 阶段,其中:
欲提交阶段 欲提交阶段从名字就可以看出他并不是真正的提交阶段,而是处于向资源管理器询问是否可以提交,这其中主要分为三个阶段:
向所有资源管理器发送询问
资源管理器执行事物操作,如资源上锁等
返回信息给事物管理器(成功情况下)
执行事务提交阶段 执行事务提交阶段可以理解为欲提交阶段后的一个过程,他的主要有四个流程,分别为:
请求提交:事务管理器向资源管理器发出提交请求
事务提交:资源管理器收到请求后,开始事务提交操作,完成后释放事务资源
反馈结果:完成事务提交之后,向事务管理器发送 已提交(ACK) 消息
完成事务:事务管理器收到了所有资源管理器反馈的 ACK 后即完成提交。
假如其中某个资源管理器返回了未提交,或者说等待超时,那么将会中断事务,其流程如下:
发送事务请求:事务管理器向资源管理器发送回滚请求
事务回滚:资源管理器通过撤销信息来执行事务回滚,释放事务资源
反馈事务回滚结果:完成事务回滚后发送 ACK (协调者发布)消息
中断事务:当事务管理器收到了来自所有参与者反馈的 ACK 消息后中断服务。
缺点 堵塞 尽管 2PC 看似非常的优秀,但是如果认真品一下就会发现,这么多资源管理器,如果只有中某个资源管理器在执行事物提交阶段的时候,返回了 未提交 ,之后的所有提交是不是都需要进行等待而无法继续完成操作?
单点 单点即一旦事务管理器出现了问题,那么整个 2PC 的提交将无法运转。或者说一旦在提交的时候出现了问题,那么所有的资源管理器都会处于锁定状态,从而无法继续提供服务。
3PC 3PC 和 2PC 不同之处在于他有三个阶段,分别为 欲提交(PreCommit)、可以提交(CanCommit)、提交(doCommit) 。在3PC 中三个阶段都有一个核心的理念,即 询问时不会锁定资源管理器,除非所有人 Ack 了才开始锁 ,因此他解决了 2PC 的堵塞问题。
而单点问题 3PC 则通过假设资源管理器无法即时接收到来自事务管理器的信息,他们会默认执行 doCommit ,而不会一直卡在那里不动,最终导致无法提供服务。
TTY Try-Confirm-Cancel TTY 主要有三个接口,分别为 尝试(Try)、确认(Confirm)、取消(Cancel)。会比 2/3PC 更加好理解,因此他三个阶段功能如下:
Try:检查所有业务的 一致性 、并预留业务资源保证在事务执行时不会被其他事务打扰
Confirm:使用 Try 阶段预留的业务资源来执行业务
Cancel:取消执行业务,并释放 Try 阶段预留资源
消息中间件模式 通过引入消息中间件,可以进行异步处理、系统解耦、事务管理。从而解决多个系统之间同步通信所造成的阻塞,并将这些耦合在一起。消息中间件模式与 TCC 同样是最终一致性来管理事务,而 2/3PC 则是强一致。
在这个过程中,主要有三个要素,分别是将数据消息投递到中间件服务器中,通过消息确认机制保证成功投递。然后通过消费者可以正常使用消息,最后来保证第一个事务先执行。
目前世面上主流的消息中间件有 Redis、ActiveMQ、RabbitMQ、RocketMQ、Kafka等。
Saga 模式 Saga是根据 Hector 和 Kenneth 与 1987 年所发表的论文 《sagas》作为理论基础而出现的补偿协议。
Saga 模式其主要的就是 将长事务拆分为多个且可交错运行的子事务合集 而这些子事务都保持一定的一致性,都将由 saga 事务协调器来进行协调。
如果每一个子事务都正常结束,则整个事务完成。假设有某个子事务失败,那么则整个事务失败,并根据相反顺序来执行补偿步骤。
需要注意的是,Saga 由一系列的 T 事务组成,每个 T都对应一个补偿动作 C,用于撤销 T 所造成的结果。
恢复策略 向后恢复 向后恢复(backward recovery),补偿所有已完成事务。如果某一个子任务失败,则之前所有成功的子事物即 整个saga执行结果撤销 。
向前恢复 向前恢复(forward recovery),将失败的事务重试,假设所有失败的业务最终都会成功,在这种情况下就没有补偿事务什么事情了,常见与必须成功的场景。‘
Paxos 一致性算法 Paxos 算法由 Lamport 所提出的一种基于消息传递的分布式一致性算法,也被认为是类似算法中最为有效的,因此使其获得了 2013年图灵奖,他的主要作用就是 在分布式系统中就某个值,来达成一致 。
角色 首先在 Paxos 算法中最终要的就是角色,非常重要,主要有三个,分别为:
提议者(Proposer)
决策者(Acceptor )
学习者(Learner)
决策 首先,提议者(Proposer) 提出议案(Proposal),信息中包含了案件编号(Proposer id)以及提议的值(Value)
之后 决策者(Acceptor) 参与决策,回应 提议者(Proposer) 提案,如果收到的提案(Proposal)获得多数 提议者(Acceptor) 的接受,则该提案被批准
最后 学习者(Learner) 来学习从会议中获得到达成一致的提案(value)。
2F+1 我们可以理解为,假设要是决策者 5个 同意 5个拒绝,那么就不会构成会议的决策。在这种情况下,如果有2倍的 决策者 人数并加一,就可以保证信息的正确,使得会议正常进行。
Paxos 算法允许运行在宕机故障的异步系统内,也不要求可靠的消息传递,他使用了多数机制保证了 2F(faulty)+1 的容错,即 2F(faulty)+1 个节点最多允许 F 个节点出现问题。
Spring cloud 跨服务查询与微服务部署概述 跨服务之间查询 在微服务中,如果需要服务之间与服务之间进行数据的查询,可以被称之为 “跨服务”,而进行这个过程的有两种,分别为 API 和 CQRS模式两种。
API 组合器模式 API 组合器模式由两个或多个服务提供者组成,服务提供者拥有数据服务(即数据库),API 组合器通过查询每个服务提供者的 API 结果并组合,来实现从多个服务检索数据库的查询。
CQRS 命令查询职责分离(CQRS,Command Query Responsibility Segregation),是由 Eiffel 语言之父 Betrand Meyer 提出的概念,简单来讲就是将业务上分离了 读(query)和写(command)的行为 ,就比人的睡和醒是两种行为。
命令(Command),不返回任何结果(void),但会改变对象状态,指增加、删除、修改
查询(Query)用于返回结果(void),不会改变对象状态
因此 CQRS 中主要强调 读(Query)和写(Command)的分离 ,可能当查询数据会有一些问题,如时间过时的数据问题,可以通过使用事件(Event)机制来解决问题并修改查询数据源。
微服务部署 部署模式 微服务的部署主要可以分为四种,这四周部署模式中主要通过下述方式进行:
WAR、JAR 包运行
服务部署虚拟机运行
通过 Docker 或 Kubernetes 等容器化技术进行微服务部署
Serverless 无服务器部署
无服务器(Serverless)通过应用 使用第三方功能或服务 ,不需要管理服务器,但不代表他不需要服务器,主要包括了:
功能即服务(Function as a Service,FaaS) 简单来讲就是功能运行在独立容器中,基于事件驱动,由第三方托管功能。
后端即服务(Backend as a Service,Baas) 使用第三方服务来达到目的。
升级(维护)模式 蓝绿部署 蓝绿部署(Blue-green deployment),可以称之为 可不停机维护 ,主要分为蓝(Blue)和绿(Green)两个版本,其流程如下:
当更新时,将所有访问流量分给绿色部分来提供服务,从而升级蓝色集群。
当蓝色集群升级完后,来将绿色流量切换到蓝色集群,从而升级绿色集群
升级完成后将访问流量分配到全部集群中
滚动部署 滚动部署(Rolling deployment),指每次只一个或多个服务暂停。进行更新,后将此服务继续投入使用,直到集群中所有服务都被更新或完成为止。
金丝雀发布(灰度发布)
为什么叫金丝雀发布? 在以前矿工开矿的时候,需要在下矿洞前需要检查下方是否有毒气,矿工会放一只金丝雀进去,来去探路是否有毒气。
金丝雀发布也叫灰度发布(Canary release / Grayscale release),其主要是一部分更新,一部分使用老版本,当新版本用户没什么建议的话,则全部将迁移到新版本中。
Spring cloud 云原生概述 目前我们所谈论的云原生大多数分为两个,一个由 spring 开发公司 Pivotal 所定义的,另一个则是由谷歌所牵头成立的 云原生计算基金会 (CNCF,Cloud Native Computing Foundation)。
本文我们仅理解 Pivotal 公司和部分 CNCF 所定义的为服务,其主要概述主要分为四类,分别为:
微服务
DevOps3. CI\CD(持续集成和持续交付)
容器化
微服务 读者可通过下一篇文章深度和实践中了解到微服务到底可以做什么,他的作用都会被理解,主要明确服务之间的解藕,多个服务集群。
DevOps DevOps(Development Opertainos)组合,即开发和运维组合,可以减少应用程序发布的风险。
CI\CD(Continuous integration \ Continuous deployment),及持续集成和持续交付的所略词,其主要概念是通过应用程序的共建、测试和部署中实施自动化,在开发和运营团队架起桥梁,也是构成 DevOps 关键之一。
容器化 容器化即以容器为基础,在容器中运行的进程和应用,其 Docker 是应用最为广泛的容器引擎。通过容器封装让进程和应用成为一个独立的单元,并实现除了高水平的资源隔离,简化了维护。
除此之外还有 kubernetes 容器编排系统,主要用于容器管理和容器之间的负载均衡,都是实现容器话的关键。
安装 本文使用 1.8.0_202 和 Maven 3.6.0 进行演示和构建,读者可自行进行安装其环境。此时我们需要使用 Spring Assistant ,有两种方式可以进行安装,分别是从 jetbrains 内的 插件社区中 https://plugins.jetbrains.com/plugin/10229-spring-assistant 进行获取,也可从 Plugins 内直接进行安装,搜索 Spring Assistant 之后重启 IDEA 即可。
Sentinel 是 Alibaba Spring Cloud 微服务组件之一,他以 流量为切入点,主要实现流量控制、熔断降级、系统负载保护等多个维度来保证服务的稳定性,因此他还提供了机器发现、健康情况、监控、规则管理和推送等功能,并提供了一个可视化控制页面 Sentinel Dashboard,可前往 https://github.com/alibaba/Sentinel/releases/tag/1.8.2 进行下载,之后可以通过下述命令启动(Sentinel Dashboard 的帐号密码同样都为 sentinel):
1 sudo java -Dserver.port=9899 -Dcsp.sentinel.dashboard.server=localhost:9899 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.2.jar
限流模式 直接失败
通过 postman 进行测试
直接限流
当实例运行后,进入该实例中,在控制台中的流控规则中添加一个控流规则,并设置 QPS 为 1,并选择直接控流模式,而这个规则所达到的效果就是让超过阀值的请求直接失效,因此我们需要在项目中添加所需依赖:
1 2 3 4 5 6 7 8 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
在配置中我们依然只需要连接 Sentinel 控制台即可,并配置项目的名称,这将会在控制台中进行显示状态以及管理等。
1 2 3 4 5 6 7 8 9 spring: application: name: alibaba-sentinel cloud: sentinel: transport: dashboard: localhost:9899 server: port: 8210
自定义埋点 所谓 “埋点” 我们可以理解为当用户访问特定的某个点,也就是监听这个点是否被用户所访问,在 Sentinel 中,我们可以通过 @SentinelResource 依赖中的埋点与 Sentinel 控制台中所增加的规则进行绑定,从而实现直接限流的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.example.demo.controller;import com.alibaba.csp.sentinel.annotation.SentinelResource;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class heyController { @GetMapping("/hey") @SentinelResource("hey") public String hey () { return "Server ok!" ; } }
当这完成之后,我们在 Sentinel 控制台中所增加的控流规则 QPS=1 当超过这个阀值的时候,该请求也会快速失败。
关联限流
关联限流简单理解就是,当我访问 test/one 的 QPS=1 的阀值过了以后,那么 test/two 这个接口就无法进行访问,因此我们在直接限流的基础上可以不需要设置埋点,直接添加两个接口即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.example.demo.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/test") public class heyController { @GetMapping("/one") public String one () { return "Server one ok!" ; } @GetMapping("/two") public String two () { return "Server two ok!" ; } }
链路限流
链路路由主要的作用就是,假设我们的接口都使用一个方法接口,那么在 Sentinel 中所填写的控流模式的入口资源,当入口资源超过 QPS 阀值时,那么将会被限流,但同样使用方法接口的另一个接口不会受此影响,为实现链路路由,我们需要添加如下依赖和增加配置:
1 2 3 4 5 6 7 8 9 10 11 12 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-web-servlet</artifactId> </dependency>
之后在配置文件中将是否统一网络上下文中选择 false:
1 2 3 4 5 6 7 8 9 10 spring: application: name: alibaba-sentinel cloud: sentinel: transport: dashboard: localhost:9899 web-context-unify: false server: port: 8210
server 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.example.demo.server;import com.alibaba.csp.sentinel.annotation.SentinelResource;import org.springframework.stereotype.Service;@Service public class sentinelServer { @SentinelResource("message") public void sentinelTest () { System.out.println("sentinel is Test." ); } }
controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.example.demo.controller;import com.example.demo.server.sentinelServer;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/test") public class heyController { @Autowired private sentinelServer sentinelServer; @GetMapping("/one") public String one () { sentinelServer.sentinelTest(); System.out.println("Server two ok!" ); return "Server one ok!" ; } @GetMapping("/two") public String two () { sentinelServer.sentinelTest(); System.out.println("Server two ok!" ); return "Server two ok!" ; } }
Warm Up
Warm Up 限流的主要作用就是为了应对猛烈的流量请求,分批次的进行处理,之后慢慢加流量,而不是向 DDos 一样直接就把服务干崩溃了。做一个很形象的比喻,我们吃饭的时候都是嚼了好几下才开始咽下去的,而一些猛人吃饭跟喝水一样,最终导致食物卡到了喉咙了,而这种情况在限流中也同样存在,因此 Warm Up 就很好的解决了这个问题。
排队限流
排队等待的限流模式主要通过超时时间,也就是每个请求的处理时常,以此让服务匀速处理请求而不是直接拒绝请求的作用,在新的控流规则中,我们设置 QPS 为 4,而超时时间(每个请求的处理时间为 1000ms),从而我们的服务性能可以处理,但如果超时时间设置为 100ms 时,则会发现被拦截的请求很多,这是因为服务的处理性能达不到预设的超时时间而造成的。
Spring Cloud Alibaba 是一个遵循 Spring Cloud 变成规范的框架,也就是 Alibaba 所推出的微服务解决方案,他主要分为 Sentinel(流量控制)、Nacos(服务发现)、RocketMQ(分布式消息系统)、Dubbo(高性能 Java RPC 框架)、Seata(高性能微服务事务解决方案)、ACM(配置中中心)、OSS(对象存储服务)、SchedulerX(分布式任务调度)、SMS(短信服务)
Alibaba 对为 Spring Cloud 的贡献同样可以与 Netfilx 相同的产品都有类似的解决方案,但在 Spring Cloud 中 Netfilx 的贡献要比 Alibaba 早上许多,因此通常 Alibaba 的解决方案依然可以替换掉 Netfilx 的产品,但 Netfilx 同样也可以替换掉 Alibaba 的解决方案,与其相比之下 Alibaba 的解决方案更加的简洁,通常一个项目可以代替很多个 Netfilx 的子项目。
Nacos 是 Alibaba Spring Cloud 中用于构建微服务应用与服务治理的配置管理组建,与 Eureka、Conusl 同样分为服务中心、服务提供者、服务消费者等三个角色,除此之外还可以直接实现配置中心的服务。
在实现服务提供者和消费者之前,我们首先需要通过 Nacos 仓库来下载其发行版本的服务中心,需要注意的是环境环境依赖是:
64bit 支持 Linux\Unix\Mac\Windows(官方推荐 Linux\Mac\Unix)
JDK 1.8+
Maven 3.2.+
满足依赖环境后可以通过 https://github.com/alibaba/nacos/releases/tag/2.0.3 来获取你想要的版本进行下载。
之后解压进入 bin 目录,并运行 ./startup.sh -m standalone 命令以启动 Nacos,并访问 http://localhost:8848/nacos 即可登入 nacos 的控制台,帐号密码均为 nacos,因此服务中心的地址为 localhost:8848
服务治理 服务提供者 我们首先创建两个服务提供者,并连接服务中心,之后进行配置,在默认情况下我们需要引入 spring-boot-starter-web、spring-cloud-starter-alibaba-nacos-discovery 依赖以作为必要的服务支撑:
1 2 3 4 5 6 7 8 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
同样的我们需要进行配置连接服务中心以及名称等,以便在微服务系统中发现该功能是那个服务所提供的,因此好进行改进和调整,配置后我们还需要在启动类中添加 @EnableDiscoveryClient 依赖
1 2 3 4 5 6 7 8 9 10 spring: application: name: localhost-provider cloud: nacos: discovery: server-addr: localhost:8848 server: port: 8210
heyController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.example.demo.controller;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class heyController { @Value("${spring.application.name}") private String appName; @GetMapping("/hey") public String hey () { String sty = "This is Provider: " + appName + "-1" ; return sty; } }
从上所述我们的服务提供者所提供的基础服务已经构建完成,在演示中我们构建的是两个服务提供者,都提供同样的功能,端口为 8210~8211 ,为两个服务提供者,之后我们可以编写服务消费者。
服务消费者
至于服务消费者,我们同样的需要引入 spring-cloud-starter-alibaba-nacos-discovery 依赖以及负载均衡 feign 依赖,和一些基础服务提供依赖,来完成服务消费者的构建,在此之前我们需要在启动类中添加 @EnableDiscoveryClient 注解并完善配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
配置文件的主要作用同样是配置服务名称以及连接服务中心等:
1 2 3 4 5 6 7 8 9 spring: application: name: localhost-consumer cloud: nacos: server-addr: 127.0 .0 .1 :8848 server: port: 8310
heyController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.example.demo.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;import org.springframework.context.annotation.Bean;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import java.net.URI;@RestController @EnableDiscoveryClient public class heyController { @Autowired private LoadBalancerClient loadBalancerClient; @Autowired private RestTemplate restTemplate; @Bean public RestTemplate restTemplate () { return new RestTemplate (); } @Value("${spring.application.name}") private String appName; @GetMapping("/hey") public String echoApplication () { ServiceInstance serviceInstance = loadBalancerClient.choose("localhost-provider" ); URI uri = serviceInstance.getUri(); String callService = new RestTemplate ().getForObject(uri + "/hey" , String.class); return callService; } }
配置中心
相对与之前的 Spring Cloud Config ,Alibaba 所提供的 Spring Cloud Aliababa Config 主要依赖于服务中心的分发与配置,同时还支持配置动态刷新,以及达到和 Spring Cloud Config 相同效果的配置文件应用,只需要依赖 spring-cloud-starter-alibaba-nacos-config 即可,在此之前我们还需要在启动类中加入 @EnableDiscoveryClient 依赖:
1 2 3 4 5 6 7 8 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
在此之前,我们需要在服务中心中新建一个 .properties 类型的文件,当然他默认支持了 TEXT、JSON、XML、YAML、HTML、Properties 等配置格式,我们只需要填写配置信息和 Data ID 即可:
1 2 3 app.version=rsa message=Spring Cloud Alibaba Config Server server.port=9510
bootstrap.yml & application.yml bootstrap.yml 1 2 3 4 5 spring: cloud: nacos: config: refreshable-dataids: server-config-example.properties
我们通过 refreshable-dataids 来绑定配置文件的名称,之后通过服务中心进行获取,因为是从服务中心直接获取,从而省去了很多配置。
application.yml 1 2 3 4 5 6 7 8 9 spring: application: name: localhost-consumer cloud: nacos: server-addr: 127.0 .0 .1 :8848 server: port: 8410
Controller 控制器主要作用就是通过服务中心以及配置所绑定的文件,来获取到服务中心的配置文件信息,并通过指定接口来进行输出,当一切开始运行后访问 http://localhost:9510/hey 即可看到从服务中心和控制器所输出的信息。
ServerConfigController.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.example.demo.controller;import org.springframework.beans.factory.annotation.Value;import org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RefreshScope public class ServerConfigConortller { @Value("${app.version}") private String appVersion; @Value("${message}") private String message; @RequestMapping("/hey") public String hey () { return "application: " + appVersion + " message: " + message; } }
Spring Cloud Config 可以为分布式系统中外部化配置提供服务器端和客户端的支持,通过 Config Service 即配置中心,可以集中管理所有环境中应用程序配置,这其中主要用于集中管理信息的组件,用于提供配置变更、配置推送、历史版本管理以及灰度发布、配置变更审计等主要的功能,以此来降低分布式系统中所管理和配置信息的成本。
其实 Spring Cloud Config 说简单一点就是通过本地(含 Git/远程代码仓库)来获取配置信息,然后服务提供者A、B…… 从配置中心获取服务,在这个过程中有一个 Spring Cloud Bus 即消息总线,用于通知服务提供者需要获取配置。
目前主流的配置中心有 Spring Cloud Config、Apollo、Nacos、Disconf 等项目,其中 Spring Cloud Config 与 Disconf 是这些项目中最早开源的项目。
但这些项目中,Spring Cloud Config 他的功能全面,以及无缝贴合 Spring 体系,因此大受欢迎。
Config Server localhost 目前主要且流行的方式就是通过 git 仓库来获取多个或多个配置文件,并通过对称/非对称加密来对密文转为明文,这都的易于 Spring Cloud Config 的特性,如果只满足简单的获取配置文件只需要添加 spring-cloud-config-server 依赖:
1 2 3 4 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
之后在其启动类中加入注解 @EnableConfigServer,并通过 application.yml 全局 Spring 配置中设置仓库地址和帐号密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 spring: application: name: cloud-server cloud: config: server: git: uri: https://gitee.com/sif_one/spring-cloud-config search-paths: config-repositories username: xxx password: xxx encrypt: enabled: true server: port: 8210
git 对于 git 仓库中的配置文件,需要注意的是 - 在访问时会被 / 所代替,如你的配置文件名为 config-dev.properties,那么所请求的 URI 就为 http://localhost:8210/config/dev ,当然这是 json 格式的,如果想以常用的格式进行返回,可将 URI 改为其配置文件名 http://localhost:8210/config-dev.properties 即可。
1 2 3 4 app.version =dev message =Spring Cloud Config Demo password ={cipher}32fd20f46a9c00238236ff530349eefb1dbaad99374c62276f266c326fa8e1ec server.port =8017
Config Server Client Spring cloud config client 的主要作用就是通过配置中心来获取配置并使用,主要通过 spring-cloud-starter-config 依赖来进行实现,并通过 bootstrap.yml 文件来达到配置文件切换的效果,最后在使用控制器进行输出:
1 2 3 4 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
bootstrap.yml and application.yml 1 2 3 4 5 6 7 spring: cloud: config: name: config profile: rsa uri: http://localhost:8210 label: master
在 bootstrap.yml 文件中,你可以通过 profile 来切换配置文件的数据,也就是 name(config) 和 profile(rsa),这也是我们配置文件 config-rsa 拼合而成。当然你也可以在 application.yml Spring 全局配置文件中来为其添加应用名称,毕竟他才是权重第一的配置文件。
1 2 3 spring: application: name: SpringCloudConfigClient
controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.example.demo.controller;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class heyController { @Value("${app.version}") private String version; @Value("${message}") private String message; @RequestMapping("/hey") public String hey () { String string = "message:" + message + ":" + " version:" + version; return string; } }
配置文件的加密
对称加密
Config Server 除了管理配置文件和版本管理之外,还支持对文件的对称和非对称加密,其中对称加密简单的来将就是采用单个密钥的方式进行加密,也就是说一个密钥可以加密和解密。
而非对称加密就与对称加密不同了,非对称加密算法分为公钥和私钥,如果用公钥对数据进行加密,那么只能通过私钥才可解密。目前有很多文章说需要使用新版本的 JDK 或者通过 JCE 来进行解决,当然这也是解决方法之一,在新版的 Spring 中,需要引入除 spring-cloud-config-server 之外的新依赖,即 spring-cloud-starter-bootstrap 依赖:
1 2 3 4 5 6 7 8 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
此时 spring-cloud-starter-bootstrap 会提供三个端口,分别用于加密、解密、检查等方式进行对称加密的操作:
在原有的 Spring Cloud Config 基础上,我们只需要新建一个 bootstrap.properties 文件并写入密钥,这主要的作用就是,你请求这个接口显示的是明文,而没有密钥的客户端发起请求返回的则是密文。
当然你如果有不良癖好喜欢密文的话,也可以通过 encrypt 来停止 Spring cloud config 的直接翻译,这时返回的则是密文信息:
1 2 3 4 5 6 spring: cloud: config: server: encrypt: enabled: true
非对称加密 至于非对称加密则需要通过 keytools 来新建一个密钥,之后生成一个密钥移动到 resourcs 目录下,并在 bootstrap 全局配置文件 bbootstrap.applicatiyon 下填写相关的配置信息即可。
1 keytool -genkeypair -alias "spring-cloud-config" -keypass "keypass" -keyalg "RSA" -storepass "stropess" -keystore "spring-cloud-config.jks"
其中上述命令最为关键的部分则是 alias(别名) 、keypass(密钥口令)、storepass(密钥库口令) 这三类主要配置,将会在配置文件中进行使用以验证身份,而 -keylag 则主要表示密钥的类型为 RSA 加密算法。
RSA是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的,当时他们三人都在麻省理工学院工作,RSA 就是他们三人姓氏开头字母拼在一起组成的。
1 2 3 4 5 6 7 8 # 密钥位置 encrypt.key-store.location=classpath:/spring-cloud-config.jks # 密钥别名 encrypt.key-store.alias=spring-cloud-config # 密钥库密码 encrypt.key-store.secret= # 密钥密码 encrypt.key-store.password=
与对称加密一样,可以通过之前的三个端点来进行加密、解密、状态检查等服务:
我们在 git 仓库重新建立一个配置文件,他主要通过非对称加密作为密文,之后通过我们的密钥来进行解密的这么一个流程:
1 2 3 4 app.version =rsa message =Spring Cloud Config Demo password ={cipher}AQA15miINQvSMr5kJorYoX2MLH+1XgjBAGrJKPJWzAvaUhxfrd6So0skCEXjiidn/Vgf+aWCcz17pugBoRqEeDU6XLyu1tS3dAyNE/z7vbyEcp+DsJOEqHiHnr3VqzrhQTJheJOibkskvd8kJt6WP4F9Tl8qyen6mOz+4/Ce4x9iCzPoWTO2Yc+jXJFAjOSB02/kh1wnqAdyc0s7cDYLjjR+FBSMFjFJXV0Qax28hZNjjdlUb/Hy+8mPaSAQbipc9FIKB+dQaSAABYCviscHz7XkjpfmQN3qOAYiMum851OP1OOn8KHGpUu1u7D8ET+404iAHjNPTKh/As8T9A92Av73YE6b8Dj3m9wZf4hCUyEMFUpo0kffw9ThOAzVgR1RnNY= server.port =8017
Spring Cloud Bus Spring Cloud Bus 将会用于自动刷新配置,并且可以管理和传播分布式项目中的消息,利用消息中间件广播的机制传播可以实现将消息连接整个集群,他目前支持 Kafka 和 RabbitMQ,其中目前主要流行的就是这两个项目。
RabbitMQ RabbitMQ 是一个开源的消息代理项目,即面向消息的中间件,采用 Erlang 语言来进行编写,因此我们除了安装 RabbitMQ 之外还需要安装 Erlang 。
对于使用 debian 系的 Linux 用户,可以通过使用 apt-get install erlang 直接进行安装 erl,之后同样的安装 RabbitMQ Server 并运行:
1 2 apt-get install rabbitmq-server systemctl status rabbitmq-server
运行之后就开始进行配置 RabbitMQ 了,这里面主要的就是配置帐号密码以及权限和安装可视化插件:
Id
Name
Info
1
rabbitmq-plugins enable rabbitmq_management
安装可视化插件
2
rabbitmqctl add_user root toor
添加 root 帐号密码为 toor
3
set_user_tags root administrator
将 root 帐号设置为管理
4
set_permissions -p / root ".*" ".*" ".*"
为 root 帐号添加写入权限
5
rabbitmqctl list_permissions
查看当前的当前的权限列表
值得注意的是,当 RabbimtMQ 配置完成后,他会提供三个非常重要的端口,分别为 5672\25672\15672 在本文中主要使用 5672 端口来进行 AMQP 连接接口,而 15672 用于 HTTP 访问接口,也就是监控中心,之后你可以将这个接口添加到 Config Server & Client 配置文件中:
1 2 3 4 5 6 7 8 spring: application: name: SpringCloudConfigClient rabbitmq: username: root password: toor host: xxx port: 5672
Config Server 对于 Config Server ,我们可以理解在原有的基础上,增加了 Spring Cloud Config Bus 来实现配置文件更新和广播的作用,简单来讲就是默认情况下 Config Client 是无法实现自动更新的,只可以通过 Spring Config Bus 来进行实现发送 POST 来进行刷新客户端配置文件的应用,因此我们需要添加如下依赖(在原有的非对称加密的基础上配置 Config Server):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-amqp</artifactId> <version>5.5.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 spring: application: name: cloud-server cloud: config: server: git: uri: https://gitee.com/sif_one/spring-cloud-config search-paths: config-repositories username: xxx password: xxx encrypt: enabled: true rabbitmq: username: root password: toor host: xxx port: 5672 server: port: 8210 management: endpoints: web: exposure: include: "bus-refresh"
之后打包上传到服务器之中,运行后即可通过使用 http://111.67.201.159:8210/actuator/busrefresh 接口来进行刷新客户端的配置,这是新版本中所提供的接口,当然你如果使用的是其他版本也可以通过访问 /actuator 端口来查看目前所支持的 Spring-Cloud-Bus 接口。
Config Client Config Client 的主要作用就是获取到 Config Server 从 Git 仓库中的配置文件并运用,但是在不使用 Spring-Cloud-Config-Bus 的情况下,即使你将最新的配置文件上传了 Config Config 也不会进行更新,因此需要通过 Spring Cloud Config Bus 来进行实现,在原有的 Config CLient 基础上进行添加即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> <version>3.0.4</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-amqp</artifactId> <version>5.5.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
@RefreshScope 依赖主要的作用就是当 git 仓库发生改变或更新后,我们通过 Config server 所提供的 ```actuator/busrefresh``` POST 请求来刷新 Config Client 的配置文件,因此这个依赖主要的作用就是刷新的范围,我们在原有的 Config Server Client 基础上添加即可: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ```java package com.example.demo.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 获取配置中心的数据 * * @author kunlun * @date 2021/7/26 */ @RestController @RefreshScope public class heyController { @Value("${app.version}") private String version; @Value("${message}") private String message; @RequestMapping("/hey") public String hey() { String string = "message:" + message + ":" + " version:" + version; return string; } }
当然你也可以在启动类中进行添加
application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring: application: name: SpringCloudConfigClient rabbitmq: username: root password: toor host: xxx port: 5672 management: endpoints: web: exposure: include: "bus-refresh"
配置文件中的 management 为开启此接口,默认的情况下是关闭的,需要通过配置文件中进行配置,之后即可访问,虽然在最新版本中不知道有没有什么作用,但是为了保险期间还是添加为好。
新版本中 Spring Cloud Config Bus 所提供的 POST 更新接口为 actuator/busrefresh
bootstrap.yml 1 2 3 4 5 6 7 spring: cloud: config: name: config profile: rsa uri: http://xxx:8210 label: master
bootstrap 配置文件主要做的就是通过 spring.cloud.config 来链接配置中心,并通过配置中心所获取的配置文件来进行运用,最后配置完成后我们可以尝试修改配置文件信息,你可以全部接口都访问一遍看看最初的效果(除了 actuator/busrefresh),之后通过 actuator/busrefresh 接口以 POST 方式进行刷新,最后访问 Spring Cloud Config Client 所提供的 /hey 接口来查看是否刷新成功。
WebHooks
WebHooks 主要的作用就是当推送到 git 仓库的时候 WebHooks 服务会将 POST 请求到对应的 uri 中,从而实现我们手动请求 POST 来刷新服务配置文件的效果。但在新版本中却请求失败,考虑其他不可抗拒的因素建议国内读者选择 Gitte,但 WebHooks 依然请求返回 400 如有读者解决可在下放进行评论。
📖 more posts 📖