Spring Cloud Netflix 是在 Netflix OSS 基础上的封装,里面包含有分布式系统中常用的核心组件:Eureka、Ribbon、Hystrix、Feign、Zuul
# Spring Cloud Netflix
Netflix OSS 开源组件集成,包括Eureka、Hystrix、Ribbon、Feign、Zuul等核心组件。
- Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;
- Ribbon:负载均衡的服务调用组件,具有多种负载均衡调用策略;
- Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力;
- Feign:基于Ribbon和Hystrix的声明式服务调用组件;
- Zuul:API网关组件,对请求提供路由及过滤功能。
# Spring Cloud Eureka:服务注册与发现 (opens new window)
# Eureka简介
微服务架构需要有一个注册中心,所有的微服务都会在注册中心注册自己的地址和端口信息,每个微服务都会定时从注册中心获取服务列表,同时汇报自己的运行情况,保证整个微服务的正常运行,Eureka就是实现这一套流程的组件。
# 搭建注册中心
- IDEA初始化一个SpringBoot应用,可以在创建的选择组件:
Spring Cloud Discovery -> Eureka Server
,或者创建后手动添加pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2
3
4
启动类添加注解
@EnableEurekaServer
注解来启用Euerka注册中心功能application.yml
添加Eureka注册中心的配置
server:
port: 8001 #指定运行端口
spring:
application:
name: eureka-server #指定服务名称
eureka:
instance:
hostname: localhost #指定主机地址
client:
fetch-registry: false #指定是否要从注册中心获取服务(注册中心不需要开启)
register-with-eureka: false #指定是否要注册到注册中心(注册中心不需要开启)
server:
enable-self-preservation: false #关闭保护模式
2
3
4
5
6
7
8
9
10
11
12
13
# 搭建客户端
- 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2
3
4
启动类添加注解
@EnableDiscoveryClient
来表明这是一个Eureka客户端注意:
@EnableEurekaClient
只适用于Eureka作为注册中心,@EnableDiscoveryClient
可以是其他注册中心application.yml
添加Eureka客户端基础配置
server:
port: 8101 #运行端口号
spring:
application:
name: eureka-client #服务名称
eureka:
client:
register-with-eureka: true #注册到Eureka的注册中心
fetch-registry: true #获取注册实例列表
service-url:
defaultZone: http://localhost:8001/eureka/ #配置注册中心地址
# defaultZone: http://replica1:8002/eureka/,http://replica2:8003/eureka/ 注册到多个配置中心
2
3
4
5
6
7
8
9
10
11
12
访问注册中心http://localhost:8001
即可客户端成功注册
# 搭建注册中心集群
由于多个微服务都是注册到注册中心通过服务列表来相互调用,一旦注册中心宕机,会导致所有服务都出现问题,所以我们需要多个注册中心来保证服务正常运行
根据注册中心配置文件新增一个
application-replica1.yml
、application-replica2.yml
配置文件,根据下面配置修改端口号,hostname(为了在注册中心里面好区分),注册中心的地址即可注意:注册中心地址使用的
replica2
这样的域名,可以在hosts里面修改下文件127.0.0.1 replica2 127.0.0.1 replica3
1
2
server:
port: 8002
# 另外一个注册中心的端口号
# port: 8003
spring:
application:
name: eureka-server
eureka:
instance:
hostname: replica1
client:
serviceUrl:
defaultZone: http://replica2:8003/eureka/ #注册到另一个Eureka注册中心
# defaultZone: http://replica2:8002/eureka/ # 另外一个注册中心的地址
fetch-registry: true
register-with-eureka: true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
访问任意一个注册中心即可看到成功注册的客户端和备份注册中心
# Eureka常用配置
eureka:
client: #eureka客户端配置
register-with-eureka: true #是否将自己注册到eureka服务端上去
fetch-registry: true #是否获取eureka服务端上注册的服务列表
service-url:
defaultZone: http://localhost:8001/eureka/ # 指定注册中心地址
enabled: true # 启用eureka客户端
registry-fetch-interval-seconds: 30 #定义去eureka服务端获取服务列表的时间间隔
instance: #eureka客户端实例配置
lease-renewal-interval-in-seconds: 30 #定义服务多久去注册中心续约
lease-expiration-duration-in-seconds: 90 #定义服务多久不去续约认为服务失效
metadata-map:
zone: jiangsu #所在区域
hostname: localhost #服务主机名称
prefer-ip-address: false #是否优先使用ip来作为主机名
server: #eureka服务端配置
enable-self-preservation: false #关闭eureka服务端的保护机制
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Spring Cloud Ribbon:负载均衡的服务调用 (opens new window)
# Ribbon简介
在微服务架构中, 我们服务一般都会部署多个,用户请求进来会调用哪一个就需要负载均衡平衡,当我们使用RestTemplate来调用其他服务时,Ribbon可以很方便的实现负载均衡功能。
RestTemplate简单使用 (opens new window)
# 创建user-service客户端模块
供 Ribbon 提供服务使用,同时修改配置文件,启动 user-service 8020,8021两个端口,注册到注册中心8001中,user-service 中写一些常见的CURD接口即可
application.yml
配置
server:
port: 8201
spring:
application:
name: user-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/
2
3
4
5
6
7
8
9
10
11
# 创建ribbon-service模块
通过ribbon-service
服务的 RestTemplate直接调用 user-service
模块的接口即可
- 新增依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2
3
4
application.yml
配置
server:
port: 8301
spring:
application:
name: ribbon-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/
service-url:
user-service: http://user-service # 对应注册中心中 user-server 服务的name地址
2
3
4
5
6
7
8
9
10
11
12
13
- 负载均衡使用,在创建
RestTemplate
bean基础上添加注解@LoadBalanced
即可
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2
3
4
5
6
7
8
9
- Ribbon 全局配置与指定服务配置
user-service: # 指定user-service 服务配置,全局配置去除这行即可,我实测全局配置未生效,不知道为啥
ribbon:
ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
OkToRetryOnAllOperations: true #对超时请求启用重试机制
MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
MaxAutoRetries: 1 # 切换实例后重试最大次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法
2
3
4
5
6
7
8
修改负载均衡算法
参数 com.netflix.loadbalancer.XXX | 说明 |
---|---|
RandomRule | 从提供服务的实例中以随机的方式 |
RoundRobinRule | 以线性轮询的方式,就是维护一个计数器,从提供服务的实例中按顺序选取,第一次选第一个,第二次选第二个,以此类推,到最后一个以后再从头来过; |
RetryRule | 在RoundRobinRule的基础上添加重试机制,即在指定的重试时间内,反复使用线性轮询策略来选择可用实例; |
WeightedResponseTimeRule | 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择; |
BestAvailableRule | 选择并发较小的实例 |
AvailabilityFilteringRule | 先过滤掉故障实例,再选择并发较小的实例 |
ZoneAwareLoadBalancer | 采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例 |
- 通过创建bean进行全局配置
@Bean
public IRule customRule(){
return new RandomRule(); // 随机
}
2
3
4
启动注册中心eureka-service
,启动user-service
8020、8021,客户端,启动ribbon-service
服务
调用ribbon-service
服务能看到user-service
两个端口的控制台交替打印即可,可以尝试其他负载均衡策略,查看控制台变化
# Spring Cloud Hystrix:服务容错保护 (opens new window)
# Hystrix简介
在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。Hystrix实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。
# 创建一个hystrix-service模块
- 依赖
<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-hystrix</artifactId>
</dependency>
2
3
4
5
6
7
8
application.yml
配置
server:
port: 8401
spring:
application:
name: hystrix-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/
service-url:
user-service: http://user-service
2
3
4
5
6
7
8
9
10
11
12
13
- 启动类添加注解
@EnableCircuitBreaker
开启断路器功能
# 服务熔断、服务降级、服务限流
微服务是完成一个单一的业务功能,这样做的好处是可以做到解耦,每个微服务可以独立演进。但是,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成。服务之间调用链路太长,相互调用,如果其中一个服务调用时间太长,或者不可用,大量请求堆积,占用系统资源,进而导致系统崩溃,所谓的"雪崩效应"
服务熔断:当检测到某个服务多次超时或相应时间太长,会对该服务降级处理,熔断该服务的调用,快速返回错误详细信息,避免长时间等待和资源占用,当检测到节点相应正常后,恢复调用
服务降级:从整体负荷考虑,服务分优先级,保证核心业务,暂时停止非核心业务
服务限流:限制并发请求量,超过阈值拒绝请求
整点秒杀活动,服务限流是指允许能负载的请求量进来,多余的请求拒绝;服务降级是整点秒杀时对用户注册等非核心的业务做降级处理;服务熔断是当秒杀活动访问太多导致超时,熔断该服务,并作降级处理,返回用户友好提示信息
- 服务降级演示
UserHystrixController
添加用于测试服务降级的接口:
@GetMapping("/testFallback/{id}")
public CommonResult testFallback(@PathVariable Long id) {
return userService.getUser(id);
}
2
3
4
在UserService中添加调用方法与服务降级方法,方法上需要添加@HystrixCommand
注解:
@HystrixCommand(fallbackMethod = "getDefaultUser")
public CommonResult getUser(Long id) {
return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
}
public CommonResult getDefaultUser(@PathVariable Long id) {
User defaultUser = new User(-1L, "defaultUser", "123456");
return new CommonResult<>(defaultUser);
}
2
3
4
5
6
7
8
9
# 验证
启动eureka-server
、user-service
、hystrix-service
服务;
正常调用http://localhost:8401/user/testFallback/1
返回
{
data: {
id: 1,
username: "macro",
password: "123456",
},
message: "操作成功",
code: 200,
}
2
3
4
5
6
7
8
9
关闭user-service
服务,再次调用,服务降级,直接返回预设错误信息
{
data: {
id: -1,
username: "defaultUser",
password: "123456",
},
message: "操作成功",
code: 200,
}
2
3
4
5
6
7
8
9
Hystrix的请求缓存 (opens new window)
Hystrix请求合并 (opens new window)
Hystrix Dashboard:断路器执行监控 (opens new window)
# Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用 (opens new window)
# Feign简介
Feign是声明式的服务调用工具,我们只需创建一个接口并用注解的方式来配置它,就可以实现对某个服务接口的调用,简化了直接使用RestTemplate来调用服务接口的开发量。Feign具备可插拔的注解支持,同时支持Feign注解、JAX-RS注解及SpringMvc注解。当使用Feign时,Spring Cloud集成了Ribbon和Eureka以提供负载均衡的服务调用及基于Hystrix的服务容错保护功能。
# 创建一个feign-service模块
- 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2
3
4
application.yml
配置
server:
port: 8701
spring:
application:
name: feign-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/
2
3
4
5
6
7
8
9
10
11
启动类上添加注解
@EnableFeignClients
启动Feign客户端功能Feigin客户端user-service服务的接口绑定
创建一个
user-service
接口,内容基于user-service
服务的Controller类修改为接口,保留springmvc的注解即可
// value对应要连接的user-service服务,fallback对应的为user-service降级服务的实现类
@FeignClient(value = "user-service",fallback = UserFallbackService.class)
public interface UserService {
@PostMapping("/user/create")
CommonResult create(@RequestBody User user);
@GetMapping("/user/{id}")
CommonResult<User> getUser(@PathVariable Long id);
}
2
3
4
5
6
7
8
9
- 创建降级服务实现类
@Component
public class UserFallbackService implements UserService {
@Override
public CommonResult create(User user) {
User defaultUser = new User(-1L, "defaultUser", "123456");
return new CommonResult<>(defaultUser);
}
@Override
public CommonResult<User> getUser(Long id) {
User defaultUser = new User(-1L, "defaultUser", "123456");
return new CommonResult<>(defaultUser);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 开启日志,打印详细的http请求细节
日志等级
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
2
3
4
5
6
7
- Feign常用配置,Feign中可以直接使用ribbon,hystrix配置
feign:
hystrix:
enabled: true #在Feign中开启Hystrix
compression:
request:
enabled: false #是否对请求进行GZIP压缩
mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型
min-request-size: 2048 #超过该大小的请求会被压缩
response:
enabled: false #是否对响应进行GZIP压缩
logging:
level: #修改日志级别
com.macro.cloud.service.UserService: debug
2
3
4
5
6
7
8
9
10
11
12
13
# 验证
启动eureka-server
、user-service
、feign-service
服务;
访问http://localhost:8701/user/1
,user-service
两端口控制台正常交替打印
关闭两个所有的user-service
服务,再次访问,返回为降级预设错误
{
data: {
id: -1,
username: "defaultUser",
password: "123456",
},
message: "操作成功",
code: 200,
}
2
3
4
5
6
7
8
9
# Spring Cloud Zuul:API网关服务 (opens new window)
# Zuul简介
API网关为微服务架构中的服务提供了统一的访问入口,客户端通过API网关访问相关服务。API网关的定义类似于设计模式中的门面模式,它相当于整个微服务架构中的门面,所有客户端的访问都通过它来进行路由及过滤。它实现了请求路由、负载均衡、校验过滤、服务容错、服务聚合等功能。
# 创建一个zuul-proxy模块
- 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2
3
4
application.yml
配置
server:
port: 8801
spring:
application:
name: zuul-proxy
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/
2
3
4
5
6
7
8
9
10
11
启动类添加注解
EnableZuulProxy
启用Zuul的API网关功能启动
eureka-server
,两个user-service
,feign-service
和zuul-proxy
来演示Zuul的常用功能配置路由规则:
zuul:
routes: #给服务配置路由
user-service:
path: /userService/**
feign-service:
path: /feignService/**
ignored-services: user-service,feign-service #关闭默认路由配置
2
3
4
5
6
7
验证接口,因为已经使用了feign,所以可以看到userService两个端口的控制台交替答应成功的日志记录,默认的路由规则就是注册到注册中心的服务名,也就是说访问userService,user-service都能成功调用user-service服务的接口,关闭默认路由后,默认的访问路径就失效了
访问http://localhost:8801/userService/user/1
,http://localhost:8801/user-service/user/1
都可以发现请求路由到了user-service上了;
访问http://localhost:8801/feignService/user/1
,http://localhost:8801/feign-service/user/1
都可以发现请求路由到了feign-service上了
- 配置路由访问前缀,访问接口底之前加上前缀路径即可
zuul:
prefix: /proxy #给网关路由添加前缀
2
验证:http://localhost:8801/proxy/userService/user/1
# 查看路由信息
- 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2
3
4
application-yml
配置
management:
endpoints:
web:
exposure:
include: 'routes'
2
3
4
5
- 测试
查看简单路由信息:http://localhost:8801/actuator/routes/
查看详细路由信息:http://localhost:8801/actuator/routes/details
# Header过滤及重定向添加Host(未测试)
Zuul在请求路由时,默认会过滤掉一些敏感的头信息,以下配置可以防止路由时的Cookie及Authorization的丢失
zuul:
sensitive-headers: Cookie,Set-Cookie,Authorization # 配置过滤敏感的请求头信息,设置为空就不会过滤
add-host-header: true # 设置为true重定向是会添加host请求头
2
3
过滤器
路由与过滤是Zuul的两大核心功能,路由功能负责将外部请求转发到具体的服务实例上去,是实现统一访问入口的基础,过滤功能负责对请求过程进行额外的处理,是请求校验过滤及服务聚合的基础
Zuul中集中典型的过滤器类型
过滤器 | 说明 | 场景 |
---|---|---|
pre | 在请求被路由到目标服务前执行, | 比如权限校验、打印日志等功能; |
routing | 在请求被路由到目标服务时执行,这是使用Apache HttpClient或Netflix Ribbon构建和发送原始HTTP请求的地方; | |
post | 在请求被路由到目标服务后执行 | 比如给目标服务的响应添加头信息,收集统计数据等功能; |
error | 请求在其他阶段发生错误时执行 |
zuul过滤器生命周期
自定义过滤器
添加下面自定义的过滤器,调用http://localhost:8801/user-service/user/1
即可看到日志生效;
仿照写了一个其他的过滤器,判断请求是否又accessToken或者其他参数的,没有生效,过滤器理解还不通透。
Prelog Filter
package com.macro.cloud.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* Created by macro on 2019/9/9.
*/
@Component
public class PreLogFilter extends ZuulFilter {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
/**
* 过滤器类型,有pre、route、post、error四种。
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器执行顺序,数值越小优先级越高。
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 是否进行过滤,返回true会执行过滤。
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 自定义的过滤器逻辑,当shouldFilter()返回true时会执行。
*/
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String host = request.getRemoteHost();
String method = request.getMethod();
String uri = request.getRequestURI();
LOGGER.info("Remote host:{},method:{},uri:{}", host, method, uri);
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
禁用过滤器
zuul:
PreLogFilter: # 对应过滤器的名称
pre: # 对应过滤器的类型
disable: true # 是否禁用过滤器,默认为false
2
3
4
核心过滤器
核心过滤器
过滤器名称 | 过滤类型 | 优先级 | 过滤器的作用 |
---|---|---|---|
ServletDetectionFilter | pre | -3 | 检测当前请求是通过DispatcherServlet处理运行的还是ZuulServlet运行处理的 |
Servlet30WrapperFilter | pre | -2 | 对原始的HttpServletRequest进行包装 |
FormBodyWrapperFilter | pre | -1 | 将Content-Type为application/x-www-form-urlencoded或multipart/form-data的请求包装成FormBodyRequestWrapper对象 |
DebugFilter | route | 1 | 根据zuul.debug.request的配置来决定是否打印debug日志 |
PreDecorationFilter | route | 5 | 对当前请求进行预处理以便执行后续操作 |
RibbonRoutingFilter | route | 10 | 通过Ribbon和Hystrix来向服务实例发起请求,并将请求结果进行返回 |
SimpleHostRoutingFilter | route | 100 | 只对请求上下文中有routeHost参数的进行处理,直接使用HttpClient向routeHost对应的物理地址进行转发 |
SendForwardFilter | route | 500 | 只对请求上下文中有forward.to参数的进行处理,进行本地跳转 |
SendErrorFilter | post | 0 | 当其他过滤器内部发生异常时的会由它来进行处理,产生错误响应 |
SendResponseFilter | post | 1000 | 利用请求上下文的响应信息来组织请求成功的响应内容 |
# Ribbon和Hystrix的支持
由于Zuul自动集成了Ribbon和Hystrix,所以Zuul天生就有负载均衡和服务容错能力,我们可以通过Ribbon和Hystrix的配置来配置Zuul中的相应功能,所以可以在配置文件中直接添加Ribbon,Hystrix的配置即可