“Springcloud”的版本间的差异
(未显示同一用户的8个中间版本) | |||
第321行: | 第321行: | ||
===== RestTemplate的DELETE请求 ===== | ===== RestTemplate的DELETE请求 ===== | ||
restTemplate.delete(); | restTemplate.delete(); | ||
==== Spring Cloud Hystrix服务熔断 ==== | |||
==== Hystrix是什么 ==== | |||
在微服务架构中,我们是将一个单体应用拆分成多个服务单元,各个服务单元之间通过注册中心彼此发现和消费对方提供的服务,每个服务单元都是单独部署,在各自的服务进程中运行,服务之间通过远程调用实现信息交互,那么当某个服务的响应太慢或者故障,又或者因为网络波动或故障,则会造成调用者延迟或调用失败,当大量请求到达,则会造成请求的堆积,导致调用者的线程挂起,从而引发调用者也无法响应,调用者也发生故障。 | |||
比如电商中的用户下订单,我们有两个服务,一个下订单服务,一个减库存服务,当用户下订单时调用下订单服务,然后下订单服务又调用减库存服务,如果减库存服务响应延迟或者没有响应,则会造成下订单服务的线程挂起等待,如果大量的用户请求下订单,或导致大量的请求堆积,引起下订单服务也不可用,如果还有另外一个服务依赖于订单服务,比如用户服务,它需要查询用户订单,那么用户服务查询订单也会引起大量的延迟和请求堆积,导致用户服务也不可用。 | |||
所以在微服务架构中,很容易造成服务故障的蔓延,引发整个微服务系统瘫痪不可用。 | |||
为了解决此问题,微服务架构中引入了一种叫熔断器的服务保护机制。 | |||
熔断器也有叫断路器,他们表示同一个意思,最早来源于微服务之父Martin Fowler的论文CircuitBreaker一文。“熔断器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,能够及时切断故障电路,防止发生过载、发热甚至起火等严重后果。 | |||
微服务架构中的熔断器,就是当被调用方没有响应,调用方直接返回一个错误响应即可,而不是长时间的等待,这样避免调用时因为等待而线程一直得不到释放,避免故障在分布式系统间蔓延。 | |||
Spring Cloud Hystrix实现了熔断器、线程隔离等一系列服务保护功能。该功能也是基于Netflix的开源框架Hystrix实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。 | |||
==== Hystrix快速入门 ==== | |||
在SpringCloud中使用熔断器Hystrix是非常简单和方便的,只需要简单三步即可: | |||
# 添加依赖<syntaxhighlight lang="xml"> | |||
<!--Spring Cloud熔断器起步依赖--> | |||
<dependency> | |||
<groupId>org.springframework.cloud</groupId> | |||
<artifactId>spring-cloud-starter-hystrix</artifactId> | |||
<version>1.4.4.RELEASE</version> | |||
</dependency> | |||
</syntaxhighlight> | |||
# 在入口类中使用@EnableCircuitBreaker注解开启断路器功能,也可以使用一个名为@SpringCloudApplication的注解代替主类上的三个注解。 | |||
# 在调用远程服务的方法上添加注解:@HystrixCommand(fallbackMethod="error")。 | |||
===== 服务消费者Hystrix测试 ===== | |||
Hystrix默认超时时间是1000毫秒,如果你后端的响应超过此时间,就会触发断路器; | |||
修改hystrix的默认超时时间:<syntaxhighlight lang="java"> | |||
@HystrixCommand(fallbackMethod="error", commandProperties={ | |||
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1500")}) //熔断器,调用不通,回调error()方法 | |||
public String webHello () { | |||
} | |||
</syntaxhighlight> | |||
===== Hystrix的服务降级 ===== | |||
有了服务的熔断,随之就会有服务的降级,所谓服务降级,就是当某个服务熔断之后,服务端提供的服务将不再被调用,此时由客户端自己准备一个本地的fallback回调,返回一个默认值来代表服务端的返回。 | |||
这种做法,虽然不能得到正确的返回结果,但至少保证了服务的可用,比直接抛出错误或服务不可用要好很多,当然这需要根据具体的业务场景来选择。 | |||
===== Hystrix的异常处理 ===== | |||
=== 微服务生态与 Spring Cloud Alibaba === | |||
==== 通用的微服务架构应包含哪些组件 ==== | |||
相对于单体式架构的简单粗暴,微服务架构将应用打散,形成多个微服务进行独立开发、测试、部署与运维。虽然从管理与逻辑上更符合业务需要,但微服务架构也带来了诸多急需解决的核心问题: | |||
# 如何发现新服务节点以及检查服务节点的状态? | |||
# 如何发现服务及负载均衡如何实现? | |||
# 服务间如何进行消息通信? | |||
# 如何对使用者暴露服务 API? | |||
# 如何集中管理众多服务节点的配置文件? | |||
# 如何收集服务节点的日志并统一管理? | |||
# 如何实现服务间调用链路追踪? | |||
# 如何对系统进行链路保护,避免微服务雪崩? | |||
可以发现,上述这些问题并不是针对某种语言或某种技术的,任何软件厂商要构建微服务架构就必须面对这些问题,要么独立开发要么将已有多种技术整合形成整体解决方案。好在经过多年沉淀,业内已经有了标准答案,下图清晰的说明微服务架构需要的标准组件。 | |||
[[文件:微服务架构组件.png|居中|489x489像素]] | |||
下面介绍每种组件的职责。 | |||
'''注册中心(Service Registry):'''注册中心是微服务架构最核心的组件。它起到新服务节点的注册与状态维护的作用,通过注册中心解决了上述问题 1。微服务节点在启动时会将自身的服务名称、IP、端口等信息在注册中心中进行登记,注册中心会定时检查该节点的运行状态。注册中心通常会采用心跳机制最大程度保证其持有的服务节点列表都是可用的。 | |||
[[文件:服务注册流程.png|居中|913x913像素]] | |||
'''负载均衡(Load Balance):'''负载均衡器解决了问题 2。通常在微服务彼此调用时并不是直接通过IP、端口直接访问,而是首先通过服务名在注册中心查询该服务拥有哪些可用节点,然后注册中心将可用节点列表返回给服务调用者,这个过程称为“服务发现”。因服务高可用的要求,服务调用者会接收到多个可用节点,必须要从中进行选择,因此在服务调用者一端必须内置负载均衡器,通过负载均衡策略选择适合的节点发起实质的通信请求。 | |||
'''服务通信(Communication):'''服务通信组件解决了问题 3。在微服务定义中阐述服务间通信采用轻量级协议,通常是 HTTP RESTful 风格。但因 RESTful 风格过于灵活,必须加以约束,通常在应用时对其进行上层封装,例如在 Spring Cloud 中就提供了 Feign 和 RestTemplate 两种技术屏蔽底层实现 RESTful 通信细节,所有开发者是基于封装后统一的SDK进行开发,这有利于团队间协作。 | |||
'''API 服务网关(API Gateway):'''服务网关解决问题 4。对于最终用户来说,微服务的通信与各种实现细节应该是透明的,用户只需关注他要使用的 API 接口即可。因此微服务架构引入服务网关控制用户的访问权限。服务网关是外部环境访问内部微服务的唯一途径,在这个基础上还可以扩展出其他功能,例如:用户认证与授权、容错限流、动态路由、A/B测试、灰度发布等。 | |||
'''配置中心(Config Management):'''配置中心解决问题 5。微服务架构下,微服务节点都包含自己的各种配置文件,如JDBC地址、自定义配置、环境配置等。要知道互联网公司微服务节点可能是成千上万个,如果这些配置信息分散存储在节点上,如发生配置变更就必须逐个调整,这必将给运维人员带来巨大的工作量。配置中心便因此而生,通过部署配置中心服务器,将原本分散的配置文件从应用中剥离,集中转存到配置中心。一般配置中心会提供 UI 界面,可以方便快捷的实现大规模集群的配置调整。 | |||
[[文件:配置文件.png|居中|875x875像素]] | |||
'''集中式日志管理(Centralized Logging):'''集中式日志解决问题 6。因为微服务架构默认将应用日志分散保存在每一个微服务节点上,当系统进行用户行为分析、数据统计时必须收集所有节点日志数据。那如何有效收集所有节点的运行日志,并对其进行分析汇总呢?业内常见的方案有 ELK、EFK,通过搭建独立的日志收集系统,定时抓取增量日志形成有效的统计报表,为决策提供数据支撑。 | |||
[[文件:ELK日志手机系统.png|居中|702x702像素]] | |||
'''分布式链路追踪(Distributed Tracing):'''分布式追踪解决问题 7。一个复杂的业务流程可能需要连续调用多个微服务,我们需要记录一个完整业务逻辑涉及的每一个微服务的运行状态,再通过可视化链路图展现,帮助软件工程师在系统出错时分析解决问题。 | |||
[[文件:微服务链路追踪.png|居中|717x717像素]] | |||
'''服务保护(Service Protection):'''服务保护解决问题 8。在服务间通信过程中,如果某个微服务出现响应高延迟可能会导致线程池满载,严重时会引起系统崩溃。这里就需要引入服务保护组件实现高延迟服务的快速降级,避免系统崩溃。 | |||
==== Spring Cloud 是如何支撑微服务架构的 ==== | |||
下面这张图很好地诠释了 Spring、Spring Boot、Spring Cloud 它们之间的关系。 | |||
Spring 是整个微服务开发生态的基石,在此基础上 Spring 经过封装和简化,形成了Spring Boot 敏捷开发框架,而 Spring Cloud 开发微服务则必须掌握 Spring Boot,如果此时你对Spring 以及 Spring Boot 还不了解,可以在拉勾教育找到相关课程补充学习。 | |||
Spring Cloud 提供了完整的微服务架构的技术生态,像刚才我们提到的微服务架构标准组件,在 Spring Cloud 中都有着对应的实现。 | |||
[[文件:Cloud组件.png|居中|611x611像素]] | |||
不过这里有一点需要注意,Spring Cloud 并不是完全由 Spring 机构自主开发的,秉持不重复造轮子的理念,Spring 机构整合了多家厂商的优秀开源产品,比如注册中心就选用了 Netflix 的 Eureka,在此基础上进行整合形成了完整的 Java 微服务架构解决方案。刚才我们列举出来的 8 个微服务的标准组件在 Spring Cloud 中都有对应的具体实现,值得注意的是同一类组件 Spring Cloud 可能给出多种不同的开源产品供我们选择,这些产品在 Spring Cloud 下是彼此兼容的。 | |||
[[文件:Springcloud1.png|居中|643x643像素]] | |||
以上是 Spring Cloud 的主要组成部分,这里还有许多没有提到的功能组件,如果你对此感兴趣,可以到 Spring Cloud 官网(https://spring.io/projects/spring-cloud<nowiki/>)了解更详细的内容。 | |||
仔细看上面的表格,你会发现其中问题,正是因为 Spring Cloud 是集众家所长,但这也受制于第三方厂商的约束。以 Zuul 为例,Netflix 宣布停止维护,Spring 机构便不得不寻找替代品或者自行研发。与此同时,Spring Cloud 作为国外产品引入国内后也出现了水土不服,例如 Spring Cloud Config 默认将配置文件转存在Github仓库,但国内开发商并不喜欢这种做法,他们更希望将这些配置信息存储到自己的数据库中。因此,阿里巴巴在原有 Spring Cloud 基础上,结合阿里巴巴多年的开源技术沉淀,设计了更符合国情的 Spring Cloud Alibaba。 | |||
==== 功能更强大的国产微服务生态Spring Cloud Alibaba ==== | |||
Spring Cloud Alibaba是直接隶属于 Spring Cloud 的子项目。官网是:https://spring.io/projects/spring-cloud-alibaba#overview | |||
Spring Cloud Alibaba是国产的微服务开发一站式解决方案,与原有 Spring Cloud 兼容的同时对微服务生态进行扩展,通过添加少量的配置注解,便可实现更符合国情的微服务架构。 | |||
[[文件:Alibaba1.png|居中|801x801像素]] | |||
下面是 Spring Cloud 与 Spring Cloud Alibaba 的对比表格。 | |||
[[文件:Springcloudalibaba.png|居中|654x654像素]] | |||
从不同维度进行比对,可以发现相比 Spring Cloud,Spring Cloud Alibaba 对服务注册、配置中心与负载均衡功能都整合进 Nacos,这样简化了微服务架构的复杂度,出问题的概率也会降低。原有的服务保护组件也调整为 Sentinel,相较Hystrix功能更强大,使用也更加友好。在表格最下方也可看到 Spring Cloud Alibaba 基于阿里云强大的能力提供了更多的新特性,很多复杂的应用场景通过 Spring Cloud Alibaba 结合阿里云便可轻松实现。 | |||
=== 服务治理:Nacos 如何实现微服务服务治理 === | |||
==== Nacos 注册中心的特性 ==== | |||
我们以现实业务为例,某超市会员线上购物送等额积分,此积分在下次购物时可抵用现金,其中涉及订单服务、会员服务、积分服务等多个微服务模块。 | |||
在以往单实例情况下,服务间通常采用点对点通信,即采用 IP+端口+接口的形式直接调用。但考虑避免单点负载压力过大以及高可用的性能要求,通常会部署多实例节点保障系统的性能,但增加多实例后,调用方该如何选择哪个服务提供者进行处理呢?还有当服务提供者出现故障后,如何将后续请求转移到其他可用实例上呢?面对这些问题,微服务架构必须要引入注册中心对所有服务实例统一注册管理、有组织地进行健康检查来保障服务的可用性。 | |||
[[文件:订单.png|居中|807x807像素]] | |||
在 Spring Cloud Alibaba 生态中,由 Nacos 中间件承担注册中心职责,需要独立部署。下面我们先来认识一下 Nacos。 | |||
Nacos 官方地址为<nowiki/>https://nacos.io/zh-cn/index.html<nowiki/>。由阿里开源,官方定义为:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 | |||
Nacos 具备以下职能: | |||
* 服务发现及管理; | |||
* 动态配置服务; | |||
* 动态 DNS 服务。 | |||
下图是Nacos 的核心特征: | |||
[[文件:Nacos特征.png|居中|1120x1120像素]] | |||
==== Nacos 的快速部署 ==== | |||
在微服务架构中,Nacos 注册中心处于核心地位,通常我们会采用高性能服务器独立部署。下面来演示 Nacos 的部署过程。 | |||
===== 环境准备 ===== | |||
Nacos 同时支持 Windows 与 Linux 系统。因大多数服务器会选择安装 Linux 操作系统,为了模拟真实环境,建议你搭建一个 CentOS 7/8 的虚拟机,我这里的服务器地址为:192.168.31.102。Nacos 采用 Java 进行开发,要求 JDK8+,如果 CentOS 系统中没有安装 JDK,可使用下面流程进行基础环境准备。 | |||
====== 利用 yum 命令安装 OpenJDK 8 ====== | |||
<syntaxhighlight lang="shell"> | |||
yum -y install java-1.8.0-openjdk-devel.x86_64 | |||
#安装成功后验证Java版本 | |||
java -version | |||
</syntaxhighlight> | |||
====== 配置 JAVA_HOME 环境变量 ====== | |||
安装后 JDK 不要忘记设置 JAVA_HOME 环境变量,OpenJDK 默认安装在 /usr/lib/jvm/ 路径下,之后通过编辑 profile 设置 JAVA_HOME 环境变量。<syntaxhighlight lang="shell"> | |||
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.272.b10-1.el7_9.x86_64 | |||
export JRE_HOME=$JAVA_HOME/jre | |||
export CLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH | |||
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH | |||
</syntaxhighlight>最后要确认 JAVA_HOME 环境变量是否配置正确。 | |||
[[文件:JAVA HOME.png|左]] | |||
JDK安装后,正式进入 Nacos 的安装过程。 | |||
===== 安装过程 ===== | |||
第一步,访问 Nacos GitHub:https://github.com/alibaba/nacos/releases/<nowiki/>获取 Nacos 最新版安装包 nacos-server-1.4.0.tar.gz。 | |||
第二步,上传 nacos-server-1.4.0.tar.gz 到 CentOS 系统,对安装包解压缩。<syntaxhighlight lang="shell"> | |||
tar -xvf nacos-server-1.4.0.tar.gz | |||
</syntaxhighlight>解压后 Nacos 目录结构如下: | |||
* bin:保存启用/关闭 Nacos Server 脚本; | |||
* conf:Nacos Server 配置目录; | |||
* data:Nacos 数据目录; | |||
* logs:存放日志目录; | |||
* target:Nacos Jar 包存放目录。 | |||
第三步,以单点方式启动 Nacos。<syntaxhighlight lang="shell"> | |||
cd nacos/bin | |||
sh startup.sh -m standalone | |||
</syntaxhighlight>启动日志如下:<syntaxhighlight lang="text"> | |||
nacos is starting with standalone | |||
nacos is starting, you can check the /usr/local/nacos/nacos/logs/start.out | |||
</syntaxhighlight>默认 Nacos 以后台模式启动,利用 tail 命令查看启动日志。可以看到 Nacos 默认端口为 8848,下面日志说明 Nacos 单机模式已启动成功。 | |||
[[文件:Nacos启动成功.png|居中]] | |||
第四步,默认 CentOS 系统并没有对外开放 7848/8848 端口,需要设置防火墙对 7848/8848 端口放行。 | |||
其中,8848 端口是 Nacos 对客户端提供服务的端口,7848 是 Nacos 集群通信端口,用于Nacos 集群间进行选举,检测等。<syntaxhighlight lang="shell"> | |||
firewall-cmd --zone=public --add-port=8848/tcp --permanent | |||
firewall-cmd --zone=public --add-port=7848/tcp --permanent | |||
firewall-cmd --reload | |||
</syntaxhighlight>此时,Nacos 已单机部署完毕。 | |||
第五步,进入 Nacos 管理界面,打开浏览器,地址栏输入:[http://192.168.31.102:8848/nacos http://192.168.31.128:8848/nacos](以实际IP为准) | |||
管理界面默认用户名与密码均为nacos,提交后进入首页。点击左侧菜单“服务管理->服务列表”,这个功能用于查看已注册微服务列表。 | |||
[[文件:Nacos服务列表.png|居中|1013x1013像素]] | |||
==== 微服务接入 Nacos ==== | |||
Spring Cloud Alibaba 作为 Spring Cloud 子项目,开发框架仍基于 SpringBoot,只是在构建项目时需要选择不同的 starter 接入注册中心,下面我们通过实操完成微服务与 Nacos 服务器的接入工作。 | |||
# 创建新工程,工程类型选择 Spring Initializr。下图是 SpringBoot 工程向导,右侧选中 Custom,写入阿里云地址http://start.aliyun.com,默认的 <nowiki>https://start.spring.io。</nowiki> 这里需要连接 spring 官方服务器,因为网络原因经常无法访问,所以采用国内阿里云镜像生成工程初始代码。[[文件:Spring Initializr.png|居中]] | |||
# 在向导后面的依赖页面,要接入 Nacos 有一项是必选的,请大家注意。[[文件:Nacos Service Discovery.png|居中]] | |||
# 工程创建成功,打开 pom.xml 文件,确认 Maven 依赖 nacos-discovery,说明服务已内置 Nacos 客户端成功。<syntaxhighlight lang="xml"> | |||
<dependency> | |||
<groupId>com.alibaba.cloud</groupId> | |||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> | |||
</dependency> | |||
</syntaxhighlight>此外,我们需要在当前微服务增加 Spring-Web 依赖。因为微服务默认通过 RESTful API 对外暴露接口,增加 Spring-Web 会在应用中内嵌 Tomcat,使微服务具备 HTTP 响应能力。<syntaxhighlight lang="xml"> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-web</artifactId> | |||
</dependency> | |||
</syntaxhighlight> | |||
# 在 application.properties 配置 Nacos 注册中心通信地址。<syntaxhighlight lang="properties"> | |||
# 应用名称,默认也是在微服务中注册的微服务 ID | |||
spring.application.name=sample-service | |||
# 配置 Nacos 服务器的IP地址 | |||
spring.cloud.nacos.discovery.server-addr=192.168.31.128:8848 | |||
#连接 Nacos 服务器使用的用户名、密码,默认为 nacos | |||
spring.cloud.nacos.discovery.username=nacos | |||
spring.cloud.nacos.discvery.password=nacos | |||
#微服务提供Web服务的端口号 | |||
server.port=9000 | |||
</syntaxhighlight> | |||
# 启动 SampleService 工程,在启动日志最后三句清晰的说明注册已成功。<syntaxhighlight lang="text"> | |||
INFO 16168 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9000 (http) with context path '' | |||
INFO 16168 --- [ main] c.a.c.n.registry.NacosServiceRegistry : nacos registry, DEFAULT_GROUP sample-service 192.168.51.98:9000 register finished | |||
INFO 16168 --- [ main] c.k.s.SampleServiceApplication : Started SampleServiceApplication in 5.122 seconds (JVM running for 6.518) | |||
</syntaxhighlight> | |||
# 浏览器打开http://192.168.31.102:8848/nacos,查看服务列表时发现 sample-service 服务已出现。[[文件:Nacos服务列表.png|居中|1123x1123像素]]点击列表右侧“详情按钮”就会出现详细信息,在服务详情下清晰列出 sample-service 服务目前可用实例的 IP 及服务端口。 | |||
[[文件:Nacos服务详情.png|居中|1207x1207像素]] | |||
到这里我们已完成了微服务向 Nacos 注册登记,因为 SpringBoot 为我们高度封装了注册过程。为了你更透彻理解 Nacos,下面我来介绍 Nacos 注册过程背后的原理。 | |||
==== Nacos 注册中心的心跳机制 ==== | |||
下图阐述了微服务与 Nacos 服务器之间的通信过程。在微服务启动后每过5秒,会由微服务内置的 Nacos 客户端主动向 Nacos 服务器发起心跳包(HeartBeat)。心跳包会包含当前服务实例的名称、IP、端口、集群名、权重等信息。 | |||
[[文件:心跳机制.png|居中|898x898像素]] | |||
开启微服务 Debug 日志,会清晰地看到每 5 秒一个心跳请求被发送到 Nacos 的 /nacos/v1/ns/instance/beat 接口,该请求会被 Nacos 服务器内置的 naming 模块处理。<syntaxhighlight lang="text"> | |||
DEBUG 10720 --- [ing.beat.sender] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@665891d213 pairs: {PUT /nacos/v1/ns/instance/beat?app=unknown&serviceName=DEFAULT_GROUP%40%40sample-service&namespaceId=public&port=9000&clusterName=DEFAULT&ip=192.168.47.1 HTTP/1.1: null}{Content-Type: application/x-www-form-urlencoded}{Accept-Charset: UTF-8}{Accept-Encoding: gzip,deflate,sdch}{Content-Encoding: gzip}{Client-Version: 1.3.2}{User-Agent: Nacos-Java-Client:v1.3.2}{RequestId: 6447aa06-9d70-41ea-83ef-cd27af1d3422}{Request-Module: Naming}{Host: 192.168.31.102:8848}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive}{Content-Length: 326} | |||
DEBUG 10720 --- [ing.beat.sender] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@5f00479a12 pairs: {PUT /nacos/v1/ns/instance/beat?app=unknown&serviceName=DEFAULT_GROUP%40%40sample-service&namespaceId=public&port=9000&clusterName=DEFAULT&ip=192.168.47.1 HTTP/1.1: null}{Content-Type: application/x-www-form-urlencoded}{Accept-Charset: UTF-8}{Accept-Encoding: gzip,deflate,sdch}{Content-Encoding: gzip}{Client-Version: 1.3.2}{User-Agent: Nacos-Java-Client:v1.3.2}{RequestId: 9fdf2264-9704-437f-bd34-7c9ee5e0be41}{Request-Module: Naming}{Host: 192.168.31.102:8848}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive} | |||
DEBUG 10720 --- [ing.beat.sender] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@3521283812 pairs: {PUT /nacos/v1/ns/instance/beat?app=unknown&serviceName=DEFAULT_GROUP%40%40sample-service&namespaceId=public&port=9000&clusterName=DEFAULT&ip=192.168.47.1 HTTP/1.1: null}{Content-Type: application/x-www-form-urlencoded}{Accept-Charset: UTF-8}{Accept-Encoding: gzip,deflate,sdch}{Content-Encoding: gzip}{Client-Version: 1.3.2}{User-Agent: Nacos-Java-Client:v1.3.2}{RequestId: ccb6a586-897f-4036-9c0d-c614e2ff370a}{Request-Module: Naming}{Host: 192.168.31.102:8848}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive} | |||
</syntaxhighlight>naming 模块在接收到心跳包后,会按下图逻辑处理心跳包并返回响应: | |||
# naming 模块收到心跳包,首先根据 IP 与端口判断 Nacos 是否存在该服务实例?如果实例信息不存在,在 Nacos 中注册登记该实例。而注册的本质是将新实例对象存储在“实例 Map”集合中; | |||
# 如果实例信息已存在,记录本次心跳包发送时间; | |||
# 设置实例状态为“健康”; | |||
# 推送“微服务状态变更”消息; | |||
# naming 模块返回心跳包时间间隔。 | |||
到这里一次完整的心跳包处理已完成。 | |||
[[文件:心跳包处理.png|居中|523x523像素]] | |||
那 Nacos 又是如何将无效实例从可用实例中剔除呢?Nacos Server 内置的逻辑是每过 20 秒对“实例 Map”中的所有“非健康”实例进行扫描,如发现“非健康”实例,随即从“实例 Map”中将该实例删除。 | |||
=== 高可用保证:Nacos 如何有效构建注册中心集群 === | |||
上一章节讲述了 Nacos 注册中心的作用以及单点运行的方法,但是单点运行是分布式应用的大忌,在分布式架构中,任何单点都可能成为系统的瓶颈,因此在生产环境中 Nacos 都需要通过部署集群来为系统带来高可用性。因此本文围绕 Nacos 集群主要讲解以下三方面内容: | |||
* 生产环境 Nacos 集群的设计要点; | |||
* Nacos 集群的部署过程; | |||
* 介绍 Nacos 集群的工作原理。 | |||
==== 生产环境部署 Nacos 集群 ==== | |||
[[文件:生产nacos集群.png|居中|613x613像素]] | |||
下面我们来解读下 Nacos 集群架构的设计要点: | |||
# 微服务并不是直接通过 IP 地址访问后端服务,而是采用域名访问。通过 DNS(域名解析服务)转换为具体的 IP 地址,通过域名方式屏蔽后端容易产生变化的 IP 地址。 | |||
# 底层 Nacos 自带集群间节点与数据同步方案,因此需要 Nacos 节点对外暴露 8848 与 7848 端口。其中 8848 端口的作用是对外暴露 API 与集群间数据同步,而 7848 端口则用于节点选举来确定集群领袖(Leader)。同时 Nacos 在集群环境下需要持久化应用配置、用户权限、历史信息等内置数据,因此需要额外部署 MySQL 数据库提供统一存储。 | |||
# 在 Nacos 层面,每一台服务器都有独立的 IP。我们并不建议直接将物理 IP 对外暴露,而是额外增加 VIP(虚拟 IP),通过 DNS 服务绑定 VIP,这样的好处是通过 VIP 屏蔽了Nacos集群实际的物理IP地址,同时为访问者提供了统一的接入入口,使微服务的注册接入和Nacos 集群实现细节彼此解耦,提高架构的维护性。 | |||
==== Nacos 集群的部署过程 ==== |
2021年9月3日 (五) 07:45的最新版本
Spring Cloud是什么
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。 Spring Cloud正是对Netflix的多个开源组件进一步的封装而成,同时又实现了和云端平台,和Spring Boot开发框架很好的集成。 Spring Cloud是一个相对比较新的微服务框架,2016年才推出1.0的release版本. 虽然Spring Cloud时间最短, 但是相比Dubbo等RPC框架, Spring Cloud提供的全套的分布式系统解决方案。 Spring Cloud 为开发者提供了在分布式系统(配置管理,服务发现,熔断,路由,微代理,控制总线,一次性token,全居琐,leader选举,分布式session,集群状态)中快速构建的工具,使用Spring Cloud的开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。
从上图可以看出Spring Cloud各个组件相互配合,合作支持了一套完整的微服务架构。
- Eureka负责服务的注册与发现,很好将各服务连接起来。
- Hystrix 负责监控服务之间的调用情况,连续多次失败进行熔断保护。
- Hystrix dashboard,Turbine 负责监控 Hystrix的熔断情况,并给予图形化的展示。
- Spring Cloud Config 提供了统一的配置中心服务。
- 当配置文件发生变化的时候,Spring Cloud Bus 负责通知各服务去获取最新的配置信息。
- 所有对外的请求和服务,我们都通过Zuul来进行转发,起到API网关的作用。
- 最后我们使用Sleuth+Zipkin将所有的请求数据记录下来,方便我们进行后续分析。
Spring Cloud从设计之初就考虑了绝大多数互联网公司架构演化所需的功能,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等。这些功能都是以插拔的形式提供出来,方便我们系统架构演进的过程中,可以合理的选择需要的组件进行集成,从而在架构演进的过程中会更加平滑、顺利。
Spring Cloud快速入门
搭建配置一个服务提供者
众所周知,SpringCloud构建微服务是基于SpringBoot开发的。
- 创建一个SpringBoot工程,并且添加SpringBoot的相关依赖;
- 创建服务提供者的访问方法,也就是后续消费者如何访问提供者。
- Spring Cloud是基于rest的访问,所以我们添加一个Controller,在该Controller中提供一个访问入口:
@RestController public class HelloController { @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello() { return "Hello Spring Cloud"; } }
- 启动运行该SpringBoot程序,访问该controller。
搭建配置一个服务消费者
- 创建一个SpringBoot工程,并且添加SpringBoot的相关依赖。
- 开发一个消费者方法,去消费服务提供者提供的服务,这个消费者方法也是一个Controller:
@RestController public class ConsumerController { @Autowired private RestTemplate restTemplate; @RequestMapping(value="/cloud/hello") public String helloController() { return restTemplate.getForEntity("http://localhost:9100/hello", String.class).getBody(); } }
- 启动该SpringBoot程序,测试服务消费者调用服务提供者。
走进服务注册中心Eureka
在微服务架构中,服务注册与发现是核心组件之一,手动指定每个服务是很低效的,Spring Cloud提供了多种服务注册与发现的实现方式,例如:Eureka、Consul、Zookeeper。
服务注册
服务注册:将服务所在主机、端口、版本号、通信协议等信息登记到注册中心上。
服务发现
服务发现:服务消费者向注册中心请求已经登记的服务列表,然后得到某个服务的主机、端口、版本号、通信协议等信息,从而实现对具体服务的调用。
Eureka是什么
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 来监控系统中各个微服务是否正常运行。
搭建与配置Eureka服务注册中心
Spring Cloud要使用Eureka注册中心非常简单和方便,Spring Cloud中的Eureka服务注册中心实际上也是一个Spring Boot工程,我们只需通过引入相关依赖和注解配置就能让Spring Boot构建的微服务应用轻松地与Eureka进行整合。
- 创建一个SpringBoot项目,并且添加SpringBoot的相关依赖;
- 添加eureka的依赖:
<!--Spring Cloud的eureka-server起步依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
- 在Spring Boot的入口类上添加一个@EnableEurekaServer注解,用于开启Eureka注册中心服务端;
- 在application.properties文件中配置Eureka服务注册中心信息:
#内嵌定时tomcat的端口 server.port=8761 #设置该服务注册中心的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
- 启动SpringBoot程序,main方法运行。通过浏览器地址访问注册中心。
向Eureka服务注册中心注册服务
我们前面搭建了服务提供者项目,接下来我们就可以将该服务提供者注册到Eureke注册中心,步骤如下:
- 在该服务提供者中添加eureka的依赖,因为服务提供者向注册中心注册服务,需要连接eureka,所以需要eureka客户端的支持;
<!--SpringCloud集成eureka客户端的起步依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- 在Spring Boot的入口函数处,通过添加@EnableEurekaClient注解来表明自己是一个eureka客户端,让我的服务提供者可以连接eureka注册中心;
- 配置服务名称和注册中心地址;
spring.application.name=02-springcloud-service-provider eureka.client.service-url.defaultZone=http://localhost:8761/eureka
- 启动服务提供者SpringBoot程序的main方法运行;
- 启动运行之后,通过在浏览器地址栏访问我们之前搭建好的eureka注册中心,就可以看到有一个服务已经注册成功了。
从Eureka服务注册中心发现与消费服务
我们已经搭建一个服务注册中心,同时也向这个服务注册中心注册了服务,接下来我们就可以发现和消费服务了,这其中服务的发现由eureka客户端实现,而服务的消费由Ribbon实现,也就是说服务的调用需要eureka客户端和Ribbon两者配合起来才能实现。
Eureka客户端是什么
Eureka客户端是一个Java客户端,用来连接Eureka服务端,与服务端进行交互、负载均衡,服务的故障切换等。
Ribbon是什么
Ribbon是一个基于HTTP 和 TCP 的客户端负载均衡器,当使用Ribbon对服务进行访问的时候,它会扩展Eureka客户端的服务发现功能,实现从Eureka注册中心中获取服务端列表,并通过Eureka客户端来确定服务端是否己经启动。Ribbon在Eureka客户端服务发现的基础上,实现了对服务实例的选择策略,从而实现对服务的负载均衡消费。
接下来我们来让服务消费者去消费服务:
我们前面搭建了服务消费者项目,接下来我们就可以使用该服务消费者通过注册中心去调用服务提供者,步骤如下:
- 在该消费者项目中添加eureka的依赖,因为服务消费者从注册中心获取服务,需要连接eureka,所以需要eureka客户端的支持;
<!--SpringCloud集成eureka客户端的起步依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- 在Spring Boot的入口函数处,通过添加@EnableEurekaClient注解来表明自己是一个eureka客户端,让我的服务消费者可以使用eureka注册中心;
- 配置服务的名称和注册中心的地址:
spring.application.name=03-springcloud-web-consumer eureka.client.service-url.defaultZone=http://localhost:8761/eureka
- 前面我介绍了服务的发现由eureka客户端实现,而服务的真正调用由ribbon实现,所以我们需要在调用服务提供者时使用ribbon来调用:加入了ribbon的支持,那么在调用时,即可改为使用服务名称来访问:
@LoadBalanced @Bean public RestTemplate restTemplate () { return new RestTemplate(); }
restTemplate.getForEntity("http://02-SPRINGCLOUD-SERVICE-PROVIDER/hello", String.class).getBody();
- 完成上面的步骤后,我们就可以启动消费者的SpringBoot程序,main方法运行;
- 启动成功之后,通过在浏览器地址栏访问我们的消费者,看是否可以正常调用远程服务提供者提供的服务。
Spring Cloud Eureka服务注册中心
在微服务架构的这种分布式系统中,我们要充分考虑各个微服务组件的高可用性问题,不能有单点故障,由于注册中心eureka本身也是一个服务,如果它只有一个节点,那么它有可能发生故障,这样我们就不能注册与查询服务了,所以我们需要一个高可用的服务注册中心,这就需要通过注册中心集群来解决。
eureka服务注册中心它本身也是一个服务,它也可以看做是一个提供者,又可以看做是一个消费者,我们之前通过配置:eureka.client.register-with-eureka=false 让注册中心不注册自己,但是我们可以向其他注册中心注册自己。
Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就会形成一组互相注册的服务注册中心,进而实现服务清单的互相同步,往注册中心A上注册的服务,可以被复制同步到注册中心B上,所以从任何一台注册中心上都能查询到已经注册的服务,从而达到高可用的效果。
Eureka注册中心高可用集群搭建
我们知道,Eureka注册中心高可用集群就是各个注册中心相互注册,所以:
- 在8761的配置文件中,让它的service-url指向8762,在8762的配置文件中让它的service-url指向8761;
- 由于8761和8762互相指向对方,实际上我们构建了一个双节点的服务注册中心集群;
eureka.client.service-url.defaultZone=http://eureka8762:8762/eureka/ eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka/
- 然后在本地hosts文件配置:C:\Windows\System32\drivers\etc\hosts;
127.0.0.1 eureka8761 127.0.0.1 eureka8762
- 运行时,在运行配置项目Program Arguments 中配配置;
--spring.profiles.active=eureka8761 --spring.profiles.active=eureka8762
- 分别启动两个注册中心,访问两个注册中心页面,观察注册中心页面是否正常。
Eureka注册中心高可用集群测试
在要进行注册的服务中配置:
eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka/,http://eureka8762:8762/eureka/
启动服务提供者服务,然后观察注册中心页面,可以看到服务会在两个注册中心上都注册成功。
Eureka服务注册中心自我保护机制
自我保护机制是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.
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自我保护的情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例,但是当发生网络分区故障时,那么微服务与Eureka Server之间将无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是正常的,此时不应该注销这个微服务,如果没有自我保护机制,那么Eureka Server就会将此服务注销掉。
Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么就会把这个微服务节点进行保护。一旦进入自我保护模式,Eureka Server就会保护服务注册表中的信息,不删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会再自动退出自我保护模式。
所以,自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务,使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
当然也可以使用配置项:eureka.server.enable-self-preservation = false 禁用自我保护模式。
但是Eureka Server 自我保护模式也会给我们带来一些困扰,如果在保护期内某个服务提供者刚好非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端具有一些容错机制,如重试,断路器等。
Eureka的自我保护模式是有意义的,该模式被激活后,它不会从注册列表中剔除因长时间没收到心跳导致注册过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。
例如,两个微服务客户端实例A和B之间有调用的关系,A是消费者,B是提供者,但是由于网络故障,B未能及时向Eureka发送心跳续约,这时候Eureka 不能简单的将B从注册表中剔除,因为如果剔除了,A就无法从Eureka 服务器中获取B注册的服务,但是这时候B服务是可用的。
所以,Eureka的自我保护模式最好还是开启它。
关于自我保护常用几个配置如下:
服务器端配置:
#测试时关闭自我保护机制,保证不可用服务及时踢出
eureka.server.enable-self-preservation=false
客户配置:
#每间隔2s,向服务端发送一次心跳,证明自己依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=2
东岱告诉服务端,如果我10s之内没有给你发心跳,就代表我故障了,将我踢出掉
eureka.instance.lease-expiration-duration-in-seconds=10
Spring Cloud Ribbon客户端负载均衡
Spring Cloud中的Ribbon是什么
我们通常说的负载均衡是指将一个请求均匀地分摊到不同的节点单元上执行,负载均和分为硬件负载均衡和软件负载均衡:
硬件负载均衡:比如 F5、深信服、Array 等;
软件负载均衡:比如 Nginx、LVS、HAProxy 等;
硬件负载均衡或是软件负载均衡,他们都会维护一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如轮询、权重、 最小连接数等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,是一个基于HTTP和TCP的客户端负载均衡工具。
Spring Cloud对Ribbon做了二次封装,可以让我们使用RestTemplate的服务请求,自动转换成客户端负载均衡的服务调用。
Ribbon支持多种负载均衡算法,还支持自定义的负载均衡算法。
Ribbon只是一个工具类框架,比较小巧,Spring Cloud对它封装后使用也非常方便,它不像服务注册中心、配置中心、API网关那样需要独立部署,Ribbon只需要在代码直接使用即可。
Ribbon 与 Nginx 的区别
Ribbon是客户端的负载均衡工具,而客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置不同,在客户端负载均衡中,所有客户端节点下的服务端清单,需要自己从服务注册中心上获取,比如Eureka服务注册中心。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。在Spring Cloud中,由于Spring Cloud对Ribbon做了二次封装,所以默认会创建针对Ribbon的自动化整合配置。
在Spring Cloud中,Ribbon主要与RestTemplate对象配合起来使用,Ribbon会自动化配置RestTemplate对象,通过@LoadBalanced开启RestTemplate对象调用时的负载均衡。
Ribbon实现客户端负载均衡
由于Spring Cloud Ribbon的封装, 我们在微服务架构中使用客户端负载均衡调用非常简单, 只需要如下两步:
- 启动多个服务提供者实例并注册到一个服务注册中心或是服务注册中心集群。
- 服务消费者通过被@LoadBalanced注解修饰过的RestTemplate来调用服务提供者。
这样,我们就可以实现服务提供者的高可用以及服务消费者的负载均衡调用。
Ribbon负载均衡策略
Ribbon的负载均衡策略是由IRule接口定义, 该接口由如下实现:
RandomRule | 随机 |
RoundRobinRule | 轮询 |
AvailabilityFilteringRule | 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问; |
WeightedResponseTimeRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略; |
RetryRule | 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务; |
BestAvailableRule | 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务; |
ZoneAvoidanceRule | 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务; |
Rest请求模板类解读
当我们从服务消费端去调用服务提供者的服务的时候,使用了一个极其方便的对象叫RestTemplate,当时我们只使用了RestTemplate中最简单的一个功能getForEntity发起了一个get请求去调用服务端的数据,同时,我们还通过配置@LoadBalanced注解开启客户端负载均衡,RestTemplate的功能非常强大,那么接下来我们就来详细的看一下RestTemplate中几种常见请求方法的使用。
在日常操作中,基于Rest的方式通常是四种情况,它们分表是:
- GET请求 --查询数据
- POST请求 –添加数据
- PUT请求 – 修改数据
- DELETE请求 –删除数据
RestTemplate的GET请求
有两种方式:
第一种:getForEntity
该方法返回一个ResponseEntity对象,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,比如响应码、contentType、contentLength、响应消息体等;
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://02-SPRINGCLOUD-SERVICE-PROVIDER/hello", String.class);
String body = responseEntity.getBody();
HttpStatus statusCode = responseEntity.getStatusCode();
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();
System.out.println(body);
System.out.println(statusCode);
System.out.println(statusCodeValue);
System.out.println(headers);
getForEntity方法第一个参数为要调用的服务的地址,即服务提供者提供的http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello接口地址,注意这里是通过服务名调用而不是服务地址,如果改为服务地址就无法使用Ribbon实现客户端负载均衡了。
getForEntity方法第二个参数String.class表示希望返回的body类型是String类型,如果希望返回一个对象,也是可以的,比如User对象。
第二种:getForObject()
与getForEntity使用类似,只不过getForObject是在getForEntity基础上进行了再次封装,可以将http的响应体body信息转化成指定的对象,方便我们的代码开发;
当你不需要返回响应中的其他信息,只需要body体信息的时候,可以使用这个更方便。
RestTemplate的POST请求
restTemplate.postForObject(); restTemplate.postForEntity(); restTemplate.postForLocation();
RestTemplate的PUT请求
restTemplate.put();
RestTemplate的DELETE请求
restTemplate.delete();
Spring Cloud Hystrix服务熔断
Hystrix是什么
在微服务架构中,我们是将一个单体应用拆分成多个服务单元,各个服务单元之间通过注册中心彼此发现和消费对方提供的服务,每个服务单元都是单独部署,在各自的服务进程中运行,服务之间通过远程调用实现信息交互,那么当某个服务的响应太慢或者故障,又或者因为网络波动或故障,则会造成调用者延迟或调用失败,当大量请求到达,则会造成请求的堆积,导致调用者的线程挂起,从而引发调用者也无法响应,调用者也发生故障。
比如电商中的用户下订单,我们有两个服务,一个下订单服务,一个减库存服务,当用户下订单时调用下订单服务,然后下订单服务又调用减库存服务,如果减库存服务响应延迟或者没有响应,则会造成下订单服务的线程挂起等待,如果大量的用户请求下订单,或导致大量的请求堆积,引起下订单服务也不可用,如果还有另外一个服务依赖于订单服务,比如用户服务,它需要查询用户订单,那么用户服务查询订单也会引起大量的延迟和请求堆积,导致用户服务也不可用。
所以在微服务架构中,很容易造成服务故障的蔓延,引发整个微服务系统瘫痪不可用。
为了解决此问题,微服务架构中引入了一种叫熔断器的服务保护机制。
熔断器也有叫断路器,他们表示同一个意思,最早来源于微服务之父Martin Fowler的论文CircuitBreaker一文。“熔断器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,能够及时切断故障电路,防止发生过载、发热甚至起火等严重后果。
微服务架构中的熔断器,就是当被调用方没有响应,调用方直接返回一个错误响应即可,而不是长时间的等待,这样避免调用时因为等待而线程一直得不到释放,避免故障在分布式系统间蔓延。
Spring Cloud Hystrix实现了熔断器、线程隔离等一系列服务保护功能。该功能也是基于Netflix的开源框架Hystrix实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。
Hystrix快速入门
在SpringCloud中使用熔断器Hystrix是非常简单和方便的,只需要简单三步即可:
- 添加依赖
<!--Spring Cloud熔断器起步依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.4.RELEASE</version> </dependency>
- 在入口类中使用@EnableCircuitBreaker注解开启断路器功能,也可以使用一个名为@SpringCloudApplication的注解代替主类上的三个注解。
- 在调用远程服务的方法上添加注解:@HystrixCommand(fallbackMethod="error")。
服务消费者Hystrix测试
Hystrix默认超时时间是1000毫秒,如果你后端的响应超过此时间,就会触发断路器;
修改hystrix的默认超时时间:
@HystrixCommand(fallbackMethod="error", commandProperties={
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1500")}) //熔断器,调用不通,回调error()方法
public String webHello () {
}
Hystrix的服务降级
有了服务的熔断,随之就会有服务的降级,所谓服务降级,就是当某个服务熔断之后,服务端提供的服务将不再被调用,此时由客户端自己准备一个本地的fallback回调,返回一个默认值来代表服务端的返回。
这种做法,虽然不能得到正确的返回结果,但至少保证了服务的可用,比直接抛出错误或服务不可用要好很多,当然这需要根据具体的业务场景来选择。
Hystrix的异常处理
微服务生态与 Spring Cloud Alibaba
通用的微服务架构应包含哪些组件
相对于单体式架构的简单粗暴,微服务架构将应用打散,形成多个微服务进行独立开发、测试、部署与运维。虽然从管理与逻辑上更符合业务需要,但微服务架构也带来了诸多急需解决的核心问题:
- 如何发现新服务节点以及检查服务节点的状态?
- 如何发现服务及负载均衡如何实现?
- 服务间如何进行消息通信?
- 如何对使用者暴露服务 API?
- 如何集中管理众多服务节点的配置文件?
- 如何收集服务节点的日志并统一管理?
- 如何实现服务间调用链路追踪?
- 如何对系统进行链路保护,避免微服务雪崩?
可以发现,上述这些问题并不是针对某种语言或某种技术的,任何软件厂商要构建微服务架构就必须面对这些问题,要么独立开发要么将已有多种技术整合形成整体解决方案。好在经过多年沉淀,业内已经有了标准答案,下图清晰的说明微服务架构需要的标准组件。
下面介绍每种组件的职责。
注册中心(Service Registry):注册中心是微服务架构最核心的组件。它起到新服务节点的注册与状态维护的作用,通过注册中心解决了上述问题 1。微服务节点在启动时会将自身的服务名称、IP、端口等信息在注册中心中进行登记,注册中心会定时检查该节点的运行状态。注册中心通常会采用心跳机制最大程度保证其持有的服务节点列表都是可用的。
负载均衡(Load Balance):负载均衡器解决了问题 2。通常在微服务彼此调用时并不是直接通过IP、端口直接访问,而是首先通过服务名在注册中心查询该服务拥有哪些可用节点,然后注册中心将可用节点列表返回给服务调用者,这个过程称为“服务发现”。因服务高可用的要求,服务调用者会接收到多个可用节点,必须要从中进行选择,因此在服务调用者一端必须内置负载均衡器,通过负载均衡策略选择适合的节点发起实质的通信请求。
服务通信(Communication):服务通信组件解决了问题 3。在微服务定义中阐述服务间通信采用轻量级协议,通常是 HTTP RESTful 风格。但因 RESTful 风格过于灵活,必须加以约束,通常在应用时对其进行上层封装,例如在 Spring Cloud 中就提供了 Feign 和 RestTemplate 两种技术屏蔽底层实现 RESTful 通信细节,所有开发者是基于封装后统一的SDK进行开发,这有利于团队间协作。
API 服务网关(API Gateway):服务网关解决问题 4。对于最终用户来说,微服务的通信与各种实现细节应该是透明的,用户只需关注他要使用的 API 接口即可。因此微服务架构引入服务网关控制用户的访问权限。服务网关是外部环境访问内部微服务的唯一途径,在这个基础上还可以扩展出其他功能,例如:用户认证与授权、容错限流、动态路由、A/B测试、灰度发布等。
配置中心(Config Management):配置中心解决问题 5。微服务架构下,微服务节点都包含自己的各种配置文件,如JDBC地址、自定义配置、环境配置等。要知道互联网公司微服务节点可能是成千上万个,如果这些配置信息分散存储在节点上,如发生配置变更就必须逐个调整,这必将给运维人员带来巨大的工作量。配置中心便因此而生,通过部署配置中心服务器,将原本分散的配置文件从应用中剥离,集中转存到配置中心。一般配置中心会提供 UI 界面,可以方便快捷的实现大规模集群的配置调整。
集中式日志管理(Centralized Logging):集中式日志解决问题 6。因为微服务架构默认将应用日志分散保存在每一个微服务节点上,当系统进行用户行为分析、数据统计时必须收集所有节点日志数据。那如何有效收集所有节点的运行日志,并对其进行分析汇总呢?业内常见的方案有 ELK、EFK,通过搭建独立的日志收集系统,定时抓取增量日志形成有效的统计报表,为决策提供数据支撑。
分布式链路追踪(Distributed Tracing):分布式追踪解决问题 7。一个复杂的业务流程可能需要连续调用多个微服务,我们需要记录一个完整业务逻辑涉及的每一个微服务的运行状态,再通过可视化链路图展现,帮助软件工程师在系统出错时分析解决问题。
服务保护(Service Protection):服务保护解决问题 8。在服务间通信过程中,如果某个微服务出现响应高延迟可能会导致线程池满载,严重时会引起系统崩溃。这里就需要引入服务保护组件实现高延迟服务的快速降级,避免系统崩溃。
Spring Cloud 是如何支撑微服务架构的
下面这张图很好地诠释了 Spring、Spring Boot、Spring Cloud 它们之间的关系。
Spring 是整个微服务开发生态的基石,在此基础上 Spring 经过封装和简化,形成了Spring Boot 敏捷开发框架,而 Spring Cloud 开发微服务则必须掌握 Spring Boot,如果此时你对Spring 以及 Spring Boot 还不了解,可以在拉勾教育找到相关课程补充学习。
Spring Cloud 提供了完整的微服务架构的技术生态,像刚才我们提到的微服务架构标准组件,在 Spring Cloud 中都有着对应的实现。
不过这里有一点需要注意,Spring Cloud 并不是完全由 Spring 机构自主开发的,秉持不重复造轮子的理念,Spring 机构整合了多家厂商的优秀开源产品,比如注册中心就选用了 Netflix 的 Eureka,在此基础上进行整合形成了完整的 Java 微服务架构解决方案。刚才我们列举出来的 8 个微服务的标准组件在 Spring Cloud 中都有对应的具体实现,值得注意的是同一类组件 Spring Cloud 可能给出多种不同的开源产品供我们选择,这些产品在 Spring Cloud 下是彼此兼容的。
以上是 Spring Cloud 的主要组成部分,这里还有许多没有提到的功能组件,如果你对此感兴趣,可以到 Spring Cloud 官网(https://spring.io/projects/spring-cloud)了解更详细的内容。
仔细看上面的表格,你会发现其中问题,正是因为 Spring Cloud 是集众家所长,但这也受制于第三方厂商的约束。以 Zuul 为例,Netflix 宣布停止维护,Spring 机构便不得不寻找替代品或者自行研发。与此同时,Spring Cloud 作为国外产品引入国内后也出现了水土不服,例如 Spring Cloud Config 默认将配置文件转存在Github仓库,但国内开发商并不喜欢这种做法,他们更希望将这些配置信息存储到自己的数据库中。因此,阿里巴巴在原有 Spring Cloud 基础上,结合阿里巴巴多年的开源技术沉淀,设计了更符合国情的 Spring Cloud Alibaba。
功能更强大的国产微服务生态Spring Cloud Alibaba
Spring Cloud Alibaba是直接隶属于 Spring Cloud 的子项目。官网是:https://spring.io/projects/spring-cloud-alibaba#overview
Spring Cloud Alibaba是国产的微服务开发一站式解决方案,与原有 Spring Cloud 兼容的同时对微服务生态进行扩展,通过添加少量的配置注解,便可实现更符合国情的微服务架构。
下面是 Spring Cloud 与 Spring Cloud Alibaba 的对比表格。
从不同维度进行比对,可以发现相比 Spring Cloud,Spring Cloud Alibaba 对服务注册、配置中心与负载均衡功能都整合进 Nacos,这样简化了微服务架构的复杂度,出问题的概率也会降低。原有的服务保护组件也调整为 Sentinel,相较Hystrix功能更强大,使用也更加友好。在表格最下方也可看到 Spring Cloud Alibaba 基于阿里云强大的能力提供了更多的新特性,很多复杂的应用场景通过 Spring Cloud Alibaba 结合阿里云便可轻松实现。
服务治理:Nacos 如何实现微服务服务治理
Nacos 注册中心的特性
我们以现实业务为例,某超市会员线上购物送等额积分,此积分在下次购物时可抵用现金,其中涉及订单服务、会员服务、积分服务等多个微服务模块。
在以往单实例情况下,服务间通常采用点对点通信,即采用 IP+端口+接口的形式直接调用。但考虑避免单点负载压力过大以及高可用的性能要求,通常会部署多实例节点保障系统的性能,但增加多实例后,调用方该如何选择哪个服务提供者进行处理呢?还有当服务提供者出现故障后,如何将后续请求转移到其他可用实例上呢?面对这些问题,微服务架构必须要引入注册中心对所有服务实例统一注册管理、有组织地进行健康检查来保障服务的可用性。
在 Spring Cloud Alibaba 生态中,由 Nacos 中间件承担注册中心职责,需要独立部署。下面我们先来认识一下 Nacos。
Nacos 官方地址为https://nacos.io/zh-cn/index.html。由阿里开源,官方定义为:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos 具备以下职能:
- 服务发现及管理;
- 动态配置服务;
- 动态 DNS 服务。
下图是Nacos 的核心特征:
Nacos 的快速部署
在微服务架构中,Nacos 注册中心处于核心地位,通常我们会采用高性能服务器独立部署。下面来演示 Nacos 的部署过程。
环境准备
Nacos 同时支持 Windows 与 Linux 系统。因大多数服务器会选择安装 Linux 操作系统,为了模拟真实环境,建议你搭建一个 CentOS 7/8 的虚拟机,我这里的服务器地址为:192.168.31.102。Nacos 采用 Java 进行开发,要求 JDK8+,如果 CentOS 系统中没有安装 JDK,可使用下面流程进行基础环境准备。
利用 yum 命令安装 OpenJDK 8
yum -y install java-1.8.0-openjdk-devel.x86_64
#安装成功后验证Java版本
java -version
配置 JAVA_HOME 环境变量
安装后 JDK 不要忘记设置 JAVA_HOME 环境变量,OpenJDK 默认安装在 /usr/lib/jvm/ 路径下,之后通过编辑 profile 设置 JAVA_HOME 环境变量。
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.272.b10-1.el7_9.x86_64
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
最后要确认 JAVA_HOME 环境变量是否配置正确。
JDK安装后,正式进入 Nacos 的安装过程。
安装过程
第一步,访问 Nacos GitHub:https://github.com/alibaba/nacos/releases/获取 Nacos 最新版安装包 nacos-server-1.4.0.tar.gz。
第二步,上传 nacos-server-1.4.0.tar.gz 到 CentOS 系统,对安装包解压缩。
tar -xvf nacos-server-1.4.0.tar.gz
解压后 Nacos 目录结构如下:
- bin:保存启用/关闭 Nacos Server 脚本;
- conf:Nacos Server 配置目录;
- data:Nacos 数据目录;
- logs:存放日志目录;
- target:Nacos Jar 包存放目录。
第三步,以单点方式启动 Nacos。
cd nacos/bin
sh startup.sh -m standalone
启动日志如下:
nacos is starting with standalone
nacos is starting, you can check the /usr/local/nacos/nacos/logs/start.out
默认 Nacos 以后台模式启动,利用 tail 命令查看启动日志。可以看到 Nacos 默认端口为 8848,下面日志说明 Nacos 单机模式已启动成功。
第四步,默认 CentOS 系统并没有对外开放 7848/8848 端口,需要设置防火墙对 7848/8848 端口放行。
其中,8848 端口是 Nacos 对客户端提供服务的端口,7848 是 Nacos 集群通信端口,用于Nacos 集群间进行选举,检测等。
firewall-cmd --zone=public --add-port=8848/tcp --permanent
firewall-cmd --zone=public --add-port=7848/tcp --permanent
firewall-cmd --reload
此时,Nacos 已单机部署完毕。
第五步,进入 Nacos 管理界面,打开浏览器,地址栏输入:http://192.168.31.128:8848/nacos(以实际IP为准)
管理界面默认用户名与密码均为nacos,提交后进入首页。点击左侧菜单“服务管理->服务列表”,这个功能用于查看已注册微服务列表。
微服务接入 Nacos
Spring Cloud Alibaba 作为 Spring Cloud 子项目,开发框架仍基于 SpringBoot,只是在构建项目时需要选择不同的 starter 接入注册中心,下面我们通过实操完成微服务与 Nacos 服务器的接入工作。
- 创建新工程,工程类型选择 Spring Initializr。下图是 SpringBoot 工程向导,右侧选中 Custom,写入阿里云地址http://start.aliyun.com,默认的 https://start.spring.io。 这里需要连接 spring 官方服务器,因为网络原因经常无法访问,所以采用国内阿里云镜像生成工程初始代码。
- 在向导后面的依赖页面,要接入 Nacos 有一项是必选的,请大家注意。
- 工程创建成功,打开 pom.xml 文件,确认 Maven 依赖 nacos-discovery,说明服务已内置 Nacos 客户端成功。此外,我们需要在当前微服务增加 Spring-Web 依赖。因为微服务默认通过 RESTful API 对外暴露接口,增加 Spring-Web 会在应用中内嵌 Tomcat,使微服务具备 HTTP 响应能力。
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
- 在 application.properties 配置 Nacos 注册中心通信地址。
# 应用名称,默认也是在微服务中注册的微服务 ID spring.application.name=sample-service # 配置 Nacos 服务器的IP地址 spring.cloud.nacos.discovery.server-addr=192.168.31.128:8848 #连接 Nacos 服务器使用的用户名、密码,默认为 nacos spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discvery.password=nacos #微服务提供Web服务的端口号 server.port=9000
- 启动 SampleService 工程,在启动日志最后三句清晰的说明注册已成功。
INFO 16168 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9000 (http) with context path '' INFO 16168 --- [ main] c.a.c.n.registry.NacosServiceRegistry : nacos registry, DEFAULT_GROUP sample-service 192.168.51.98:9000 register finished INFO 16168 --- [ main] c.k.s.SampleServiceApplication : Started SampleServiceApplication in 5.122 seconds (JVM running for 6.518)
- 浏览器打开http://192.168.31.102:8848/nacos,查看服务列表时发现 sample-service 服务已出现。点击列表右侧“详情按钮”就会出现详细信息,在服务详情下清晰列出 sample-service 服务目前可用实例的 IP 及服务端口。生成缩略图出错:无法找到文件
到这里我们已完成了微服务向 Nacos 注册登记,因为 SpringBoot 为我们高度封装了注册过程。为了你更透彻理解 Nacos,下面我来介绍 Nacos 注册过程背后的原理。
Nacos 注册中心的心跳机制
下图阐述了微服务与 Nacos 服务器之间的通信过程。在微服务启动后每过5秒,会由微服务内置的 Nacos 客户端主动向 Nacos 服务器发起心跳包(HeartBeat)。心跳包会包含当前服务实例的名称、IP、端口、集群名、权重等信息。
开启微服务 Debug 日志,会清晰地看到每 5 秒一个心跳请求被发送到 Nacos 的 /nacos/v1/ns/instance/beat 接口,该请求会被 Nacos 服务器内置的 naming 模块处理。
DEBUG 10720 --- [ing.beat.sender] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@665891d213 pairs: {PUT /nacos/v1/ns/instance/beat?app=unknown&serviceName=DEFAULT_GROUP%40%40sample-service&namespaceId=public&port=9000&clusterName=DEFAULT&ip=192.168.47.1 HTTP/1.1: null}{Content-Type: application/x-www-form-urlencoded}{Accept-Charset: UTF-8}{Accept-Encoding: gzip,deflate,sdch}{Content-Encoding: gzip}{Client-Version: 1.3.2}{User-Agent: Nacos-Java-Client:v1.3.2}{RequestId: 6447aa06-9d70-41ea-83ef-cd27af1d3422}{Request-Module: Naming}{Host: 192.168.31.102:8848}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive}{Content-Length: 326}
DEBUG 10720 --- [ing.beat.sender] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@5f00479a12 pairs: {PUT /nacos/v1/ns/instance/beat?app=unknown&serviceName=DEFAULT_GROUP%40%40sample-service&namespaceId=public&port=9000&clusterName=DEFAULT&ip=192.168.47.1 HTTP/1.1: null}{Content-Type: application/x-www-form-urlencoded}{Accept-Charset: UTF-8}{Accept-Encoding: gzip,deflate,sdch}{Content-Encoding: gzip}{Client-Version: 1.3.2}{User-Agent: Nacos-Java-Client:v1.3.2}{RequestId: 9fdf2264-9704-437f-bd34-7c9ee5e0be41}{Request-Module: Naming}{Host: 192.168.31.102:8848}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive}
DEBUG 10720 --- [ing.beat.sender] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@3521283812 pairs: {PUT /nacos/v1/ns/instance/beat?app=unknown&serviceName=DEFAULT_GROUP%40%40sample-service&namespaceId=public&port=9000&clusterName=DEFAULT&ip=192.168.47.1 HTTP/1.1: null}{Content-Type: application/x-www-form-urlencoded}{Accept-Charset: UTF-8}{Accept-Encoding: gzip,deflate,sdch}{Content-Encoding: gzip}{Client-Version: 1.3.2}{User-Agent: Nacos-Java-Client:v1.3.2}{RequestId: ccb6a586-897f-4036-9c0d-c614e2ff370a}{Request-Module: Naming}{Host: 192.168.31.102:8848}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive}
naming 模块在接收到心跳包后,会按下图逻辑处理心跳包并返回响应:
- naming 模块收到心跳包,首先根据 IP 与端口判断 Nacos 是否存在该服务实例?如果实例信息不存在,在 Nacos 中注册登记该实例。而注册的本质是将新实例对象存储在“实例 Map”集合中;
- 如果实例信息已存在,记录本次心跳包发送时间;
- 设置实例状态为“健康”;
- 推送“微服务状态变更”消息;
- naming 模块返回心跳包时间间隔。
到这里一次完整的心跳包处理已完成。
那 Nacos 又是如何将无效实例从可用实例中剔除呢?Nacos Server 内置的逻辑是每过 20 秒对“实例 Map”中的所有“非健康”实例进行扫描,如发现“非健康”实例,随即从“实例 Map”中将该实例删除。
高可用保证:Nacos 如何有效构建注册中心集群
上一章节讲述了 Nacos 注册中心的作用以及单点运行的方法,但是单点运行是分布式应用的大忌,在分布式架构中,任何单点都可能成为系统的瓶颈,因此在生产环境中 Nacos 都需要通过部署集群来为系统带来高可用性。因此本文围绕 Nacos 集群主要讲解以下三方面内容:
- 生产环境 Nacos 集群的设计要点;
- Nacos 集群的部署过程;
- 介绍 Nacos 集群的工作原理。
生产环境部署 Nacos 集群
下面我们来解读下 Nacos 集群架构的设计要点:
- 微服务并不是直接通过 IP 地址访问后端服务,而是采用域名访问。通过 DNS(域名解析服务)转换为具体的 IP 地址,通过域名方式屏蔽后端容易产生变化的 IP 地址。
- 底层 Nacos 自带集群间节点与数据同步方案,因此需要 Nacos 节点对外暴露 8848 与 7848 端口。其中 8848 端口的作用是对外暴露 API 与集群间数据同步,而 7848 端口则用于节点选举来确定集群领袖(Leader)。同时 Nacos 在集群环境下需要持久化应用配置、用户权限、历史信息等内置数据,因此需要额外部署 MySQL 数据库提供统一存储。
- 在 Nacos 层面,每一台服务器都有独立的 IP。我们并不建议直接将物理 IP 对外暴露,而是额外增加 VIP(虚拟 IP),通过 DNS 服务绑定 VIP,这样的好处是通过 VIP 屏蔽了Nacos集群实际的物理IP地址,同时为访问者提供了统一的接入入口,使微服务的注册接入和Nacos 集群实现细节彼此解耦,提高架构的维护性。