一、简介
spring cloud 是一系列框架的集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。spring cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 spring boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包,使用 spring cloud 一站式解决方案能在从容应对业务发展的同时大大减少开发成本。
二、spring cloud 技术组成
框架名 | 作用 |
---|---|
eureka | 微服务治理,服务注册和发现 |
ribbon | 负载均衡、请求重试 |
hystrix | 断路器,服务降级、熔断 |
feign | ribbon + hystrix 集成,并提供声明式客户端 |
hystrix dashboard 和 turbine | hystrix 数据监控 |
zuul | API 网关,提供微服务的统一入口,并提供统一的权限验证 |
config | 配置中心 |
bus | 消息总线, 配置刷新 |
sleuth+zipkin | 链路跟踪 |
Spring Cloud 对比 Dubbo
Dubbo
- Dubbo只是一个远程调用(RPC)框架
- 默认基于长连接,支持多种序列化格式
Spring Cloud
框架集
- 提供了一整套微服务解决方案(全家桶)
- 基于http调用, Rest API
三、准备工作
创建父工程,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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhanlijuan</groupId>
<artifactId>order-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<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>
service - 服务
item service 商品服务
application.yml 配置文件
spring:
application:
name: item-service
server:
port: 8001
sp:
username: zhangsan
controller
@RestController
@Slf4j
public class TestController {
@Value("${server.port}")
private String port;
@Value("${sp.username}")
private String username;
@GetMapping("/getPort")
public String getPort(){
log.info("============================");
return "hello the server port is "+port;
}
@GetMapping("/getUsername")
public String getUsername(){
log.info("============================");
return "sp.username is "+username;
}
}
user service 用户服务
application.yml 配置文件
spring:
application:
name: user-service
server:
port: 8101
order service 订单服务
application.yml 配置文件
spring:
application:
name: order-service
server:
port: 8201
四、eureka 注册与发现
创建 eureka-server 项目
application.yml
spring:
application:
name: eureka-server
server:
port: 2001
eureka:
server:
#eureka 的自我保护状态:心跳失败的比例,在15分钟内是否超过85%,如果出现了超过的情况,Eureka Server会将当前的实例注册信息保护起来,同时提示一个警告,一旦进入保护模式,
#Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据。也就是不会注销任何微服务
enable-self-preservation: false
instance:
hostname: eureka1 #eureka 集群服务器之间,通过 hostname 来区分
client:
fetch-registry: false #不向自身注册
register-with-eureka: false #不从自身拉取注册信息
主启动类加注解 @EnableEurekaServer
触发eureka服务器的自动配置
修改 hosts 文件,添加 eureka 域名映射
C:\Windows\System32\drivers\etc\hosts
127.0.0.1 eureka1
127.0.0.1 eureka2
启动,并访问 http://eureka1:2001 测试
五、service provider 服务提供者
修改 item-service、user-service、order-service,把微服务注册到 eureka 服务器
pom.xml 添加eureka依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml 添加eureka注册配置
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka
- eureka.instance.lease-renewal-interval-in-seconds 心跳间隔时间,默认 30 秒
- defaultZone,默认位置,可以修改为具体地理位置,比如:beiJing, shangHai, shenZhen 等,表示 eureka 服务器的部署位置, 需要云服务器提供
- eureka.client.registry-fetch-interval-seconds 拉取注册信息间隔时间,默认 30 秒
主程序启用eureka客户端
主程序添加 @EnableDiscoveryClient 注解
启动服务,在eureka中查看注册信息
六、eureka 和 “服务提供者”的高可用
1 item-service 高可用
item-service服务配置 启动参数 --server.port 可以覆盖yml中的端口配置
启动,访问 eureka 查看 item-service 注册信息
2 eureka 高可用
eureka-server添加两个配置文件
application-eureka1.yml
eureka:
instance:
hostname: eureka1 #主机名
client:
register-with-eureka: true #单台服务器不注册
fetch-registry: true #单台服务器不拉取
service-url:
defaultZone: http://eureka2:2002/eureka
application-eureka2.yml
eureka:
instance:
hostname: eureka2 #主机名
client:
register-with-eureka: true #单台服务器不注册
fetch-registry: true #单台服务器不拉取
service-url:
defaultZone: http://eureka1:2001/eureka
配置启动参数 –spring.profiles.active 和 --server.port
eureka1 启动参数:
--spring.profiles.active=eureka1 --server.port=2001
eureka2 启动参数
--spring.profiles.active=eureka2 --server.port=2002
#如果在命令行运行,可以在命令行中添加参数:
java -jar xxx.jar --spring.profiles.active=eureka1 --server.port=2001
分别启动eureka两个服务,端口分别为2001,2002
浏览器输入 http://eureka1:2001/ 查看注册信息
浏览器输入 http://eureka2:2002/ 查看注册信息
eureka客户端注册时,向两个服务器注册,修改item-service,user-service,order-service
eureka:
client:
service-url:
# 当一个 eureka 服务宕机时,仍可以连接另一个 eureka 服务
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
3 eureka运行机制
- 注册:客户端会一次一次反复连接服务器,直到注册成功为止
- 拉取: 客户端每30秒重复拉取注册表,刷新注册表
- 心跳: 客户端每30秒发送一次心跳数据,服务器在连续3次收不到一个服务的心跳后,会删除这个服务
- 自我保护模式:由于网络不稳定,或网络中断, 15分钟内,85%服务器出现心跳异常,会进入保护模式,保护所有注册信息不删除;网络恢复之后,会自动退出保护模式恢复正常
开发调试期间,可以禁用保护模式,避免影响测试
七、Hystrix dashboard 仪表盘
新建 hystrix-dashboard 项目,pom文件添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
application.yml文件配置
server:
port: 4001
#允许抓取的服务名
hystrix:
dashboard:
proxy-stream-allow-list:
- localhost
启动类添加@EnableHystrixDashboard注解
启动服务访问测试
八、zuul API网关
zuul API 网关,为微服务应用提供统一的对外访问接口,uul 还提供过滤器,对所有微服务提供统一的请求校验。
创建springboot项目,pom.xml文件添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
application.yml
zuul 路由配置可以省略,缺省以服务 id 作为访问路径
spring:
application:
name: zuul
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
zuul:
routes:
item-service: /item-service/**
user-service: /user-service/**
order-service: /order-service/**
添加 @EnableZuulProxy 和 @EnableDiscoveryClient 注解
浏览器访问 http://localhost:3001/item-service/getPort 测试,其余服务同理
1 zuul + ribbon 负载均衡
zuul 已经集成了 ribbon,默认已经实现了负载均衡(轮询)
2 zuul + ribbon 重试
pom文件添加spring-retry 依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
配置 zuul 开启重试(默认不开启),并配置 ribbon 重试参数
zuul:
retryable: true
ribbon:
MaxAutoRetriesNextServer: 2 #更换实例的次数
MaxAutoRetries: 1 #当前实例重试次数,尝试失败会更换下一个实例
OkToRetryOnAllOperations: true #默认只对GET请求重试, 当设置为true时, 对POST等所有类型请求都重试
3 zuul + hystrix 降级
对商品服务进行降级
@Component
public class ItemFB implements FallbackProvider {
/**
* 设置针对哪个后台服务进行降级
* item-service:只针对商品服务进行降级
* “*”: 对所有服务都应用当前降级类
* null: 对所有服务都应用当前降级类
*/
@Override
public String getRoute() {
return "item-service";
}
/**
* 向客户端发回的降级响应
* - 错误提示
* - 缓存数据
* - 根据业务逻辑返回任意数据
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
//用来关闭下面的输入流
//BAIS虚拟机中读取byte[]数组的流对象,不占用底层系统资源,不需要关闭
}
@Override
public InputStream getBody() throws IOException {
String json= "后台服务出错,请稍后重试";
return new ByteArrayInputStream(json.getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Type", "application/json;charset=UTF-8");
return httpHeaders;
}
};
}
}
重启服务,测试
如果还需要对订单服务降级,在新建一个类,定义getRoute指定降级的服务即可
4 zuul + hystrix 数据监控
zuul 已经包含 actuator 依赖,暴露 hystrix.stream 监控端点
#暴露actuator监控
management:
endpoints:
web:
exposure:
include: "*"
启动服务,浏览器访问测试
http://localhost:4001/hystrix
填入 feign 监控路径:http://localhost:3001/actuator/hystrix.stream
hystrix 熔断
整个链路达到一定的阈值,默认情况下,10秒内产生超过20次请求,则符合第一个条件。
满足第一个条件的情况下,如果请求的错误百分比大于阈值,则会打开断路器,默认为50%。
Hystrix的逻辑,先判断是否满足第一个条件,再判断第二个条件,如果两个条件都满足,则会开启断路器
断路器打开 5 秒后,会处于半开状态,会尝试转发请求,如果仍然失败,保持打开状态,如果成功,则关闭断路器
使用 apache 的并发访问测试工具 ab
http://httpd.apache.org/docs/current/platform/windows.html#down
用 ab 工具,以并发50次,来发送20000个请求
ab -n 20000 -c 50 http://localhost:3001/item-service/35
断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法
5 zuul 请求过滤
定义过滤器,继承 ZuulFilter
@Component
public class AccessFilter extends ZuulFilter {
//过滤器的类型:pre,routing,post,error
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;//pre
}
/**
* 顺序号 该过滤器顺序要 > 5,才能得到 serviceid
* zuul有5个默认的过滤器,在第5个过滤器中向上下文对象添加了serviceId属性,后面的过滤器才能访问serviceId
*/
@Override
public int filterOrder() {
return 6;//即FilterConstants.PRE_DECORATION_FILTER_ORDER+1
}
/**
* 针对当前请求是否要执行过滤代码
* 请求商品要检查权限,请求用户和订单不检查权限
*/
@Override
public boolean shouldFilter() {
//判断当前调用的后台服务,是不是item-service
//获得调用的服务id,
RequestContext currentContext = RequestContext.getCurrentContext();
String serviceId = (String) currentContext.get(FilterConstants.SERVICE_ID_KEY);
// 如果id是item-service,返回true,否则返回false,如果要过滤所有服务,直接返回true
return "item-service".equals(serviceId);
}
//过滤代码
@Override
public Object run() {
RequestContext currentContext = RequestContext.getCurrentContext();
//接收token参数,
String token = currentContext.getRequest().getParameter("token");
//如果没有token,阻止继续调用,直接返回响应
if (token == null || "".equals(token)) {
//阻止继续调用
currentContext.setSendZuulResponse(false);
//直接返回响应 JsonResult - {code:xx,msg:xx,data:null}
currentContext.addZuulResponseHeader("Content-Type", "application/json;charset=UTF-8");
currentContext.setResponseBody("Not login!未登录!");
}
//zuul过滤器返回的数据设计为以后扩展使用,目前该返回值没有被使用
return null;
}
}
启动服务测试,http://localhost:3001/item-service/getPort 没有token参数不允许访问,有参数可以访问
6 zuul Cookie过滤
zuul 会过滤敏感 http 协议头,默认过滤以下协议头
- Cookie
- Set-Cookie
- Authorization
- 可以设置 zuul 不过滤这些协议头
zuul:
sensitive-headers:
九、hystrix + turbine 集群聚合监控
hystrix dashboard 一次只能监控一个服务实例,使用 turbine 可以汇集监控信息,将聚合后的信息提供给 hystrix dashboard 来集中展示和监控
创建turbin项目,pom文件添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
application.yml
spring:
application:
name: turbine
server:
port: 5001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
turbine:
app-config: zuul
cluster-name-expression: new String("default")
主启动类添加注解@EnableTurbine
启动服务访问测试
网关服务在启动一个端口是3002
3001服务器产生监控数据 http://localhost:3001/item-service/getPort
3002服务器产生监控数据 http://localhost:3002/item-service/getPort
turbine 监控路径 http://localhost:5001/turbine.stream
在 hystrix dashboard 中填入turbine 监控路径,开启监控 http://localhost:4001/hystrix
十、config服务配置中心
yml 配置文件保存到 git 服务器,例如 github.com 或 gitee.com,微服务启动时,从服务器获取配置文件
创建config配置中心项目,
config 服务器,config 配置中心从 git 下载所有配置文件。
而其他微服务启动时从 config 配置中心获取配置信息。
pom文件添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
git仓库新建一个git-config文件夹,然后将item-service,order-service,user-service项目的yml配置文件,复制到config文件夹,并改名item-service-dev.yml,order-service-dev.yml,user-service-dev.yml,然后清空三个项目中的application.yml文件
禁止配置中心的配置信息覆盖客户端配置
默认配置中心配置优先级高,配置中心配置会覆盖客户端的所有配置,包括命令行参数配置,这样我们在item-service配置的端口号启动参数会无效
item-service 启动参数:
--service.port=8001
我们可以设置禁止配置中心的配置将客户端配置覆盖掉
在item-service-dev.yml,order-service-dev.yml,user-service-dev.yml三个配置文件中添加下面的配置
spring:
cloud:
config:
override-none: true
config配置中心项目yml配置
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/zhanlijuan_admin/CGB2104springcloud
search-paths: 01-springcloud-netflix/git-config
server:
port: 6001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
主程序添加 @EnableConfigServer
启动,访问测试
访问 item-service-dev.yml 可以使用以下形式:
http://localhost:6001/item-service-dev.yml
http://localhost:6001/item-service/dev
1 config 客户端
修改item-service,order-service,user-service三个项目,从配置中心获取配置信息,
pom.xml 添加 config 客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
在以上三个项目中添加 bootstrap.yml,引导配置文件,先于 application.yml 加载
- item-service
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
spring:
cloud:
config:
discovery:
enabled: true
service-id: config-server
#user-service-dev.yml
name: item-service
profile: dev
- order-service
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
spring:
cloud:
config:
discovery:
enabled: true
service-id: config-server
#order-service-dev.yml
name: order-service
profile: dev
- user-service
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
spring:
cloud:
config:
discovery:
enabled: true
service-id: config-server
#user-service-dev.yml
name: user-service
profile: dev
启动服务,观察item-service从配置中心获取配置信息的日志,其它order-service,user-service一样也会出现从配置中心获取配置信息
2 配置刷新
spring cloud 允许运行时动态刷新配置,可以重新从配置中心获取新的配置信息,
下面以 item-service 服务为例演示配置刷新
item-service 的 pom.xml 中添加 actuator 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
git仓库中item-service-dev.yml 配置文件中暴露 refresh 端点
#暴露actuator监控
management:
endpoints:
web:
exposure:
include: "*"
item-service,TestController类添加 @RefreshScope 注解
只允许对添加了 @RefreshScope 或 @ConfigurationProperties 注解的 Bean 刷新配置,可以将更新的配置数据注入到 Bean 中
重启服务,查看暴露的刷新端点 http://localhost:8001/actuator
修改git上面的item-service-dev.yml 的sp.username 的值为lisi
用postman访问刷新端点, 刷新配置
访问user-service 中 http://localhost:8001/getUsername ,查看动态更新的数据
十一、config bus + rabbitmq 消息总线配置刷新
post 请求消息总线刷新端点,服务器会向 rabbitmq 发布刷新消息,接收到消息的微服务会向配置服务器请求刷新配置信息
rabbitmq 安装及介绍点这里
动态更新配置的微服务,添加 spring cloud bus 依赖,并添加 rabbitmq 连接信息
修改以下微服务
item-service,user-service,order-service,zuul,config
pom.xml文件添加bus、rabbitmq 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
配置文件中添加 rabbitmq 连接信息
spring:
rabbitmq:
host: 1.13.9.40
port: 5672
username: admin
password: admin
config-server 暴露 bus-refresh 刷新端点
management:
endpoints:
web:
exposure:
include: bus-refresh
启动服务,访问http://localhost:6001/actuator查看刷新端点
postman 向 bus-refresh 刷新端点发送 post 请求 http://localhost:6001/actuator/bus-refresh
如果刷新指定的微服务,可按下面格式访问:
http://localhost:6001/actuator/bus-refresh/item-service
十二、Sleuth + Zipkin 链路跟踪
随着系统规模越来越大,微服务之间调用关系变得错综复杂,一条调用链路中可能调用多个微服务,任何一个微服务不可用都可能造整个调用过程失败,spring cloud sleuth 可以跟踪调用链路,分析链路中每个节点的执行情况
修改zuul,item-service,order-service,user-service微服务的 pom.xml,添加 sleuth 依赖,没有任何配置自动配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
通过 zuul 网关,访问 item-service,在控制台查看链路跟踪日志
调用微服务的控制台日志中,可以看到以下信息:
[服务id,请求id,span id,是否发送到zipkin]
- 请求id:请求到达第一个微服务时生成一个请求id,该id在调用链路中会一直向后面的微服务传递
- span id:链路中每一步微服务调用,都生成一个新的id
zipkin 可以收集链路跟踪数据,提供可视化的链路分析
下载 zipkin 服务器 https://github.com/openzipkin/zipkin
启动 zipkin 时,连接到 rabbitmq
java -jar zipkin-server-2.23.2-exec.jar --zipkin.collector.rabbitmq.uri=amqp://admin:admin@1.13.9.40:5672
浏览器访问 http://localhost:9411/zipkin/
微服务zuul,item-service,order-service,user-service添加 zipkin 起步依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
启动并访问服务http://localhost:3001/item-service/getUsername?token=11,
访问 zipkin 查看链路分析