文章目录
- Spring Cloud,注册中心,配置中心,原理详解
- 谈谈我个人对 spring Cloud 的理解
- 注册中心
- Eureka:
- 服务搭建
- 小结
- Ribbo - 负载均衡
- 1. 负载均衡流程
- 2. 负载均衡策略
- nacos
- 注册中心
- 1. 配置集群
- 1. 创建 namespace
- 2. 配置命名空间
- 1. nacos添加配置文件
- 2. 从nacos拉取配置
- 配置中心
- Nacos
- 1.搭建服务端
- 2.搭建客户端
- 3.动态刷新
- 4.nacos config高可用
- 5.nacos config高级配置
- Config
- Apollo
- 一、业务场景介绍
- 二、Spring Cloud核心组件:Eureka
- 三、Spring Cloud核心组件:Feign
- 四、Spring Cloud核心组件:Ribbon
- 五、Spring Cloud核心组件:Hystrix
- 六、Spring Cloud核心组件:Zuul
- 七、总结:使用及配置
- Eureka
- Feign
- Ribbon
- Hystrix
今天通过这篇文章想和大家来聊聊springCloud相关的知识点,由于现在的系统不断的扩大,业务不断的复杂,所以出现了Spring Cloud.
Spring Cloud,注册中心,配置中心,原理详解
谈谈我个人对 spring Cloud 的理解
spring Cloud 是一套分布式微服务的技术解决方案它提供了快速构建分布式系统常用的一些组件,比如说配置管理、服务的注册与发现、服务调用的负载均衡、资源隔离、熔断降级等等。
不过 Spring Cloud 只是 Spring 官方提供的一套微服务标准定义;
而真正的实现目前有两套体系用的比较多一个是 Spring Cloud Netflix 另一个是 Spring Cloud Alibaba
Spring Cloud Netflix 是基于 Netflix 这个公司的开源组件集成的一套微服务解决方案,其中的组件有:
**1. Eureka——服务注册与发现 **
2. Feign——服务调用;
**3. Ribbon——负载均衡 **
4. Hystrix——服务熔断
5. Zuul——网关;
Spring Cloud Alibaba 是基于阿里巴巴开源组件集成的一套微服务解决方案,其中包括:
- Dubbo——消息通讯
- Nacos——服务注册与发现
- Seata——事务隔离
- Sentinel——熔断降级;
有了 Spring Cloud 这样的技术生态使得我们在落地微服务架构时,不用去考虑第三方技术集成带来额外成本,只要通过配置组件来完成架构下的技术问题即可,从而,可以让我们更加侧重性能方面 ,以上这些就是我个人对 Spring Cloud 的理解!
注册中心
需求:当一个服务提供者 Service 部署了多个实例交给 User 远程调用时:
- 服务消费者 User 应该调用哪个实例,如何获取其对应地址和端口?
- User 如何获知实例是否健康?
注册中心作用:
- 帮助管理服务,并帮助服务调用者选择并调用服务
- 实时监测服务实例是否健康
Eureka:
构成:
eureka-server:服务端,注册中心
记录服务信息,心跳监控
eureka-client:客户端,
服务提供者(注册到服务端,定期向服务端发送心跳)、服务消费者(从服务端拉取服务列表,基于负载均衡选择服务)
作用:
服务注册: Service实例启动后,会将自己的信息注册到 eureka服务端
服务拉取: User 根据 实例名 获取 Service 地址列表
服务搭建
配置文件:
我们需要将 eureka 注册到spring容器中,所以需要在配置文件中做相关配置。
server:
port: 8099
spring:
application:
name: eureka_server
eureka:
client:
# 配置eureka服务地址
service-url:
defaultZone: http://127.0.0.1:8099/eureka
项目启动:
为了让项目能启动 eureka,需要在启动类上加一个注解:@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
启动项目,访问地址:http://127.0.0.1:8099:
注册的服务名称就是配置文件中的名称的大写。
服务注册
在上一步,已经将 eureka-server (eureka服务中心)搭建完毕,现在就开始注册服务实例了。
注意:
无论是 服务提供者 还是 服务消费者,他们的身份都是 eureka-client
记得添加 spring-boot-starter-web 的依赖,不然会报错:Field optionalArgs in org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration required a bean of type ‘com.netflix.discovery.AbstractDiscoveryClientOptionalArgs’ that could not be found.
服务发现、服务注册统一都封装在eureka-client依赖。
需要在配置文件中配置 eureka-server 的地址。
spring:
application:
name: service_provider
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8099/eureka
启动多个实例
为了让项目能启动 eureka,需要在启动类上加一个注解:@EnableEurekaClient。
@SpringBootApplication
@EnableEurekaClient
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
我们可以通过 IDEA 自带功能模仿启动多个服务实例。
- 打开 Service 面板
****
复制原来的 provider 启动配置
查看 eureka注册中心
服务发现
依赖导入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件:
spring:
application:
name: service_user
server:
port: 8084
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8099/eureka
服务拉取和负载均衡
服务拉取:
-
修改 controller 代码,将 url 路径的 ip、端口 修改为 服务名:
@RestController @RequestMapping("user") public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping("/{id}") public Book getBookById(@PathVariable("id") Integer id) { // String url = "http://127.0.0.1:8081/provider" + id; String url = "http://service_provider/provider"; if (id != null) { url = url + id; } Book book = restTemplate.getForObject(url, Book.class); return book; } }
注册 RestTemplate 的时候加上注解 @LoadBalanced
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
接口调用:
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{id}")
public Book getBookById(@PathVariable("id") Integer id) {
// String url = "http://127.0.0.1:8081/provider" + id;
String url = "http://PROVIDER/pro/";
if (id != null) {
url = url + id;
}
Book book = restTemplate.getForObject(url, Book.class);
return book;
}
}
访问接口:
访问报错:
解决方案:
多测试几下接口,可以发现,user一会儿调用的是 provider:8081 一会儿调用的是 provider:8082。这就是负载均衡算法选择的。
小结
eureka-server 搭建:
- 引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 启动类添加 @EnableEurekaServer
- 配置文件配置 eureka 地址
server:
port: 8099
spring:
application:
name: server
eureka:
client:
# 配置eureka服务地址
service-url:
defaultZone: http://127.0.0.1:8099/eureka
服务注册:
- 引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 启动类添加 @EnableEurekaClient
- 配置文件配置 eureka 地址
server:
port: 8081
spring:
application:
name: provider
eureka:
client:
# 配置eureka服务地址
service-url:
defaultZone: http://127.0.0.1:8099/eureka
服务发现:
- 引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 启动类添加 @EnableEurekaClient
- 配置文件配置 eureka 地址
server:
port: 8089
spring:
application:
name: user
eureka:
client:
# 配置eureka服务地址
service-url:
defaultZone: http://127.0.0.1:8099/eureka
Ribbo - 负载均衡
1. 负载均衡流程
Ribbon负载均衡流程图:
http://PROVIDER/pro/4 并非真实的地址,这个需要Ribbon负载均衡去拦截,然后选择具体的服务地址。而,Ribbon就是通过 LoadBalancerInterceptor 的 intercept 方法来实现拦截请求并解析选择地址。
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
负载均衡流程:
2. 负载均衡策略
Ribbon的负载均衡策略是由 IRule 接口来定义的。
自定义负载均衡策略:
- 方式一: 在user中,@Bean 注入自定义 IRule
@Bean
public IRule randomRule(){
return new RandomRule();
}
- 方式二: 在user,配置文件修改 IRule
provider: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
注意:
方式一的作用范围是:在访问任何微服务的时候,都是使用 RandomRule 负载均衡策略;
方式二的作用范围是:在访问 provider 微服务的时候才是采用 RandomRule 策略,其他的还是使用默认策略。
加载策略:
Ribbon默认采用懒加载,即会在第一次访问时才会去创建 LoadBalanceClient ,所以会在第一次请求的时候花上较长的等待时间。
可以通过配置文件更改加载策略为饿加载策略,即初始化时就创建 LoadBalanceClient ,降低第一次访问的耗时。
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: provider # 指定饥饿加载的服务名称
nacos
概念:
服务注册中心
作用:
心跳检查: 不同于 eureka 只能 Service实例 主动发起心跳,nacos 对于非临时实例可以主动发起心跳检查
临时心跳检查异常的会被剔除出 服务地址列表
- 非临时心跳检查异常的不会被剔除
- 定时推送变更: nacos 支持服务列表变更的消息推送模式,服务列表更新更及时
注册中心
引入依赖、修改配置
父工程添加 SpringCloudAlibaba 的管理依赖。
<!--spring-cloud-alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
子工程中注释掉 eureka 依赖,引入 nacos 依赖。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件配置nacos地址:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
启动 user 和 provider :
注意: eureka 注册名会变成大写,nacos 不会,所以改成 nacos 之后,需要把访问地址改成小写。
请求测试:
服务分级存储模型
一个服务可以拥有多个实例,如:provider 的 8081 和 8082,如果这些实例分布于全国不同的机房,如:provider:8081 在成都机房、provider:8082 在重庆机房,Nacos就将同一机房内的实例 划分为一个集群。
即,一个服务可以拥有多个集群,一个集群中可以拥有多个实例。
微服务相互之间访问时,访问本地的速度更快,所以应该尽可能访问相同集群的实例,只有当本集群内存不够时,才去访问其他集群。
1. 配置集群
配置服务集群:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: CD
VM Options:
-Dserver.port=8082 -Dspring.cloud.nacos.discovery.cluster-name=CQ
2. 同集群优先的负载均衡
默认的 ZoneAvoidanceRule 负载均衡策略并不能实现据同集群优先来实现负载均衡,因此Nacos中提供了一个NacosRule
的实现,可以优先从同集群中挑选实例。
修改负载均衡策略:
- 方式一:
@Bean
public IRule nacosRule(){
return new NacosRule();
}
- 方式二:
provider: # 访问的服务名
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
3. 权重配置
服务器的性能之间存在差异,为了让性能能好的服务器承担更多的用户请求,Nacos提供了权重配置来控制访问率:权重越大、访问率越高。
注意: 权重为0的服务永远不会被访问。
4. 环境隔离
Nacos提供了namespace来实现环境隔离功能。
- nacos中可以有多个namespace
- namespace下可以有group、service等
- 不同namespace之间相互隔离(不同namespace的服务互相不可见)
1. 创建 namespace
Nacos默认的namespace是:public
命名空间
Nacos创建命名空间流程:
2. 配置命名空间
spring:
cloud:
nacos:
discovery:
namespace: e11eb3bc-8eed-4cdd-93f0-7a6c01b85eb4 # 命名空间,填ID
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置为永久实例
3. 配置管理
Nacos不仅可以担任微服务的注册中心,还可以担任配置管理。
统一配置管理
可以使用统一配置管理来处理因为部署的微服务数量过多,配置繁杂等问题。
Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。
注意: 只把那些需要热更新的配置文件交给Nacos
1. nacos添加配置文件
Data ID: 配置文件id:服务名称-profile.后缀名 。
2. 从nacos拉取配置
spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:
引入nacos-config依赖:
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
添加bootstrap.yml
spring:
application:
name: user # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据
s
p
r
i
n
g
.
a
p
p
l
i
c
a
t
i
o
n
.
n
a
m
e
−
{spring.application.name}-
spring.application.name−{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。
读取nacos配置
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
启动报错: org.springframework.beans.factory.BeanCreationException: Error creating bean with name xxx
解决方案:
检查 namespace,配置文件与服务要在同一个 namespace 中
名称是否拼错
升级 nacos 版本
@Value 切换为 @NacosValue
配置热更新
配置热更新: 修改nacos中的配置后,微服务中无需重启即可让配置生效。
实现方式:
方式一: 在@Value注入的变量所在类上添加注解 @RefreshScope
方式二:
使用 @ConfigurationProperties 注解代替@Value注解
在 user 服务中,添加一个类,读取patterrn.dateformat属性:
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
在UserController中使用这个类代替@Value:
@Autowired
private PatternProperties patternProperties;
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat));
}
配置共享
其实微服务启动时,会去nacos读取多个配置文件,因为nacos管理的配置文件不包含环境信息,可以被多个环境共享。
只需要取名的时候,不加上 profile 就能被共享,如下:
配置共享时的优先级:
配置中心
Nacos
微服务-配置中心
微服务架构中存在很多服务,每个服务都有自己的配置文件,这些配置文件如果集成在服务中,也是难以维护的,于是便可以使用配置中心来管理这些配置文件。
nacos既可以作为 注册中心 ,也可以作为 配置中心 ,使用nacos配置中心,你可以将服务里的配置文件写在nacos上通过nacos面板来修改管理服务的配置而无需停止服务。
如果使用nacos作为注册中心而不作为配置中心,添加配置:spring.cloud.nacos.config.enabled =false 即可。
可以以请求方式发布、获取配置:
发布配置:post,http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test&content=HelloWorld
获取配置:get,http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test
1.搭建服务端
nacos服务端搭建是十分容易的,只需下载+配置+启动就行,参考注册中心这篇:点击前往
建议:使用mysql作为持久化。
2.搭建客户端
客户端即每个配置要被管理的服务,服务的搭建还如往常一样搭建,只不过配置文件发生一些变化,以下就认为你已经创建了一个服务模块/项目,并以此起步。
1.客户端引入nacos config依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2.创建一个bootstrap.yml(或properties),配置nacos config的配置 :
spring:
cloud:
nacos:
config: #nacos的信息,添加后可以省略注册中心的nacos此类信息
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
contextPath: /nacos
#--------------------------
file-extension: yaml #显示的声明dataId文件扩展名
3.application.yml(或properties),除特殊配置外,只留下下面2个配置:
server:
port: 41150
spring:
application:
name: server-producer
到这里你会发现你有2个配置文件: bootstrap.yml、application.yml 。这时你可能就有疑问了,不是说好的将配置交给配置中心管理了吗,怎么服务里还变多了呢。
那么我来给你解释一下吧:
bootsteap.yml:这里只配置配置中心的信息,服务通过它去获取服务所需配置,而且这里的内容基本不变。
application.yml:这里只配置端口+服务名,因为首次启动服务时,需要通过它注册达到nacos,然后才能获取配置。
可以看出,上面的配置基本上就是固定的,而且基本上不会被修改,实际项目中配置文件远远不止这些,其他配置都将被放在配置中心统一管理。
4.在nacos配置中心添加配置文件 :
点击添加后页面如下:配置名要和服务名一致,后缀带不带都行。
第一次发布配置后,重启项目即可。之后修改配置都不需要启动服务。
服务与配置文件的匹配:
文件名=服务名,建议显示指定配置文件后缀。
多同名配置会互相覆盖,比如server、server.yml在指定yml格式后,两个文件会覆盖。
更多特性自己摸索,使用:服务名.yml 并 在bootstrap.yml指定格式为yaml 一定能用。
测试一下效果:按照阿里的示例进行举例。
在客户端创建一个测试类,并在nacos上配置中文件添加属性,然后注入到属性中,启动后控制台就会打印。
@Configuration
public class TestConfig {
@Value("${user.names}")
public String test1;
@Value("${user.pwds}")
public String test2;
@PostConstruct
public void getUser(){
System.out.println("name=" + test1 + "|pwd=" + test2);
}
}
启动后,会发现打印的数据就是配置中心上的数据了。
3.动态刷新
此节代码来自于:阿里云-知行动手实验室
配置发生变化后,服务中绑定的属性值将刷新,会刷新的情况有3种:
- 类上加注解:@RefreshScope、类种非静态属性加@Value注解
@RestController
@RefreshScope
public class NacosConfigDemo {
@Value("${user.name}")
private String userName;
@Value("${user.age}")
private int userAge;
@PostConstruct
public void init() {
System.out.printf("[init] user name : %s , age : %d%n", userName, userAge);
}
@RequestMapping("/user")
public String user() {
return String.format("[HTTP] user name : %s , age : %d", userName, userAge);
}
}
- 类上加注解:@RefreshScope、@ConfigurationProperties
@RefreshScope
@ConfigurationProperties(prefix = "user")
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 使用Nacos Config监听来对Bean属性实施监听。
@RestController
@RefreshScope
@EnableConfigurationProperties(User.class)
public class NacosConfigDemo {
@Value("${user.name}")
private String userName;
@Value("${user.age}")
private int userAge;
@PostConstruct
public void init() {
System.out.printf("[init] user name : %s , age : %d%n", userName, userAge);
}
@PreDestroy
public void destroy() {
System.out.printf("[destroy] user name : %s , age : %d%n", userName, userAge);
}
@RequestMapping("/user")
public String user() {
return String.format("[HTTP] user name : %s , age : %d", userName, userAge);
}
}
4.nacos config高可用
nacos是配置中心也是注册中心,实现nacos的高可用和 注册中心 高可用一样。
5.nacos config高级配置
namespace命名空间
nacos可以实现配置文件之间隔离,不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置,可以实现不同开发环境(开发、测试、生产等)配置文件的隔离。
创建命名空间也很容易
切换命名空间
创建后,在客户端bootstrap.yml添加如下配置进行绑定:
spring:
cloud:
nacos:
config:
namespace: c70a9f91-a687-427f-97bb-5a6e54289a6e
Group分组
在创建配置时可以指定分组,自定义分组在bootstrap.yml添加如下:
spring:
cloud:
nacos:
config:
group: DEVELOP_GROUP
自定义配置名
三种情况完整配置案例:
spring:
application:
name: myserver
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
extension-configs[0]: # 1.在默认组,不支持动态刷新
data-id: myconfig-1.yaml
extension-configs[1]: # 2.不在默认组,不支持动态刷新
data-id: myconfig-2.yaml
group: MY_GROUP
extension-configs[2]: # 3.不在默认的组,支持动态刷新
data-id: myconfig-3.yaml
group: MY_GROUP
refresh: true
总结:默认配置名+默认组时才会自动刷新,否则需指定refresh: true才自动刷新。
注意:
- 多个相同配置文件存在优先级extension-configs[a]里a越大,优先级越高。
- data-id结尾必须有properties/yaml/yml等扩展名。
Config
Apollo
Spring Cloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术。不过大多数讲解还停留在对Spring Cloud功能使用的层面,其底层的很多原理,很多人可能并不知晓。因此本文将通过大量的手绘图,给大家谈谈Spring Cloud微服务架构的底层原理。
实际上,Spring Cloud 是一个全家桶式的技术栈,包含了很多组件。本文先从其最核心的几个组件入手,来剖析一下其底层的工作原理。也就是 Eureka、Feign、Ribbon、Hystrix、Zuul 这几个组件。 EFRHZ
一、业务场景介绍
先来给大家说一个业务场景,假设咱们现在开发一个电商网站,要实现支付订单的功能,流程如下:
- 创建一个订单后,如果用户立刻支付了这个订单,我们需要将订单状态更新为“已支付”
- 扣减相应的商品库存
- 通知仓储中心,进行发货
- 给用户的这次购物增加相应的积分
针对上述流程,我们需要有: 订单服务、库存服务、仓储服务、积分服务。整个流程的大体思路如下:
- 用户针对一个订单完成支付之后,就会去找订单服务,更新订单状态
- 订单服务调用库存服务,完成相应功能
- 订单服务调用仓储服务,完成相应功能
- 订单服务调用积分服务,完成相应功能
至此,整个支付订单的业务流程结束
下图这张图,清晰表明了各服务间的调用过程:
好!有了业务场景之后,咱们就一起来看看Spring Cloud微服务架构中,这几个组件如何相互协作,各自发挥的作用以及其背后的原理。
二、Spring Cloud核心组件:Eureka
咱们来考虑第一个问题:订单服务想要调用库存服务、仓储服务,或者积分服务,怎么调用?
- 订单服务压根儿就不知道人家库存服务在哪台机器上啊!他就算想要发起一个请求,都不知道发送给谁,有心无力!
- 这时候,就轮到Spring Cloud Eureka出场了。Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。
咱们来看看下面的这张图,结合图来仔细剖析一下整个流程:
如上图所示,库存服务、仓储服务、积分服务中都有一个Eureka Client组件,这个组件专门负责将这个服务的信息注册到Eureka Server中。说白了,就是告诉Eureka Server,自己在哪台机器上,监听着哪个端口。而Eureka Server是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口号
订单服务里也有一个Eureka Client组件,这个Eureka Client组件会找Eureka Server问一下:库存服务在哪台机器啊?监听着哪个端口啊?仓储服务呢?积分服务呢?然后就可以把这些相关信息从Eureka Server的注册表中拉取到自己本地缓存起来。
这时如果订单服务想要调用库存服务,不就可以找自己本地的Eureka Client问一下库存服务在哪台机器?监听哪个端口吗?收到响应后,紧接着就可以发送一个请求过去,调用库存服务扣减库存的那个接口!同理,如果订单服务要调用仓储服务、积分服务,也是如法炮制。
总结一下:
- Eureka Client:负责将这个服务的信息注册到Eureka Server中
- Eureka Server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号
三、Spring Cloud核心组件:Feign
现在订单服务确实知道库存服务、积分服务、仓库服务在哪里了,同时也监听着哪些端口号了。但是新问题又来了:难道订单服务要自己写一大堆代码,跟其他服务建立网络连接,然后构造一个复杂的请求,接着发送请求过去,最后对返回的响应结果再写一大堆代码来处理吗?
这是上述流程翻译的代码片段,咱们一起来看看,体会一下这种绝望而无助的感受!!!
友情提示,前方高能:
看完上面那一大段代码,有没有感到后背发凉、一身冷汗?实际上你进行服务间调用时,如果每次都手写代码,代码量比上面那段要多至少几倍,所以这个事压根儿就不是地球人能干的。
既然如此,那怎么办呢?别急,Feign早已为我们提供好了优雅的解决方案。来看看如果用Feign的话,你的订单服务调用库存服务的代码会变成啥样?
看完上面的代码什么感觉?是不是感觉整个世界都干净了,又找到了活下去的勇气!没有底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 FeignClient 接口,然后调用那个接口就可以了。人家Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等。这一系列脏活累活,人家Feign全给你干了。
那么问题来了,Feign是如何做到这么神奇的呢?很简单,Feign的一个关键机制就是使用了动态代理。咱们一起来看看下面的图,结合图来分析:
- 首先,如果你对某个接口定义了**@FeignClient**注解,Feign就会针对这个接口创建一个动态代理
- 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心
- Feign的动态代理会根据你在接口上的**@RequestMapping**等注解,来动态构造出你要请求的服务的地址
- 最后针对这个地址,发起请求、解析响应
四、Spring Cloud核心组件:Ribbon
说完了Feign,还没完。现在新的问题又来了,如果人家库存服务部署在了5台机器上,如下所示:
- 192.168.169:9000
- 192.168.170:9000
- 192.168.171:9000
- 192.168.172:9000
- 192.168.173:9000
这下麻烦了!人家Feign怎么知道该请求哪台机器呢?
- 这时Spring Cloud Ribbon就派上用场了。Ribbon就是专门解决这个问题的。它的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上
- Ribbon 的负载均衡默认使用的最经典的Round Robin轮询算法。这是啥?简单来说,就是如果订单服务对库存服务发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器、第4台机器、第5台机器,接着再来—个循环,第1台机器、第2台机器。。。以此类推。
此外,Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体如下:
- 首先Ribbon会从 Eureka Client里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号。
- 然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器
- Feign就会针对这台机器,构造并发起请求。
对上述整个过程,再来一张图,帮助大家更深刻的理解:
五、Spring Cloud核心组件:Hystrix
在微服务架构里,一个系统会有很多的服务。以本文的业务场景为例:订单服务在一个业务流程里需要调用三个服务。现在假设订单服务自己最多只有100个线程可以处理请求,然后呢,积分服务不幸的挂了,每次订单服务调用积分服务的时候,都会卡住几秒钟,然后抛出—个超时异常。
咱们一起来分析一下,这样会导致什么问题?
- 如果系统处于高并发的场景下,大量请求涌过来的时候,订单服务的100个线程都会卡在请求积分服务这块。导致订单服务没有一个线程可以处理请求
- 然后就会导致别人请求订单服务的时候,发现订单服务也挂了,不响应任何请求了
上面这个,就是微服务架构中恐怖的服务雪崩问题,如下图所示:
如上图,这么多服务互相调用,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。比如积分服务挂了,会导致订单服务的线程全部卡在请求积分服务这里,没有一个线程可以工作,瞬间导致订单服务也挂了,别人请求订单服务全部会卡住,无法响应。
但是我们思考一下,就算积分服务挂了,订单服务也可以不用挂啊!为什么?
- 我们结合业务来看:支付订单的时候,只要把库存扣减了,然后通知仓库发货就OK了
- 如果积分服务挂了,大不了等他恢复之后,慢慢人肉手工恢复数据!为啥一定要因为一个积分服务挂了,就直接导致订单服务也挂了呢?不可以接受!
现在问题分析完了,如何解决?
这时就轮到Hystrix闪亮登场了。Hystrix是隔离、熔断以及降级的一个框架。啥意思呢?说白了,Hystrix会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。
打个比方:现在很不幸,积分服务挂了,会咋样?
当然会导致订单服务里那个用来调用积分服务的线程都卡死不能工作了啊!但由于订单服务调用库存服务、仓储服务的这两个线程池都是正常工作的,所以这两个服务不会受到任何影响。
这个时候如果别人请求订单服务,订单服务还是可以正常调用库存服务扣减库存,调用仓储服务通知发货。只不过调用积分服务的时候,每次都会报错。**但是如果积分服务都挂了,每次调用都要去卡住几秒钟干啥呢?有意义吗?当然没有!**所以我们直接对积分服务熔断不就得了,比如在5分钟内请求积分服务直接就返回了,不要去走网络请求卡住几秒钟,这个过程,就是所谓的熔断!
**那人家又说,兄弟,积分服务挂了你就熔断,好歹你干点儿什么啊!别啥都不干就直接返回啊?**没问题,咱们就来个降级:每次调用积分服务,你就在数据库里记录一条消息,说给某某用户增加了多少积分,因为积分服务挂了,导致没增加成功!这样等积分服务恢复了,你可以根据这些记录手工加一下积分。这个过程,就是所谓的降级。
为帮助大家更直观的理解,接下来用一张图,梳理一下Hystrix隔离、熔断和降级的全流程:
六、Spring Cloud核心组件:Zuul
说完了Hystrix,接着给大家说说最后一个组件:Zuul,也就是微服务网关。**这个组件是负责网络路由的。**不懂网络路由?行,那我给你说说,如果没有Zuul的日常工作会怎样?
假设你后台部署了几百个服务,现在有个前端兄弟,人家请求是直接从浏览器那儿发过来的。打个比方:人家要请求一下库存服务,你难道还让人家记着这服务的名字叫做inventory-service?部署在5台机器上?就算人家肯记住这一个,你后台可有几百个服务的名称和地址呢?难不成人家请求一个,就得记住一个?你要这样玩儿,那真是友谊的小船,说翻就翻!
上面这种情况,压根儿是不现实的。所以一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。
而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。
七、总结:使用及配置
最后再来总结一下,上述几个Spring Cloud核心组件,在微服务架构中,分别扮演的角色:
-
Eureka:各个服务启动时,Eureka Client 都会将服务注册到 Eureka Server,并且 Eureka Client 还可以反过来从 Eureka Server 拉取注册表,从而知道其他服务在哪里
-
Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
-
Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
-
Hystrix:发起请求是通过 Hystrix 的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
-
Zuul:如果前端、移动端要调用后端系统,统一从 Zuul 网关进入,由Zuul网关转发请求给对应的服务
以上就是我们通过一个电商业务场景,阐述了 Spring Cloud 微服务架构几个核心组件的底层原理。
文字总结还不够直观?没问题!我们将Spring Cloud的5个核心组件通过一张图串联起来,再来直观的感受一下其底层的架构原理:
Eureka
一、erueka 客户端配置
1、Eureka 启禁用
eureka.client.enabled=true
2、Eureka 连接超时时间
eureka.client.eureka-server-connect-timeout-seconds=5
eureka.client.eureka-connection-idle-timeout-seconds=30
3、Eureka Server等待超时时间
eureka.client.eureka-server-read-timeout-seconds=8
4、拉取Eureka注册信息
eureka.client.fetch-registry=true
5、注册自身到Eureka
eureka.client.register-with-eureka=true
6、过滤状态存活的实例
eureka.client.filter-only-up-instances=true
7、注册实例的名称
eureka.instance.appname=unknown
8、健康检查相对路径
eureka.instance.health-check-url-path=/health
9、HOME页面相对路径
eureka.instance.home-page-url-path=/
10、服务续约到期时间
eureka.instance.lease-expiration-duration-in-seconds=90
11、服务续约间隔时间
eureka.instance.lease-renewal-interval-in-seconds=30
12、注册元数据
eureka.instance.metadata-map=
13、通过配置文件找到namespace,忽略springcloud的配置
eureka.instance.namespace=
14、注册是否显示IP地址(第一个非回环地址)
eureka.instance.prefer-ip-address=false
15、注册时使用的IP地址
eureka.instance.ip-address=
16、压缩Eureka Server的数据
eureka.client.g-zip-content=true
17、心跳执行器的线程池初始值
eureka.client.heartbeat-executor-thread-pool-size=2
18、最初复制实例信息到Eureka服务器所需的时间
eureka.client.initial-instance-info-replication-interval-seconds=40
19、同步实例变更信息到Eureka服务到周期
eureka.client.instance-info-replication-interval-seconds=30
20、从Eureka服务器拉取服务信息周期
eureka.client.registry-fetch-interval-seconds=30
21、Eureka Server地址
eureka.client.serviceUrl.defaultZone=xxx,xxx,xxx
二、eureka 服务端配置
1、注册实例
eureka.instance.registry.default-open-for-traffic-count=1
eureka.instance.registry.expected-number-of-renews-per-min=1
2、服务面板
eureka.dashborad.enabled=true
eureka.dashboard.path=/
3、自我保护
eureka.server.enable-self-preservation: true
eureka.server.eviction-interval-timer-in-ms: 60000
eureka.server.renewal-percent-threshold: 0.85
4、限流
eureka.server.rate-limiter-enabled: false
eureka.server.rate-limiter-throttle-standard-clients: false
eureka.server.rate-limiter-burst-size: 10
eureka.server.rate-limiter-full-fetch-average-rate: 100
eureka.server.rate-limiter-registry-fetch-average-rate: 500
5、相应缓存
eureka.server.response-cache-auto-expiration-in-seconds: 180
eureka.server.response-cache-update-interval-ms: 30000
eureka.server.use-read-only-response-cache: true
6、安全校验
#启用安全校验
security.basic.enabled=true
#授权用户名
security.user.name=root
#授权密码
security.user.password=123456
三、高可用配置
1、节点数据同步
eureka.server.batch-replication: false
eureka.enable-replicated-request-compression: false
2、同步线程池
eureka.server.min-threads-for-status-replication: 1
eureka.server.max-threads-for-status-replication: 1
eureka.server.max-idle-thread-in-minutes-age-for-status-replication: 10
eureka.server.min-threads-for-peer-replication: 5
eureka.server.max-threads-for-peer-replication: 20
eureka.server.max-idle-thread-age-in-minutes-for-peer-replication: 15
eureka.server.number-of-replication-retries: 5
3、定时任务
eureka.server.peer-eureka-status-refresh-time-interval-ms: 3000
eureka.server.peer-eureka-nodes-update-interval-ms: 600000
eureka.server.min-available-instances-for-peer-replication: -1
4、连接
eureka.server.peer-node-total-connections-per-host: 500
eureka.server.peer-node-total-connections: 1000
eureka.server.peer-node-read-timeout-ms: 200
eureka.server.peer-node-connect-timeout-ms: 200
eureka.server.peer-node-connection-idle-timeout-seconds: 30
5、重置注册
eureka.server.registry-sync-retries: 0
eureka.server.registry-sync-retry-wait-ms: 30000
Feign
使用及配置url: SpringCloud:Feign的使用及配置_feignclient配置_Poetry-Distance的博客-CSDN博客
Ribbon
Hystrix
@RestController
@RequestMapping("/circuitBreaker")
public class CircuitBreakConfigController {
@GetMapping
@HystrixCommand(
fallbackMethod = "fallback",
commandProperties = {
@HystrixProperty(
name = "execution.isolation.thread.timeoutInMilliseconds",
value = "1000"
),
@HystrixProperty(
name = "circuitBreaker.requestVolumeThreshold",
value = "4"
),
@HystrixProperty(
name = "circuitBreaker.errorThresholdPercentage",
value = "50"
),
@HystrixProperty(
name = "metrics.rollingStats.timeInMilliseconds",
value = "60000"
),
@HystrixProperty(
name = "circuitBreaker.sleepWindowInMilliseconds",
value = "60000"
)
})
public ResponseEntity<String> demo() {
try {
// 模拟接口请求
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ResponseEntity<>("Hello!", HttpStatus.OK);
}
private ResponseEntity<String> fallback() {
return new ResponseEntity<>("Timeout!", HttpStatus.OK);
}
}