一、概述
1. 名词解释
在系统架构与设计的实践中,经历了两个阶段,一个阶段是早些年常见的集中式系统,一个阶段是近年来流行的分布式系统;
-
集中式系统:
集中式系统也叫单体应用,就是把所有的程序、功能、模块都集中到一个项目中,部署在一台服务器上,从而对外提供服务; -
分布式系统:
分布式系统就是把所有的程序、功能拆分成不同的子系统,部署在多台不同的服务器上,这些子系统相互协作共同对外提供服务,而对用户而言他并不知道后台是多个子系统和多台服务器在提供服务,在使用上和集中式系统一样; -
那什么是微服务架构呢?分布式强调系统的拆分,微服务也是强调系统的拆分,因此微服务架构属于分布式架构的范畴;
简单地说, 微服务是系统架构上的一种设计风格, 它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于
HTTP
的RESTful API
进行通信协作;被拆分后的每一个小型服务都围绕着系统中的某一项业务功能进行构建, 并且每个服务都是一个独立的项目,可以进行独立的测试、开发和部署等;由于各个独立的服务之间使用的是基于HTTP的作为数据通信协作的基础,所以这些微服务可以使用不同的语言来开发; -
什么是
RESTful API
?
首先要知道REST
是什么:REST
表示性状态转移(representation state transfer)。简单来说,就是用URI
表示资源,用HTTP
方法(GET(查)、POST(增)、PUT(改)、DELETE(删))表征对这些资源的操作(增删改查)。而遵循REST
规定设计的应用程序接口,即为RESTful API
。简单理解,RESTful API
统一了发送请求的语法格式,服务端就只需要定义一个统一的响应接口即可。
更详细的RESTful API请戳:
2. 微服务架构的优缺点
优点:
- 微服务架构将系统中的不同功能模块拆分成多个不同的服务,这些服务进行独立地开发和部署,每个服务都运行在自己的进程内,这样每个服务的更新都不会影响其他服务的运行;
- 由于每个服务是独立部署的,所以我们可以更准确地监控每个服务的资源消耗情况,进行性能容量的评估,通过压力测试,也很容易发现各个服务间的性能瓶颈所在;
- 由于每个服务都是独立开发,项目的开发也比较方便,减少代码的冲突、代码的重复,逻辑处理流程也更加清晰,让后续的维护与扩展更加容易;
- 微服务可以使用不同的编程语言进行开发;
缺点:
- 微服务架构增加了系统维护、部署的难度,导致一些功能模块或代码无法复用;
- 随着系统规模的日渐增长,微服务在一定程度上也会导致系统变得越来越复杂,增加了集成测试的复杂度;
- 随着微服务的增多,数据的一致性问题,服务之间的通信成本等都凸显了出来;
所以在系统架构时也要提醒自己:不要为了微服务而微服务。
3. 为什么使用SpringCloud
整个微服务架构是由大量的技术框架和方案构成,比如:
服务 | 框架 |
---|---|
服务基础开发 | Spring MVC、Spring、SpringBoot |
服务注册与发现 | Netflix的Eureka、Apache的ZooKeeper等 |
服务调用 | RPC调用有阿里巴巴的Dubbo,Rest方式调用有当当网Dubbo基础上扩展的Dubbox、还有其他方式实现的Rest,比如Ribbon、Feign |
分布式配置管理 | 百度的Disconf、360的QConf、淘宝的Diamond、Netflix的Archaius等 |
负载均衡 | Ribbon |
服务熔断 | Hystrix |
API网关 | Zuul |
批量任务 | 当当网的Elastic-Job、Linkedln的Azkaban |
服务跟踪 | 京东的Hydra、Twitter的Zipkin等 |
在微服务架构上,几乎大部分的开源组件都只能解决某一个场景下的问题,所以当我们准备实施微服务架构时,我们要整合各个公司或组织的开源软件,非常麻烦。
Spring Cloud
,是一个解决微服务架构实施的综合性解决框架,它整合了诸多被广泛实践和证明有效的框架作为实施的基础组件,又在该体系基础上创建了一些非常优秀的边缘组件将它们很好地整合起来。让我们实施微服务架构变得异常简单。
4. 什么是Spring Cloud
Spring Cloud
是一个一站式的开发分布式系统的框架,为开发人员提供了快速构建分布式系统中一些常见模式的工具(比如:配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等),并能方便地在任何分布式环境中部署与运行。
Spring Cloud基于Spring Boot框架构建微服务架构,学习Spring Cloud需要先学习Spring Boot;
SpringCloud官网:http://spring.io
5. Spring Cloud的版本
Spring Cloud
的版本并不是传统的使用数字的方式标识,而是使用诸如:Angel、Brixton、Camden…等伦敦的地名来命名版本,版本的先后顺序使用单词首字母对应字母表A-Z
的先后来标识,现在已经进入Hoxton
版本,对应SpringBoot 2.2.x
Spring Cloud并不是从0开始开发一整套微服务解决方案,而是集成各个开源软件,构成一整套的微服务解决方案,这其中有非常著名的Netflix
公司的开源产品;
目前Netflix公司贡献的活跃项目包括:
- spring-cloud-netflix-eureka
- spring-cloud-netflix-hystrix
- spring-cloud-netflix-stream
- spring-cloud-netflix-archaius
- spring-cloud-netflix-ribbon
- spring-cloud-netflix-zuul
6. Spring Cloud的整体架构
- Service Provider: 暴露服务的服务提供方。
- Service Consumer:调用远程服务的服务消费方。
- EureKa Server: 服务注册中心和服务发现中心。
7. 开发环境
SpringBoot版本:2.2.4.RELEASE
Spring Cloud版本:Hoxton SR1
JDK版本:1.8.0_102
IntelliJ IDEA
二、Spring Cloud快速开发入门
这里并没有使用注册中心,只是简单的使服务消费者直接服务提供者
1. 搭建服务提供者
SpringCloud构建微服务是基于SpringBoot开发的。
关于Spring Boot的使用,请参考这里:SpringBoot集成Web项目
搭建基本步骤为:
-
创建一个SpringBoot模块(SpringBoot的Web模块),并且添加SpringBoot的相关依赖
可以在创建SpringBoot时,勾选
Web --> Spring Web
-
创建服务提供者的访问方法,也就是后续消费者如何访问提供者;
//这里就写一个简单的控制层 @RestController public class Controller { @RequestMapping("/test") public String test() { return "Hello Spring Cloud!"; } }
-
修改配置文件,application.properties
server.port=8081
2. 搭建服务消费者
服务消费者也是一个SpringBoot项目,服务消费者主要用来消费服务提供者提供的服务;
-
创建一个SpringBoot模块(SpringBoot的Web模块),并且添加SpringBoot的相关依赖(勾选
Web --> Spring Web
); -
定义自定义类
RestTemplateConfig
类,将一个RestTemplate
对象注入到Spring容器中,我们利用这个对象使用Http
协议访问服务的消费者//定义一个配置类用于模拟Spring的配置文件 @Configuration public class RestTemplateConfig { //将RestTemplate对象定义到Spring的应用上下文容器中 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
-
写一个消费者的Controller层,使用第二步创建的
RestTemplate
对象访问服务提供者://这里是服务消费者的控制类,这个控制器类可以返回一个视图页面也可以返回一个Json数据 //我们这里暂时返回Json数据只是为了方便我们测试 @RestController public class TestController { //注入一个RestTemplate对象,我们利用这个对象使用Http协议访问服务的消费者 //这个对象是Spring提供的 @Resource private RestTemplate restTemplate; @RequestMapping("/test") public String test(){ //使用get方式发送请求访问服务提供者的test请求 //参数1 为服务提供者的某个请求的绝对路径 //参数2 为本次请求以后服务提供者响应数据时的具体数据类型 //返回只为一个ResponseEntity对象,这个对象中封装了本次请求后服务提供者响应的所有内容 //包括本次响应的具体的状态码以及头文件信息和具体响应数据等等 ResponseEntity<String> result = restTemplate.getForEntity("http://localhost:8081/test", String.class); System.out.println(result.getStatusCode());//获取响应编码,如果访问成功输出为:200 OK System.out.println(result.getStatusCodeValue());//只输出响应编码,如果访问成功输出为:200 System.out.println(result.getHeaders());//获取响应头文件信息 System.out.println(result.getBody());//返回本次响应的具体数据内容,类型为ResponseEntity对应的泛型 return "这是服务消费者的test请求-----" + result.getBody(); } }
三. 注册中心Eureka
在微服务架构中,服务注册与发现是核心组件之一,Spring Cloud支持得最好的是Eureka
,其次是Consul
,再次是Zookeeper
。
服务注册:将服务所在主机、端口、版本号、通信协议等信息登记到注册中心上;
服务发现:服务消费者向注册中心请求已经登记的服务列表,然后得到某个服务的主机、端口、版本号、通信协议等信息,从而实现对具体服务的调用;
1. 什么是Eureka
Eureka
是一个服务治理组件,基于 REST 的服务,它主要包括服务注册和服务发现,主要用来搭建服务注册中心。
Eureka
是Netflix 公司开发的,Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现,也就是说Spring Cloud对Netflix Eureka 做了二次封装;
Eureka
采用了C-S
(客户端/服务端)的设计架构,也就是Eureka由两个组件组成:Eureka服务端和Eureka客户端。Eureka Server
作为服务注册的服务端,它是服务注册中心,而系统中的其他微服务,使用 Eureka
的客户端连接到 Eureka Server
服务端,并维持心跳连接,Eureka客户端是一个Java客户端,用来简化与服务器的交互、负载均衡,服务的故障切换等;有了Eureka注册中心,系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
2. Eureka与Zookeeper的比较
著名的CAP
理论指出,一个分布式系统不可能同时满足C
(一致性)、A
(可用性)和P
(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A
和C
之间进行权衡,在此Zookeeper
保证的是CP
, 而Eureka
则是AP
。
-
Zookeeper保证CP
在ZooKeeper
中,当master
节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader
选举,但是问题在于,选举leader
需要一定时间, 且选举期间整个ZooKeeper
集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得ZooKeeper
集群失去master
节点是大概率事件,虽然服务最终能够恢复,但是在选举时间内导致服务注册长期不可用是难以容忍的。 -
Eureka保证AP
Eureka
优先保证可用性,Eureka
各个节点是平等的,某几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka
的客户端在向某个Eureka
注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka
还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。
3. 搭建与配置 Eureka Server
Spring Cloud要使用Eureka注册中心非常简单和方便,Spring Cloud中的Eureka服务注册中心实际上也是一个Spring Boot工程,我们只需通过引入相关依赖和注解配置就能让Spring Boot构建的微服务应用轻松地与Eureka进行整合。
创建Eureka Server:
-
创建一个SpringBoot模块(我这里起名eureka-server),并且添加SpringBoot的相关依赖;
- 勾选 Web --> Spring Web
- 勾选 Spring Cloud Discovery --> Eureka Server
也可以直接添加Eureka Server的依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
如果手动添加依赖,必须添加Maven的依赖管理器,否则eureka无法被识别
<properties> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
注意:如果通过SpringBoot的开发工具创建Web工程那么这个依赖以及依赖管理是自动添加的
-
在
application.properties
文件中配置Eureka服务注册中心信息#内嵌定时tomcat的端口 server.port=9100 #设置该服务注册中心的hostname eureka.instance.hostname=localhost #由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注册中心(也是它自己)注册它自己, #设置为false表示禁止这种自己向自己注册的默认行为 eureka.client.register-with-eureka=false #表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他服务 eureka.client.fetch-registry=false #指定服务注册中心的位置 eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
-
在Spring Boot的入口类上添加一个
@EnableEurekaServer
注解,用于开启Eureka注册中心服务端@SpringBootApplication @EnableEurekaServer public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
至此,Eureka Server 搭建完毕,接下来就进行测试:
启动Spring Boot 内嵌 Tomcat ,启动成功之后,通过在浏览器地址栏访问我们的注册中心;访问地址为:
# 两个参数均为application.properties文件中配置的值
http://${eureka.instance.hostname}:${server.port}
如果按我上面配置的参数,则访问地址为:http://localhost:9100
如果访问成功,则会显示如下的页面:
4. 使用 Eureka Client 注册服务
前面搭建了服务提供者项目,接下来我们就可以将该服务提供者注册到Eureke Server,步骤如下:
-
创建服务提供者时勾选:
Spring Cloud Discovery --> Eureka Discovery Client
也可以直接添加Eureka Server的依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
如果手动添加依赖,必须添加Maven的依赖管理器,否则eureka无法被识别
<properties> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
注意:如果通过SpringBoot的开发工具创建Web工程那么这个依赖以及依赖管理是自动添加的
-
修改服务提供者的
application.properties
文件,配置服务名称和注册中心地址#指定服务名字,这个名称将在服务消费者时被调用,内容任意,建议使用模块名 spring.application.name=server-provider #指定eureka的访问地址 #注意:这个地址必须要与EurekaServer中配置的eureka.client.service-url.defaultZone参数完全一样 eureka.client.service-url.defaultZone=http://localhost:9100/eureka #指定Tomcat的端口号需要避免和其他的Tomcat端口冲突 server.port=8081
-
激活
Eureka
中的EnableEurekaClient
功能:
在Spring Boot的入口函数处,通过添加@EnableEurekaClient
注解来表明自己是一个eureka
客户端,让服务消费者可以使用eureka
注册中心
至此,Eureka Client 搭建完毕,接下来就进行测试:
启动服务提供者,启动成功之后,浏览器访问 Eureka Server
,可以看到服务提供者(如图),则说明 Eureka Client
已经将服务注册到了 Eureka Server
。
5. 从 Eureka Server 发现与消费服务
服务的调用需要 Eureka Client
和 Ribbon
两者配合起来才能实现:服务的发现由 Eureka Client
实现,而服务的消费由 Ribbon
实现
**Ribbon**
:
Ribbon
是一个基于HTTP
和 TCP
的客户端负载均衡器,当使用Ribbon
对服务进行访问的时候,它会扩展 Eureka Client
的服务发现功能,实现从 Eureka Server
中获取服务端列表,并通过 Eureka Client
来确定服务端是否己经启动。Ribbon
在 Eureka Client
服务发现的基础上,实现了对服务实例的选择策略,从而实现对服务的负载均衡消费。
(1)搭建服务消费者
搭建服务消费者,过程与服务提供者完全相同,这里就不再赘述。
注意:
- 如果在同一台机器上的话,Tomcat 端口号不能重复
- 服务的消费者也会将自己注册到注册中心,所以也需要指定
spring.application.name
参数,注意名字要唯一
(2)加入 Ribbon
前面介绍了服务的发现由 Eureka Client
实现,而服务的真正调用由Ribbon
实现,所以我们需要在调用服务提供者时使用Ribbon
来调用。只需在创建 RestTemplate
对象时,加个 @LoadBalanced
注解:
@Configuration
public class RestTemplateConfig {
//设置当前的RestTemplate启动Ribbon的负载均衡,默认负载均衡策略为轮询策略(后面有更详细的使用说明)
//如果使用Eureka以后没有启动负载均衡是不能访问服务的
@LoadBalanced
//将RestTemplate对象放到Spring容器中
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
(3)调用服务
加入了Ribbon
的支持,那么在调用时,即可改为使用服务名称来访问:
@RestController
public class TestController {
@Resource
private RestTemplate restTemplate;
@RequestMapping("/test")
public String test(){
//加入了Ribbon之后,就可以使用Eureka中的服务名来访问服务提供者,不需要再次指定服务提供者的地址了
//注意:服务名不区分大小写,其中SERVER-PROVIDER就是服务提供者的服务名
ResponseEntity<String>result= restTemplate.getForEntity("http://SERVER-PROVIDER/test",String.class);
return "基于Eureka的服务消费者-----"+result.getBody();
}
}
测试:启动服务消费者,通过在浏览器地址栏访问我们的消费者,看是否可以正常调用远程服务提供者提供的服务。
6. 服务提供者集群
服务提供者如何集群?非常简单,当两个或多个服务提供者的服务名(spring.application.name
参数)相同时,Eureka认为这两个服务为同一个服务。
在Eureka Server中会显示:
7. Eureka高可用集群
(1)概述
Eureka Server
它本身也是一个服务,它也可以看做是一个提供者,又可以看做是一个消费者,我们之前通过配置:eureka.client.register-with-eureka=false
让注册中心不注册自己,但是我们可以向其他注册中心注册自己。
Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就会形成一组互相注册的服务注册中心,进而实现服务清单的互相同步,往注册中心A上注册的服务,可以被复制同步到注册中心B上,所以从任何一台注册中心上都能查询到已经注册的服务,从而达到高可用的效果。
(2)搭建
根据刚才的介绍,Eureka
注册中心高可用集群就是各个注册中心相互注册,所以准备两个 Eureka Server
,搭建过程与之前的相同,只需要修改 application.properties
文件中的参数即可。
第一个 Eureka Server 配置:
#内嵌的tomcat的端口
server.port=9100
#设置该服务注册中心的hostname
eureka.instance.hostname=eureka9100
#由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注册中心(也是它自己)注册它自己,设置为false表示禁止这种自己向自己注册的默认行为
eureka.client.register-with-eureka=false
#表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他服务
eureka.client.fetch-registry=false
#指定服务注册中心的位置,当有多个时,通过逗号隔开
eureka.client.service-url.defaultZone=http://eureka9200:9200/eureka
第二个 Eureka Server 配置:
#内嵌的tomcat的端口
server.port=9200
#设置该服务注册中心的hostname
eureka.instance.hostname=eureka9200
#由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注册中心(也是它自己)注册它自己,设置为false表示禁止这种自己向自己注册的默认行为
eureka.client.register-with-eureka=false
#表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他服务
eureka.client.fetch-registry=false
#指定服务注册中心的位置,当有多个时,通过逗号隔开
eureka.client.service-url.defaultZone=http://eureka9100:9100/eureka
两个Eureka Server
配置的主要区别是:hostname
和port
不同,且eureka.client.service-url.defaultZone
参数均指向的对方的地址,因为使用了域名,所以要在本地hosts
文件(C:\Windows\System32\drivers\etc\hosts)中添加配置:
127.0.0.1 eureka9100
127.0.0.1 eureka9200
分别启动两个注册中心,访问两个注册中心页面,观察注册中心页面是否正常:
http://eureka9100:9100
中显示:
http://eureka9200:9200
中显示:
当有两个以上的Eureka Server时,可以通过下面的参数指定多个服务注册中心的位置,使用逗号隔开
eureka.client.service-url.defaultZone=http://eureka9100:9100/eureka,http://eureka9200:9200/eureka,...
(3)访问
当Eureka注册中心实现集群以后,那么将服务注册到任意一个Eureka的注册中心后,数据服务都会同步到其他的Eureka注册中心,但是实际应用时还是建议将服务分别注册到全部的Eureka集群的所有服务中,这个防止某个以Eureka出现故障后,服务仍然可以注册成功
Eureka Client
通过下面的参数将服务同时注册到多个注册中心:
#将服务同时注册到多个注册中心
eureka.client.service-url.defaultZone=http://eureka9100:9100/eureka/,http://eureka9200:9200/eureka/
8. Eureka Server自我保护机制
自我保护机制是Eureka
注册中心的重要特性,当Eureka
注册中心进入自我保护模式时,在Eureka Server
首页会输出如下提示信息:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
翻译成中文的意思是:紧急情况!Eureka 可能错误地声称实例未启动。续约小于阈值,因此,为了安全起见,实例不会过期。
自我保护模式被激活的条件是:在 1 分钟后,Renews (last min) < Renews threshold
。
有兴趣的可以看一下
Renews (last min)
和Renews threshold
参数的计算方式:Spring Cloud Eureka 自我保护机制
Eureka Server
在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%(简单来说:就是短时间内丢失过多客户端(可能发生了网络分区故障)),如果低于 85%,Eureka Server
会进入自我保护模式,将这些实例保护起来,让这些实例不会过期;当网络故障恢复后,该Eureka Server
节点会再自动退出自我保护模式。但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。
自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务,使用自我保护模式,可以让Eureka集群更加的健壮、稳定。开发测试阶段可以关闭,但在生产环境推荐开启。
关于自我保护常用几个配置如下:
-
通过配置项
eureka.server.enable-self-preservation
来启用/禁用自我保护模式,不设置时默认为true(开启):# false为禁用自我保护模式,默认为true eureka.server.enable-self-preservation = false
-
Eureka Client 心跳配置:
#心跳间隔:每间隔2s,向服务端发送一次心跳,证明自己依然"存活" eureka.instance.lease-renewal-interval-in-seconds=2 #没有心跳告淘汰时间:告诉服务端,如果我10s之内没有给你发心跳,就代表我故障了,将我踢出掉 eureka.instance.lease-expiration-duration-in-seconds=10
常见的提示信息:
-
在配置上,自我保护机制关闭
RENEWALS ARE LESSER THAN THE THRESHOLD. THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS. -
自我保护机制开启了
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE. -
在配置上,自我保护机制关闭了,但是一分钟内的续约数没有达到85% ,可能发生了网络分区,会有如下提示
THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
四、客户端负载均衡Ribbon
1. 概述
Ribbon
是Netflix
发布的开源项目,主要功能是提供客户端的软件负载均衡算法,是一个基于HTTP
和TCP
的客户端负载均衡工具。支持多种负载均衡算法,还支持自定义的负载均衡算法。
-
服务器端负载均衡:例如
Nginx
,通过Nginx
进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。 -
客户端负载均衡:例如
Ribbon
,客户端会有一个服务器地址列表(从注册中心上获取),在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。
Spring Cloud
对Ribbon
做了二次封装,主要是和RestTemplate
对象配合使用:Ribbon会自动化配置RestTemplate
对象,当使用RestTemplate
的服务请求,自动转换成客户端负载均衡的服务调用。通过@LoadBalanced
开启RestTemplate
对象调用时的负载均衡。
2. 使用Ribbon实现客户端负载均衡
由于Spring Cloud
对 Ribbon
的封装, 在微服务架构中使用客户端负载均衡调用非常简单。在第二章第2节,搭建服务消费者时,已经使用过 Ribbon
了,只需要如下两步:
- 启动多个服务提供者实例并注册到一个服务注册中心或是服务注册中心集群。
- 服务消费者通过被
@LoadBalanced
注解修饰过的RestTemplate
对象来调用服务提供者。
这样,我们就可以实现服务提供者的高可用以及服务消费者的负载均衡调用。
3. Ribbon负载均衡策略
Ribbon
的负载均衡策略是由IRule
接口定义,该接口由如下实现:
类 | 负载均衡策略 |
---|---|
RandomRule | 随机(基本不用) |
RoundRobinRule(默认) | 轮询(要保证机器性能相同) |
AvailabilityFilteringRule | 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问; |
WeightedResponseTimeRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略; |
RetryRule | 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务(如果使用轮询,建议使用这个,带重试的轮询) |
BestAvailableRule | 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务; |
ZoneAvoidanceRule | 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务 |
在创建RestTemplate对象时,定义一个负载均衡的配置,改变 Ribbon 默认的轮询策略:
@Configuration
public class RestTemplateConfig {
//如果使用Eureka以后没有启动负载均衡是否不能访问服务的
@LoadBalanced
//将RestTemplate对象定义到Spring容器中
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
//定义一个负载均衡的配置,用于改变默认的轮询策略
@Bean
public IRule iRule(){
//随机的负载均衡策略
return new RandomRule();
}
}
4. RestTemplate方法详解
我们之前只是简单的使用了RestTemplate
对象的getForEntity
方法,并通过配置@LoadBalanced
注解开启客户端负载均衡,这里详细的介绍一下RestTemplate
中几种常见请求方法的使用。
前面也说过了,REST
通过用HTTP
请求方式表征对资源的操作:
请求方式 | 对资源的操作 |
---|---|
GET请求 | 查询数据 |
POST请求 | 添加数据 |
PUT请求 | 修改数据 |
DELETE请求 | 删除数据 |
(1)GET请求
Get请求可以有两种方式:getForEntity()
和 getForObject()
a. getForEntity():
ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);
ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);
ResponseEntity<T> getForEntity(URI url, Class<T> responseType)
参数:
String url
和URI uri
:要调用的服务的地址,通过服务名调用,例如:http://SERVICE-PROVIDER/test ,如果有变量时,则要用{}
。- 当请求参数为
数组
时,使用数组下标:http://SERVICE-PROVIDER/test?name={0}&age={1} - 当请求参数为
Map
时,使用key:http://SERVICE-PROVIDER/test?name={name}&age={age}
- 当请求参数为
Class<T> responseType
:表示希望返回的body类型Map<String, ?> uriVariables
和Object... uriVariables
:用于对URI进行动态赋值,在Get请求中可以用来拼接请求参数,在别的请求方式中可以不指定。
响应类型ResponseEntity<T>
,它继承了HttpEntity
。封装了返回的响应信息,包括响应状态,响应头和响应体 :
getStatusCode()
:获取响应编码,类型为HttpStatusgetStatusCodeValue()
:获取响应编码的值,类型为intgetHeaders()
:获取响应头文件信息,类型为HttpHeadersgetBody()
:返回本次响应的具体数据内容,类型为 T。实际上返回值是一个Json数据,任何一个能够封装Json中属性的对象,都可以用来接收返回数据
b. getForObject():
相当于getForEntity().getBody();
,getForObject()
只能返回响应体;当不需要返回响应中的其他信息,只需要响应体信息的时候,使用这个更方便。
T getForObject(String url, Class<T> responseType, Object... uriVariables);
T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);
T getForObject(URI url, Class<T> responseType);
getForObject()
与getForEntity()
使用上完全一致,这里就不再赘述
(2)POST请求
Post请求可以有三种方式:postForEntity()
、 postForObject()
和postForLocation()
a. postForEntity():
ResponseEntity<T> postForEntity(String url, @Nullable Object request,
Class<T> responseType, Object... uriVariables);
ResponseEntity<T> postForEntity(String url, @Nullable Object request,
Class<T> responseType, Map<String, ?> uriVariables);
ResponseEntity<T> postForEntity(URI url, @Nullable Object request,
Class<T> responseType);
返回值类型和大部分参数与getForEntity()
中相同,这里只说明不同的参数
参数:
Object request
:为Post请求的参数列表,null
表示不传递参数。
注意:虽然该参数为Object
类型,但是并不是什么对象都可以作为参数,只支持Map
集合,且Map
的value
为List
类型,建议使用LinkedMultiValueMap
。该类是java.util.Map
集合接口的子类,且value
为List
类型。
b. postForObject():
相当于postForEntity().getBody();
,postForObject()
只能返回响应体;当不需要返回响应中的其他信息,只需要响应体信息的时候,使用这个更方便。
T postForObject(String url, @Nullable Object request,
Class<T> responseType, Object... uriVariables);
T postForObject(String url, @Nullable Object request,
Class<T> responseType, Map<String, ?> uriVariables);
T postForObject(URI url, @Nullable Object request,
Class<T> responseType);
c. postForLocation():
postForLocation
表示新资源提交成功之后,返回新资源的URI
,postForLocation
的参数和前面两种的参数基本一致,只不过该方法的返回值为URI
,这个只需要服务提供者返回一个URI
即可,该资源URI
表示新资源的位置。
URI postForLocation(String url, @Nullable Object request, Object... uriVariables);
URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables);
URI postForLocation(URI url, @Nullable Object request);
如果在没有特定的前提要求时,例如:后台如果没有使用PostMapping
,那么建议使用getForObject
或getForEntity
方法提交请求到后台的服务提供者,因为get
请求比post
请求速度要快
(3)PUT请求
Put请求只有一种方式,参数在Get请求和Post请求中都介绍过了,且没有返回值
void put(String url, @Nullable Object request, Object... uriVariables);
void put(String url, @Nullable Object request, Map<String, ?> uriVariables);
void put(URI url, @Nullable Object request);
Put
请求不能获取服务提供者的响应数据,因此实际工作是不建议使用,可以使用get
或post
所对应的方法来替代
(4)DELETE请求
DELETE请求也只有一种方式,用于删除指定url的数据,参数的传递方式与Get相同(url拼接)。
void delete(String url, Object... uriVariables);
void delete(String url, Map<String, ?> uriVariables);
void delete(URI url);
由于delete
方法无法获取服务提供者的响应数据因此建议使用getForEntity()
或getForObject()
方法进行替换