SpringCloud学习笔记
一 、概述
1. 什么是springcloud
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
SpringCloud通常被称作微服务框架,关于微服务框架的产生背景以及实现思想,我在知乎上找到了一篇文章,觉得写得很好
一文详解微服务架构, 等我学习完了再回过头去看。
从上面的springcloud的基本信息来看,SpingCloud就是一系列微服务框架的有序集合,而微服务通常要解决的问题主要是下面的四个:
- 客户端如何访问多个服务?——API网关
- 各个服务之间如何直接通信?——RPC(同步), 消息队列(异步)
- 多个服务如何管理?——服务治理,注册和发现
- 服务挂了怎么办?——重试机制、服务熔断、服务降级、服务限流
2.优点和缺点
优点
- 提升开发交流,每个服务足够内聚,足够小,代码容易理解;
- 服务独立测试、部署、升级、发布;
- 按需定制的DFX,资源利用率,每个服务可以各自进行x扩展和z扩展,而且,每个服务可以根据自己的需要部署到合适的硬件服务上;
- 每个服务按需要选择HA的模式,选择接受服务的实例个数;
- 容易扩大开发团队,可以针对每个服务(service)组件开发团队;
- 提高容错性(fault isolation),一个服务的内存泄露并不会让整个系统瘫痪;
- 新技术的应用,系统不会被长期限制在某个技术栈上;
缺点
- 没有银弹,微服务提高了系统的复杂度;
- 开发人员要处理分布式系统的复杂性;
- 服务之间的分布式通信问题;
- 服务的注册与发现问题;
- 服务之间的分布式事务问题;
- 数据隔离再来的报表处理问题;
- 服务之间的分布式一致性问题;
- 服务管理的复杂性,服务的编排;
- 不同服务实例的管理。
3.微服务技术栈
微服务条目 | 落地技术 |
---|---|
服务开发 | SpringBoot,Spring,SpringMVC |
服务配置与管理 | Netflix公司的Archaius。阿里的Diamond等 |
服务注册与发现 | Eureka、Consul、Zookeeper等 |
Rest、RPC、gRPC | |
服务熔断器 | Hystrix、Envoy等 |
负载均衡 | Ribbon、Nginx等 |
服务接口调用(客户端调用服务的简化工具) | Feign等 |
Kafka、RabbitMQ、ActiveMQ等 | |
SpringCloudConfig、Chef等 | |
服务路由(API网关) | Zuul等 |
Zabbix/Nagios、Metrics、Specatator等 | |
Zipkin、Brave、Dapper等 | |
Docker、OpenStack、Kuberbetes等 | |
数据流操作开发包 | SpringCloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息) |
事件消息总栈 | SpringCloud Bus |
4.SpringBoot和SpringCloud的关系
- SpringBoot专注于快速方便地开发单个个体服务。
- SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理,服务发现,断路由,微代理,事件总线,全局锁,决策竞选,分布式绘画等等继承服务。
- SpringBoot可以离开SoringCloud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系。
- SpringBoot专注于快速、方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架
5.学习疑问
- 什么是微服务?
将单一的应用程序划分为多组小的服务,每个服务运行在自己独立的系统环境中,服务之间相互协调工作,为应用程序提供最终的功能。
- 微服务之间是如何独立通讯的?
- SpringCloudhe Dubbo有哪些区别?
- 谈谈SpringBoot和SpringCloud的理解。
- 什么是服务熔断?什么是服务降级?
- 微服务的优缺点分别是什么?
- 你所知道的微服务技术栈有哪些?
- eureka和zookeeper都可以提供服务注册与发现的功能,它们二者有什么区别?
二、SpringCloud和中间件
1. 中间件概述
中间件与操作系统、数据库并列为传统基础软件的三驾马车。传统中间件的概念,是诞生于上一个“分布式”计算的年代,也就是小规模局域网中的服务器、客户端计算模式,在操作系统之上、应用软件之下的“中间层”软件。按照自己的理解,中间件属于用户访问具体业务的时候需要经过中间层处理的一种软件。
2. 服务治理中间件
服务治理中间件包含服务注册与发现、服务路由、负载均衡、自我保护、丰富的治理管理机制等功能。其中服务路由包含服务上下线、在线测试、机房就近选择、A/B测试、灰度发布等等。负载均衡支持根据目标状态和目标权重进行负载均衡。自我保护包括服务降级、优雅降级和流量控制。
3. 配置中心中间件
在单体的做法中,我们一般的做法是把属性配置和代码硬编码放在一起 ,这没有什么问题。但是在分布式的系统中,由于存在多个服务实例,需要分别管理每个具体服务工程中的配置,每当其中一个服务的配置出现了改动,整个服务就要重新地启动,这样开发管理变得十困难。于是,我们把分布式系统中的配置信息抽取出来统一管理,这个管理的中间件成为配置中心。
4. 网关中间件
API 网关就是出现在系统边界上的一个管控服务,它的出现主要是为了解决访问认证、报文转换、访问统计等问题。作为网关中间件,至少要具备如下的四种功能:
- 统一的接入服务: 为各种无线应用提供统一的接入服务,提供一个高性能、高并发、高可靠的网关服务。不仅如此,还要支持负载均衡、容灾切换和异地灵活。
- 协议适配功能: 网关对外请求的协议一般就是HTTP协议,而后台发布的服务要么是RESF服务要么是RPC服务。所以网关要根据请求进来的协议进行协议分配。
- 流量管控功能: 网关作为所有请求流量的入口,当请求流量瞬间剧增的时候,需要有一定的管控能力保障服务器。
- 安全防护功能: 网关对收到的所有外来请求都要有一定的安全辨识能力,防止后台遭受到恶意请求的攻击。
5. 全链路监控中间件
一个分布式的系统在面对用户的请求的时候一般都会经过大量的分布式服务,如此我们对这些请求的日志监控信息就不能统一集中的管理起来。于是全链路中间件就是为了解决这个问题而诞生的,它主要包含的功能有:
- 定位慢调用
- 定位各种异常和错误
- 展现依赖和拓扑
- Trace调用链
- 应用警告
三、 Spring Cloud Eureka
Netfix Eureka是由Netfix开源的一款基于RESF的服务发现组件。包括Eureka Server 和 Eureka Client。
1. 入门案例
当我使用书上的办法,先创建一个父工程,里面添加spring-cloud-dependencies依赖,然后分别创建子工程,添加上对应的Eureka server 和 Eureka client依赖的时候,客户端启动的时候一直报一种某个核心配置类无法初始化bean的情况,后面我尝试了下面的办法,终于错误消除了。
- 创建一个空的项目文件夹。
- 在这个空的项目中添加Eureka server模块(spring initializr方式),命名好自己模块的信息之后,从idea自带的选择依赖中选择“spring web”和“eureka server”依赖。完成之后,该模块的pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.achao</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件的设置如下:
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
allow-redirects: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 和创建Eureka server模块的方法一样,选择一个spring initialzr创建Eureka client模块,完成之后的pom文件是这样的:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.achao</groupId>
<artifactId>eureka-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件的设置如下:
server:
port: 8081
spring:
application:
name: demo-01
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
- 访问服务管理页面:http://localhost:8761
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dyN91J27-1615045775877)(C:\Users\achao\AppData\Roaming\Typora\typora-user-images\image-20210208182016545.png)]
2. Eureka Server 的REST API简介
Eureka Server提供的REST API允许其他语言通过HTTP REST 的方式接入到Eureka的服务发现中,主要的方式是通过访问一些特定的地址,传入特定格式的xml内容来实现对服务发现的操作。
2.1 Eureka REST API接口列表
操作 | http动作 | 描述 |
---|---|---|
注册新的应用实例 | POST/eureka/apps/{appId} | 可以输入json或xml格式的body,成功返回204 |
注销应用实例 | DELETE/euraka/apps/{appId}/{instancedId} | 成功返回200 |
应用实例发送心跳 | PUT/euraka/apps{appId}/{instancedId} | 成功放回200,如果instancedId不存在的话,返回404 |
查询所有实例 | GET/eureka/apps | 成功返回200,输出json或xml数据 |
查询指定appId的实例 | GET/eureka/apps/{appId} | 成功返回200,输出json或xml数据 |
根据指定appId和instancedId查询 | GET/eureka/apps/{appId}/{instancedId} | 成功返回200,输出json或xml数据 |
根据指定instancedId查询 | GET/eureka/instances/{instancedId} | 成功返回200,输出json或xml数据 |
暂停应用实例 | PUT/eureka/apps/{appId}/{instanceId}/status?value=OUT_OF_SERVICE | 成功返回200,失败返回500 |
恢复应用实例 | DELETE/eureka/apps/{appId}/{instacedId}/status?value=UP(value参数可以不传) | 成功返回200,失败返回500 |
更新元数据 | PUT/eureka/apps/{appId}/{instancedId}/metadate?key=value | 成功返回200,失败返回500 |
根据vip地址查询 | GET/eureka/vips/{vipAddress} | 成功返回200,失败返回500 |
根据svip查询 | GET/eureka/svips/{svipAddress} | 成功返回200,失败返回500 |
上面的接口可以自己尝试一下
3. Eureka核心类
3.1 InstanceInfo
InstanceInfo类主要用来代表注册的服务实例,每一个服务实例对应着一个具体的InstanceInfo对象。里面常见的属性有实例id,应用名,ip地址,端口号,应用实例的url等等。
3.2 LeaseInfo
LeaseInfo代表的是应用实例的租约信息,与上述的InstanceInfo构成“has-a”的关系,主要包含的属性有间隔周期,有效市场,注册时间,剔除的时间,等等
3.4 ServiceInstance
ServiceInstance是Spring Cloud对服务发现实例信息的抽象接口,约定了服务发现实例具有哪些的通用信息,主要约定的通用信息(属性)有:服务id,服务端口,服务元数据信息等等。
Spring Cloud对该接口的实现类之一为EurekaRegistration,同时这个类还实现了Closeable接口,它的作用之一就是在关闭的时候调用eurekaClient.shutdown()方法,实现优雅关闭客户端。
3.5 InstanceStatus
InstanceStatus用来标识服务实例的状态,它是一个枚举类,主要有以下的五种状态UP,DOWN,STARTING,OUT_OF_SERVICE,UNKOWN。
4. 服务的核心操作
对于服务发现来说,围绕服务实例主要有如下几个重要的操作:
- 服务注册(register)
- 服务下线(cancel)
- 服务租约(renew)
- 服务剔除(evict)
围绕上面的几个功能,Eureka设计了下面的几个核心操作类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yEjIxyRH-1615045775880)(C:\Users\achao\AppData\Roaming\Typora\typora-user-images\image-20210105235610137.png)]
其中的LeaseManager以及LookupService是Eureka关于服务发现相关操作定义的接口类,前者定义了服务写操作相关的方法,后者定义的查询操作的相关方法。具体的代码内容可以去查看依赖下的类源码
5. Eureka的设计理念
作为一个服务注册及发现中心,主要解决如下几个问题:
5.1 服务实例是怎么样注册到服务中心的
本质上就是调用register方法将服务注册到服务中心管理,SpringBoot可以使用自动配置,自动帮你实现服务信息的注册。
5.2 服务实例如何从服务中心剔除的
正常情况下服务实例在关闭应用的时候,应该通过钩子方法(模板模式)或者其他生命周期回调方法调用de-register方法,来删除自身服务实例的信息,另外还有可能服务实例本身不够健壮导致无法删除自身的信息,服务中心会根据心跳机制来主动剔除。
5.3 服务实例一致性的问题
由于服务注册及发现中心不可能是单点的,其自身势必有个集群,那么服务实例注册信息必须在集群环境下保持一致,这就跟Eureka架构有关,具体由下面的四个方面来阐述:
- AP优于CP
- Peer to Peer架构
- Zone及Region设计
- SELFPRESERVATION设计
5.4 AP优于CP
在分布式系统领域有一个重要的CAP理论,而一个分布式的系统最多只能满足它们中的两个特性,要么CA,要么CP,要么AP,实际的分布式环境中,网络条件相对不可控,所以某个节点或者网络出现故障的问题时,要求我们的分布式系统能够满足分期分区容忍性。下面是三个特性的具体说明:
- 一致性(Consistency):数据存在多个副本的情况下,由于网络或者是其他原因,在对数据进行更新的时候,不能对所有的副本同时更新成功,从而导致数据不一致的问题。
- 可用性(Availablity):不管分布式的服务正常的还是错误的,客户端访问服务的时候都能在一定的延时内得到响应,而不是出现“访问无果”的情况。
- 分区容忍性(Partition Tolerance):指的分布式系统中的某个节点或者网络分区出现了故障的时候,**整个系统仍然能对外提供满足一致性和可用性的服务。**也就是说部分故障不影响整体使用。
5.5 Peer to Peer架构
一般来说,分布式系统中的数据在多个副本之间的复制方式,可以分为主从复制和对等复制。主从复制主要的应用场景在于那种读多写少的环境,主节点负责写数据,然后同步到从节点,从节点主要用来满足用户的读操作。而对等模式,不分主从,每个节点都可以用来读写操作,这样的话,面临的主要问题就是如何处理数据同步和冲突。当然,两种都要进行负载均衡的设计。
5.6 Zone及Region设计
- 背景
用户量比较大或者地理位置分布范围很广的项目,一般会有多个机房(zone)。这个时候如果上线的spring cloud服务的话,我们希望一个机房内的服务优先调用同一个机房内的服务,当同一个机房的服务不可用的时候,再去调用其它机房的服务,已达到减少延时的作用。
- 概念
eureka提供了region和zone两个概念来进行分区,这两个概念均来自亚马逊的AWS:
- region:可以简单理解为地理上的分区,比如亚洲地区,或者华北地区,再或者北京等等,没有具体大小的限制。根据项目具体的情况,可以自行合理划分region。
- zone:可以简单理解为region内的具体机房,比如region划分为北京,然后北京有两个机房,就可以在此region之下划分出zone1.zone2两个zone。
如图所示,有一个region:beijing,下面有zone-1和zone-2两个分区,每个分区内有一个注册中心Eureka Server和一个服务提供者Service 我们在zone-1内创建一个Consumer-1服务消费者的话,其会优先调用同一个zone内的Service-1,当Service-1不可用时,才会去调用zone-2内的Service-2
详细的客户端服务配置可以参考这篇文章:eureka分区的深入讲解
5.7 SELF PRESERVATION设计
在分布式系统设计里头,通常需要对应用实例的存活进行健康检查,这里比较关键的问题就是要处理好网络偶然抖动或短暂不可用时造成的误判。所谓的误判指的是当某个应用实例因为网络的抖动无法定时发送心跳的时候,服务中心会把它剔除,但是这个服务实际上是可以使用的。所以在这个心跳基础上,服务中心去检测的是总的应用实例的最少心跳次数,从而让那些出现网络抖动的应用实例有了”喘息“的机会。具体的配置使用案例可以参考:Eureka的自我保护机制
6. Eureka参数调优及监控
eureka参数调优分为client和server,主要就是通过配置文件对Eureka客户端和服务端进行参数设置,client主要包括常见的基本参数,定时任务参数和HTTP参数三大类;而server端也是分为基本参数,response cache参数,peer相关参数和HTTP参数等。参数的内容过多,不建议每一个去背记,只需要知道有那几个参数便可以了,当真正使用的时候,运用起来,那个时候便能记忆得更牢固。
eureka配置的使用手册可以参考这篇文章 Spring Cloud Eureka 之常用配置解析
7.Eureka实战
7.2 Eureka集群实战
根据Eureka的描述,其中的服务可以分为region和zone,当同一个zone中的服务调用者调用同一个zone中的服务时,如果当前zone中的服务挂掉了,会选择调用同一个region中的另一个zone中的服务。按照这个特点,构建如下的测试环境:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETebo1On-1615045775885)(C:\Users\achao\AppData\Roaming\Typora\typora-user-images\image-20210217100425874.png)]
此集群的实战参考的是eureka分区的深入讲解文章中的分区搭建,本篇不作过多的累赘,只是对一些相关的配置进行说明。
首先是两个server段的配置文件:
spring:
application:
name: Server-2 #Server-1
server:
port: 30001
eureka:
instance:
#使用ip地址来显示当前服务中心的访问地址而不是默认的主机地址
prefer-ip-address: true
#状态页面信息的地址
status-page-url-path: /actuator/info
#健康检查的地址
health-check-url-path: /actuator/health
hostname: localhost
client:
#是否进行注册
register-with-eureka: true
#要不要去注册中心获取其他服务的地址
fetch-registry: true
#优先使用同一个zone中的服务
prefer-same-zone-eureka: true
#地区
region: beijing
availability-zones:
beijing: zone-2,zone-1
# 注册到各自的服务中
service-url:
zone-1: http://localhost:30000/eureka/
zone-2: http://localhost:30001/eureka/
之后是两个client段的配置文件:
spring:
application:
name: service # 服务名称
server:
port: 30010
eureka:
instance:
prefer-ip-address: true
status-page-url-path: /actuator/info
health-check-url-path: /actuator/health
# 所属的zone区
metadata-map:
zone: zone-1
client:
register-with-eureka: true
fetch-registry: true
prefer-same-zone-eureka: true
#地区
region: beijing
availability-zones:
beijing: zone-1,zone-2
#注册到两个zone中
service-url:
zone-1: http://localhost:30000/eureka/
zone-2: http://localhost:30001/eureka/
zone.name: zone-1
client中的controller层的代码:
@RestController
public class HicController {
@Value("${zone.name}")
private String zoneName;
@GetMapping("/hi")
public String Hi(){
return zoneName;
}
}
consumer消费者段的配置文件和client中的大致相同,不同的是controller中调用的是client段中暴露出来的服务:
@RestController
public class HiController {
@Autowired
private RestTemplate restTemplate;
private String url = "http://SERVICE";
@GetMapping("/consumer")
public String Hi(){
return restTemplate.getForObject(url + "/hi", String.class);
}
}
ss HicController {
@Value("${zone.name}")
private String zoneName;
@GetMapping("/hi")
public String Hi(){
return zoneName;
}
}
consumer消费者段的配置文件和client中的大致相同,不同的是controller中调用的是client段中暴露出来的服务:
```java
@RestController
public class HiController {
@Autowired
private RestTemplate restTemplate;
private String url = "http://SERVICE";
@GetMapping("/consumer")
public String Hi(){
return restTemplate.getForObject(url + "/hi", String.class);
}
}
这里直接访问的地址是通过client段向server段中