介绍
SpringCloud是在SPringBoot的基础上构建的,用于简化分布式系统构建的工具集. 该工具集为微服务架构中设计的配置管理, 服务发现, 智能路由, 熔断器, 控制哦总线等操作提供了一种简单的开发方式. 也就是说SpringCloud是把非常流行的微服务的技术整合到了一起,方便开发.
主要用到的技术有:
- 注册中心: Eureka
- 负载均衡: Ribbon
- 熔断器: Hystrix
- 服务通信: Feign
- 网管: Gateway
- 配置中心: config
- 消息总线: Bus
与SpringBoot版本匹配关系
鉴于SpringBoot与SpringCloud关系,SpringBoot建议采用2.1.x版本
SpringBoot | SpringCloud |
1.2.x | Angel版本 |
1.3.x | Brixton版本 |
1.4.x | Camden版本 |
1.5.x | Dalston版本、Edgware |
2.0.x | Finchley版本 |
2.1.x | Greenwich GA版本 (2019年2月发布) |
使用RestTemplate发送请求
首先我们需要了解HTTP和RPC的区别, 之前我的博客有写过Dubbo框架的使用, Dubbo就是基于RPC的
- 定义不同
- RPC 是一种技术思想而非一种规范或协议, 他可以基于HTTP或者TCP协议
- HTTP是一种协议
- 性能消耗,主要在于序列化和反序列化的耗时
- RPC,可以基于thrift实现高效的二进制传输
- HTTP,大部分是通过json来实现的,字节大小和序列化耗时都比thrift要更消耗性能
- 负载均衡
- RPC,基本都自带了负载均衡策略
- HTTP,需要配置Nginx,HAProxy来实现
- 服务治理(下游服务新增,重启,下线时如何不影响上游调用者)
- RPC,能做到自动通知,不影响上游
- HTTP,需要事先通知,修改Nginx/HAProxy/LVS(阿里开源的产品)配置
- 思想不同
- RPC是面向方法, 客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。
- HTTP是面向资源的, 客户端并不知道具体方法。客户端只想获取资源,直接发起HTTP请求,而服务端接收到请求后根据URI经过一系列的路由才定位到方法上面去
小结:RPC主要用于组件内部的服务调用,性能消耗低,传输效率高,服务治理方便。HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。推荐一篇博文,写的非常详细https://baijiahao.baidu.com/s?id=1637758852641939872&wfr=spider&for=pc
RestTemplate介绍
一、概述
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
在Spring应用程序中访问第三方REST服务与使用Spring RestTemplate类有关。RestTemplate类的设计原则与许多其他Spring *模板类(例如JdbcTemplate、JmsTemplate)相同,为执行复杂任务提供了一种具有默认行为的简化方法。
RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory方法替换为例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。
考虑到RestTemplate类是为调用REST服务而设计的,因此它的主要方法与REST的基础紧密相连就不足为奇了,后者是HTTP协议的方法:HEAD、GET、POST、PUT、DELETE和OPTIONS。例如,RestTemplate类具有headForHeaders()、getForObject()、postForObject()、put()和delete()等方法。
小结:
- RestTemplate就是Rest的HTTP客户端模板工具类,对HTTPClient的封装
- 实现对象和JSON的序列化和反序列化,也就是相互转换
- 不限定客户类型,支持: HttpClient, OKHttp , JDK原生URLConnection
最新api地址:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html
实现:
- 服务端消费方:
- 在启动类中使用@Bean注入RestTemplate对象
- 在消费者的Controller中使用 restTemplate.getForObject(url,String.class) 远程调用
/**
* @author Zuoyueer
* Date: 2019/12/25
* Time: 12:43
* @projectName SpringBoot
* @description: 启动类
*/
@SpringCloudApplication//组合注包含:SpringBootApplication,DiscoveryClient (这个注解可以多种注册中心里),EnableCircuitBreaker(熔断器)
public class EurekaClientConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientConsumerApplication.class, args);
}
/**
* 注入RestTemplate
*/
@Bean
@LoadBalanced //负载均衡,如果开启了负载均衡,那么url要变,ConsumerController中的url
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package com.zuoyueer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @author Zuoyueer
* Date: 2019/12/25
* Time: 12:45
* @projectName SpringBoot
* @description: 消费者 控制层
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
/**
* 特别注意:负载均衡在启动类RestTemplate注入的时候设置,RestTemplate整合了Ribbion(负载均衡策略)
* 没有使用负载均衡的url是 "http://"+host+":"+port+"/user/findById/" + id;
* 使用了负载均衡的url是 "http://eureka-client-provider/user/findUserById/"+id;
*
*/
@GetMapping("/getUser/{id}")
public String getUser(@PathVariable Integer id) {
//获取Eureka注册中心提供者的实例列表
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("eureka-client-provider");
//获得具体的服务
ServiceInstance serviceInstance = serviceInstances.get(0);
//如果不使用负载均衡,那么使用RestTemplate的地址是 "http://localhost:9091/api/user/findById/" + id;
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/findUserById/" + id;
String json = restTemplate.getForObject(url, String.class);
return json;
}
}
总结下: 在组件和组件之间,使用RestTemplate通信, 他是封装了HttpClient, 使用的是Rest风格来访问资源, 他通过URL之间的转换,来实现远程访问资源, 调用者不需要指定被调用者的具体方法, 只要知道地址就好了
Eureka注册中心
Eureka 是 Netflix 开发的,一个基于 REST 服务的,服务注册与发现的组件
它主要包括两个组件:Eureka Server 和 Eureka Client
Eureka Client:一个Java客户端,用于简化与 Eureka Server 的交互(通常就是微服务中的客户端和服务端)
Eureka Server:提供服务注册和发现的能力(通常就是微服务中的注册中心)
各个微服务启动时,会通过 Eureka Client 向 Eureka Server 注册自己,Eureka Server 会存储该服务的信息
也就是说,每个微服务的客户端和服务端,都会注册到 Eureka Server,这就衍生出了微服务相互识别的话题
同步:每个 Eureka Server 同时也是 Eureka Client(逻辑上的)
多个 Eureka Server 之间通过复制的方式完成服务注册表的同步,形成 Eureka 的高可用
识别:Eureka Client 会缓存 Eureka Server 中的信息
即使所有 Eureka Server 节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者(笔者已亲测)
续约:微服务会周期性(默认30s)地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)
续期:Eureka Server 会定期(默认60s)执行一次失效服务检测功能
它会检查超过一定时间(默认90s)没有Renew的微服务,则会注销该微服务节点
从图可以看出,当客户端服务通过注解等方式嵌入到程序的代码中运行时,客户端发现组件就会向注册中心注册自身提供的服务,并周期性地发送心跳来更新服务(默认时间为30s,如果连续三次心跳都不能够发现服务,那么Eureka就会将这个服务节点从服务注册表中移除)。
与此同时,客户端发现组件还会从服务端查询当前注册的服务信息并缓存到本地,即使Eureka Server出现了问题,客户端组件也可以通过缓存中的信息调用服务节点的服务。
各个服务之间会通过注册中心的注册信息以Rest方式来实现调用,并且也可以直接通过服务名进行调用。
在Eureka的服务发现机制中,包含了3个角色:服务注册中心、服务提供方和服务消费方。
服务注册中心即Eureka Server,而服务提供者和服务消费者是Eureka Client。这里的服务提供者是指提供服务的应用,可以是Spring Boot应用,也可以是其他技术平台且遵循Eureka通信机制的应用,应用在运行时会自动的将自己提供的服务注册到Eureka Server以供其他应用发现。
服务消费者就是需要服务的应用,该服务在运行时会从服务注册中心获取服务列表,然后通过服务列表知道去何处调用其他服务。服务消费者会与服务注册中心保持心跳连接,一旦服务提供者的地址发生变更时,注册中心会通知服务消费者。
需要注意的是,Eureka服务提供者和服务消费者之间的角色是可以相互转换的,因为一个服务既可能是服务消费方,同时也可能是服务提供方。
注册中心
@EnableDiscoveryClient与@EnableEurekaClient区别
- 所在的包不一样
- EnableDiscoveryClient注解, 可以使用多种注册中心
- EnableEurekaClient注解, 只能用Eureka注册中心
服务注册中心Eureka Server
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 启动类加@EnableEurekaServer 声明为注册中心
@SpringBootApplication
@EnableEurekaServer //声明为eureka的注册中心
public class EurekaServerRegistryApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerRegistryApplication.class,args);
}
}
- 编写配置文件
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
instance:
hostname: 127.0.0.1 #该服务实例所在主机名
prefer-ip-address: true #是否优先使用服务实例的IP地址,相较于hostname,默认false
client:
service-url: #注册中心地址
defaultZone: http://127.0.0.1:10086/eureka/ # 点击defaultZone查看源码,serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
register-with-eureka: false # 是否将自己注册到eureka中,指示此实例是否应将其信息注册到eureka服务器以供其他服务发现,默认为false
fetch-registry: false # 是否从eureka中获取信息,客户端是否获取eureka服务器注册表上的注册信息,默认为true
server:
enable-self-preservation: true # 启动自我保护机制,默认为true
服务提供者Eureka Client
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 启动类加@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient //开启Eureka客户端
public class EurekaClientProviderApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientProviderApplication.class,args);
}
}
- 编写Controller,Service,Dao,Pojo... 略
- 编写配置文件
server:
port: 9091
spring:
application:
name: eureka-client-provider
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: 123456
url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
jpa:
open-in-view: false #关闭警告
# 指定注册中心
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
服务消费者Eureka Client
- 导入依赖 ,和提供者一样
- 启动类加上@EnableEurekaClient, 注入RestTemplate
略
- 编写Controller,使用RestTemplate访问消费提供方
看前面的RestTemplate的使用
- 编写配置文件
server:
port: 8080
spring:
application:
name: eureka-client-consumer
# 配置Eureka Server,也就是指定注册中心
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
feign:
hystrix:
enabled: true #开启Feign的熔断器,默认是关闭的
Ribbon实现负载均衡
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。
负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们通常所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间按照专门用于负载均衡的设备,比如F5等;而软件负载均衡则是通过在服务器上安装一些用于负载均衡功能或模块等软件来完成请求分发工作,比如Nginx等。不论采用硬件负载均衡还是软件负载均衡,只要是服务端都能以类似下图的架构方式构建起来:
硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端端地址,然后进行转发。
而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端端清单来自于服务注册中心.
有一点需要注意的是,使用ribbon实现负载均衡的时候,服务名称不能用下划线。
Hystrix熔断器
- Hystrix,英文意思是豪猪,全身是刺,刺是一种保护机制。Hystrix也是Netflix公司的一款组件。
- Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库、防止出现级联失败也就是雪崩效应。
- 微服务中,一个请求可能需要多个微服务接口才能实现,会形成复杂的调用链路。
- 如果某服务出现异常,请求阻塞,用户得不到响应,容器中线程不会释放,于是越来越多用户请求堆积,越来越多线程阻塞。
- 单服务器支持线程和并发数有限,请求如果一直阻塞,会导致服务器资源耗尽,从而导致所有其他服务都不可用,从而形成雪崩效应。
原理
在Spring Cloud中,Spring Cloud Hystrix就是用来实现断路器、线程隔离等服务保护功能的。Spring Cloud Hystrix是基于Netflix的开源框架Hystrix实现的,该框架的使用目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
断路器是可以实现弹性容错的,在一定条件下它能够自动打开和关闭,其使用时主要有三种状态。
- Closed:关闭状态(前健康状况高于设定阈值),所有请求正常访问
- Open:打开状态(当前健康状况低于设定阈值),所有请求静止通过,如果设置了fallback方法,则进入该流程
- Half Open:半开状态(当断路器开关处于打开状态,经过一段时间后,断路器会自动进入半开状态。这时断路器只允许一个请求通过;当该请求调用成功时,断路器恢复到关闭状态;若该请求失败,断路器继续保持打开状态,接下来的请求会被禁止通过)
断路器的开关由关闭到打开的状态是通过当前服务健康状况(服务的健康状况=请求失败数/请求总数)和设定阈值(默认为5秒内的20次故障)比较决定的。当断路器开关关闭时,请求被允许通过断路器,如果当前健康状况高于设定阈值,开关继续保持关闭;如果当前健康状况低于设定阈值,开关则切换为打开状态。当断路器开关打开时,请求被禁止通过;如果设置了fallback方法,则会进入fallback的流程。当断路器开关处于打开状态,经过一段时间后,断路器会自动进入半开状态,这时断路器只允许一个请求通过;当该请求调用成功时,断路器恢复到关闭状态;若该请求失败,断路器继续保持打开状态,接下来的请求会被禁止通过。
服务降级方法:
spring Cloud Hystrix能保证服务调用者在调用异常服务时快速的返回结果,避免大量的同步等待,这是通过HystrixCommand的fallback方法实现的。
熔断器的核心:线程隔离和服务降级。
- 线程隔离:是指Hystrix为每个依赖服务调用一个小的线程池,如果线程池用尽,调用立即被拒绝,默认不采用排队。
- 服务降级(兜底方法):优先保证核心服务,而非核心服务不可用或弱可用。触发Hystrix服务降级的情况:线程池已满、请求超时。