![2dee13049a3e4b62358e25c5048ad29c.png](https://i-blog.csdnimg.cn/blog_migrate/0c6e141f9413310722e2ffb1bee3e5b9.jpeg)
SpringCloud简介
官网地址
SpringCloud是基于SpringBoot的一整套实现微服务的框架。它提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。最重要的是,基于SpringBoot,会让开发微服务架构非常方便。常用的组件如下:
- Eureka:注册中心
- Zuul:服务网关
- Ribbon:负载均衡
- Feign:服务调用
- Hystix:熔断器
Eureka
代码示例
搭建Eureka Server
引入依赖
<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>
配置
server:
port: 10001
spring:
application:
name: eureka-server
eureka:
client:
# 是否注册自己的信息到EurekaServer,默认是true
register-with-eureka: false
# 是否拉取其它服务的信息,默认是true
fetch-registry: false
# EurekaServer的地址,如果是集群,需要加上其它Server的地址。
service-url:
defaultZone: http://127.0.0.1:${server.port}/eureka
添加@EnableEurekaServer注解
在启动类添加@EnableEurekaServer
注解
查看euraka面板
启动服务并访问,http://127.0.0.1:10001/,如下:
![694b209c18f16ef407d1742ed9c25bce.png](https://i-blog.csdnimg.cn/blog_migrate/9da5570ff6d6c6358650a767a46cd775.jpeg)
注册Eureka客户端
引入依赖
<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-web</artifactId>
</dependency>
配置
server:
port: 10002
spring:
application:
name: consumer_test1
eureka:
client:
service-url:
# EurekaServer地址
defaultZone: http://127.0.0.1:10001/eureka
instance:
# 当其它服务获取地址时提供ip而不是hostname
prefer-ip-address: true
# 指定自己的ip信息,不指定的话会自己寻找
ip-address: 127.0.0.1
添加@EnableDiscoveryClient注解
在启动类添加@EnableDiscoveryClient
注解
查看euraka面板
![953665daac3e4a483d9646ed7ebcc2dc.png](https://i-blog.csdnimg.cn/blog_migrate/843862c28f8db1337f54986b755c2e05.png)
在status一列中,显示以下信息:
- UP(1):代表现在是启动了1个示例,没有集群
- DESKTOP-KJUJ87C:consumer_test1:10002:是示例的名称(instance-id),
- 默认格式是:
${hostname} + ${spring.application.name} + ${server.port}
- instance-id是区分同一服务的不同实例的唯一标准,因此不能重复。
- 默认格式是:
通过instance-id属性来修改它的构成:
eureka:
instance:
instance-id: ${spring.application.name}:${server.port}
重启服务再查看:
![2e9b24f3e0464661f743417410b019af.png](https://i-blog.csdnimg.cn/blog_migrate/01323029ae75cd048c3a8caff67c4963.png)
Eureka 详解
上面用代码演示了一下,接下来就详细解释一下euraka
基础架构
Eureka架构中的三个核心角色:
- 服务注册中心
Eureka的服务端应用,提供服务注册和发现功能 - 服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。 - 服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。
搭建Eureka Server 集群
Eureka Server配置
修改上文中的Eureka Server配置,区别在于defaultZone地址不同,删掉register-with-eureka: false
和 fetch-registry: false
。因这两个参数默认为true,可以不用配置,这样eureka server就可以互相注册并拉取到每个服务的信息了
Eureka Server服务1
server:
port: 10001
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10003/eureka
Eureka Server服务2
server:
port: 10003
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10001/eureka
查看面板
![f460130e363c9621cff86591618ea338.png](https://i-blog.csdnimg.cn/blog_migrate/b6d3e5779017ce227b595afcdd915772.png)
Eureka客户端修改
在defaultZone
添加服务端地址,用逗号隔开即可
server:
port: 10002
spring:
application:
name: consumer_test1
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10001/eureka,http://127.0.0.1:10003/eureka
instance:
instance-id: ${spring.application.name}:${server.port}
Eureka特性
服务注册
无论是服务的提供者还是服务的消费者都需要向EurekaServer注册服务
eureka.client.register-with-erueka=true
属性
是否注册自己的信息到EurekaServer,默认为true
当服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-erueka=true
参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。第一层Map的Key就是服务名称,第二层Map的key是服务的实例id。
eureka.client.fetch-registry=true
属性
是否拉取其它服务的信息,默认是true
当服务消费者启动是,会检测eureka.client.fetch-registry=true
参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒
会重新获取并更新数据。可以通过registry-fetch-interval-seconds
来修改拉取服务的间隔时间
eureka:
client:
registry-fetch-interval-seconds: 5
服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告知自己是否还存活着,这个就是所谓的服务续约。当然,这个间隔时间可以通过以下参数修改:
lease-renewal-interval-in-seconds
属性
服务续约的间隔,默认为30秒
lease-expiration-duration-in-seconds
属性
服务失效时间,默认值90秒
默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。但是在开发时,这个值有点太长了,比如开发时经常会关掉一个服务,然而Eureka依然认为服务在活着。所以开发阶段可以时可以适当修改一下值。
eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
失效剔除和自我保护
- 失效剔除
当一个服务出现故障而不能提供服务时,Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。可以通过eureka.server.eviction-interval-timer-in-ms
参数对其进行修改,单位是毫秒,开发时可以适当配置一下值,因为重启一个服务时,Eureka Server 60秒才反应过来,对开发造成不便。
- 自我保护
当关停一个服务,就会在Eureka面板看到一条警告。
![3ce4c7df5e3208bc99fff9af2a0c8579.png](https://i-blog.csdnimg.cn/blog_migrate/4b9493516cd042bc8ce79375202629db.jpeg)
这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。
在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。
eureka:
server:
# 关闭自我保护模式(缺省为打开)
enable-self-preservation: false
# 扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 1000
Ribbon
什么是Ribbon
Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间层服务连接在一起。Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。
负载均衡
Eureka中已经集成了负载均衡组件,所以不用引用ribbon的依赖
代码示例
服务提供方
引入依赖
<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>
添加@EnableDiscoveryClient注解
在启动类添加@EnableDiscoveryClient注解
配置
server:
port: 10005
spring:
application:
name: Product-01
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10001/eureka
instance:
# 当其它服务获取地址时提供ip而不是hostname
prefer-ip-address: true
# 指定自己的ip信息,不指定的话会自己寻找
ip-address: 127.0.0.1
添加方法
@RestController
public class TestController {
@GetMapping("/tt")
public String tt(){
return "访问服务提供者成功!";
}
}
服务消费方
还是以上文的consumer_test1为例子
开启负载均衡
在启动类里面注入RestTemplate并添加@LoadBalanced
注解
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerTest1Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerTest1Application.class, args);
}
}
新增service
这里注意一下url,这里直接写服务名称。服务名称不能用下划线_,要换成中划线-。如果用了下划线restTemplate会报错:Request URI does not contain a valid hostname。
@Service
public class TestService {
@Autowired
private RestTemplate restTemplate;
public String tt(){
String url = "http://PRODUCT-01/tt";
String s = restTemplate.getForObject(url, String.class);
System.out.println(s);
return s;
}
}
新增controller
@RestController
public class TConsumerController {
@Autowired
private TestService testService;
@GetMapping("/getTest")
public String getTest(){
return testService.tt();
}
}
测试
![e6624c0b69d74f9f34a75b9776339685.png](https://i-blog.csdnimg.cn/blog_migrate/9ec9ad087c283a319db0aa21203a4729.png)
原理分析
为什么restTemplate.getForObject直接写服务名称就会调用成功呢?这是因为LoadBalancerInterceptor
根据service名称,获取到了服务实例的ip和端口,如下源码:
restTemplate.getForObject发起请求,被LoadBalancerInterceptor拦截,接着进入intercept方法获取到uri和服务名称。
![f83354d1230a8bfa81e7e90e1f124cff.png](https://i-blog.csdnimg.cn/blog_migrate/d95c121c84e20b55cf3b17e88ed29602.jpeg)
在RibbonLoadBalanceClient
类中接着执行execute方法,getLoadBalancer
获取一个负载均衡器,然后getServer
根据 负载均衡器的算法在服务列表中选择server。
![b9cf053aee145b3b1808e72c1c6c0e87.png](https://i-blog.csdnimg.cn/blog_migrate/61def0cf69984efac429ac3b14517885.jpeg)
负载均衡策略
先增加一个服务提供者,端口号为10006,以便后面测试。
![8d1e3b9c7d57abeaad5d970a11337982.png](https://i-blog.csdnimg.cn/blog_migrate/222d84bb8ece3c52dd1922d34c070401.png)
在刚才的源码中看到拦截是使用RibbonLoadBalanceClient
来进行负载均衡的,其中有一个choose方法,这个方法的作用:就是从LoadBalancer中为指定的服务选择服务示例
public interface ServiceInstanceChooser {
/**
* Chooses a ServiceInstance from the LoadBalancer for the specified service.
* @param serviceId The service ID to look up the LoadBalancer.
* @return A ServiceInstance that matches the serviceId.
*/
ServiceInstance choose(String serviceId);
}
测试一下
@SpringBootTest
class ConsumerTest1ApplicationTests {
@Autowired
private RibbonLoadBalancerClient client;
@Test
public void test(){
for (int i = 0; i < 20; i++) {
ServiceInstance instance = this.client.choose("Product-01");
System.out.println(instance.getHost() + ":" + instance.getPort());
}
}
}
查看控制台
可以看到默认就是轮询
![84d670d8c112f4a60474d42943da53cf.png](https://i-blog.csdnimg.cn/blog_migrate/0389991dccc1880a67ec7e3e55e32708.jpeg)
修改负载均衡策略
继续查看源代码会看到有一个这样的方法,然后这个rule就是用来进行服务选择的。
![0c30f27411f211b6f86beeab1098063f.png](https://i-blog.csdnimg.cn/blog_migrate/56850bdc9713ccd9c01c5315d426354a.jpeg)
可以看到这个rule默认就是轮询
![11e348ffae957a16b47a5cc1fc18c82b.png](https://i-blog.csdnimg.cn/blog_migrate/40c0cd6e62b733b92d470d956bfbe178.jpeg)
![9ee6998591fedc2d4366980364caca45.png](https://i-blog.csdnimg.cn/blog_migrate/4b6c25989702e0eb7c16f8519bbb006d.jpeg)
那么有多少种策略呢?可以查看它的实现类,如下
![56737b765eca5d9dfd8cb129e3bcb28e.png](https://i-blog.csdnimg.cn/blog_migrate/286e1dc2b00af3e0ffae664c7f03753c.jpeg)
那怎么去修改负载均衡策略呢?可以修改如下配置:
格式是:
{服务名称}.ribbon.NFLoadBalancerRuleClassName
,值就是IRule的实现类。
Product-01:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
再测试
可以发现已经是随机了
![9b3837d0411f9ec98d8a7dc89fef21a0.png](https://i-blog.csdnimg.cn/blog_migrate/252038ce6b6ee63bfae3ba054bc8999a.jpeg)
重试机制
当把10006端口的服务提供者停掉后,再次访问http://localhost:10002/getTest,如下结果。这是因为服务剔除的延迟,consumer并不会立即得到最新的服务列表,此时再次访问你会得到错误提示。
![fdf91777689cf4679f5595d0111cc5f7.png](https://i-blog.csdnimg.cn/blog_migrate/5a278a588403a0c843cf4795c16dd1b8.png)
但是,不是还有一个服务10005是正常的吗,此时,可以用重试机制,当一次服务调用失败后,不会立即抛出异常,而是再次重试另一个服务。
配置
根据如下配置,当访问到某个服务超时后,它会再次尝试访问下一个服务实例,如果不行就再换一个实例,如果不行,则返回失败。切换次数取决于MaxAutoRetriesNextServer
参数的值
spring:
application:
name: consumer_test1
cloud:
loadbalancer:
retry:
enabled: true # 开启Spring Cloud的重试功能
Product-01:
ribbon:
ConnectTimeout: 250 # Ribbon的连接超时时间
ReadTimeout: 1000 # Ribbon的数据读取超时时间
OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数
测试
![31539e141bca2bbc675637ceeccc9425.png](https://i-blog.csdnimg.cn/blog_migrate/defa386b6e4a98321b783a6fb031a09d.jpeg)
Hystix
简介
Hystix,即熔断器。它是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。
服务雪崩
服务雪崩是一种因“服务提供者的不可用”导致“服务调用者不可用”,并将不可用逐渐放大的现象。
例如A为服务提供者, B为A的服务调用者, C和D是B的服务调用者. 当A的不可用,引起B的不可用,并将不可用逐渐放大C和D时, 服务雪崩就形成了。
服务降级
服务降级就是优先保证核心服务,而非核心服务不可用或弱可用。
服务降级目的就是先保证主要服务的畅通,一切资源优先让给主要服务来使用。比如访问一个商品详情时,获取评论时10s都还没有响应,但是商品服务是正常的,这时候就可以做服务降级,保证商品服务正常,商品的评论就给出温馨的提示即可。
代码示例
服务消费方
引入依赖
在服务消费方(consumer_test1)引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
添加@EnableCircuitBreaker注解
亦可以直接写
@SpringCloudApplication
,该注解包含下面三个注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ConsumerTest1Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerTest1Application.class, args);
}
}
编写降级方法
@HystrixCommand(fallbackMethod = "ttFallback")
:用来声明一个降级逻辑的方法。这里要注意,熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。
@RestController
public class TConsumerController {
@Autowired
private TestService testService;
@HystrixCommand(fallbackMethod = "ttFallback")
@GetMapping("/getTest")
public String getTest(){
return testService.tt();
}
public String ttFallback(){
return "系统繁忙,请稍后再试!";
}
}
服务提供方
模拟服务网络延迟
Hystix的默认超时时长为1s,所以这里设置2s,消费方就会进行服务降级。这里对端口为10006的服务方改造。
@RestController
public class TestController {
@GetMapping("/tt")
public String tt() throws InterruptedException {
Thread.sleep(2000);
return "访问服务提供者成功!";
}
}
测试
访问http://localhost:10002/getTest,结果如下:
![0fc74edc07d7ba6128574d44324011e0.png](https://i-blog.csdnimg.cn/blog_migrate/a951fa8a20cced746cf21eddfd4b3bd6.jpeg)
优化
不知道看到测试结果后有没有伙伴想到上文的重试机制呢?为什么没有进行重试呢?那是因为ribbon的读取时间设置成了一秒。因此重试机制没有被触发,而是先触发了熔断。所以,Ribbon的超时时间 一定要小于Hystix的超时时间。
![ce092ea81f38ae4136bff4324235e6b0.png](https://i-blog.csdnimg.cn/blog_migrate/94af4b667ea84eead059432e7735b031.jpeg)
那么可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
来设置Hystrix超时时间。在消费方增加如下配置:
###配置请求超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 7000
###配置具体方法超时时间
tt:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
在消费方指定commandkey
@HystrixCommand(commandKey = "tt",fallbackMethod = "ttFallback")
再测试
![632099e87ee01f7f52fb45740613b764.png](https://i-blog.csdnimg.cn/blog_migrate/710ff8f50ee5f41e4c592c55670e3a54.jpeg)
Feign
简介
Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。Spring Cloud 给 Feign 添加了支持Spring MVC注解,并整合Ribbon及Eureka进行支持负载均衡。
代码示例
引入依赖
在服务消费端引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Feign的客户端
配置需要调用的微服务接口,然后添加@FeignClient
注解,value指定服务名称。那么Feign会通过动态代理,生成实现类,并且Feign会根据注解生成URL,并访问获取结果。
@FeignClient("Product-01")
public interface TTFeignClient {
@GetMapping("/tt")
String tt();
}
开启Feign功能
在启动类添加@EnableFeignClients
注解
服务消费方
把RestTemplate相关的代码都注释掉,改造如下:
@Service
public class TestService {
// @Autowired
// private RestTemplate restTemplate;
@Autowired
private TTFeignClient ttFeignClient;
public String tt(){
return ttFeignClient.tt();
}
}
服务提供方
不变
测试
![46e884fd6a45322b09d98c7af3310a7c.png](https://i-blog.csdnimg.cn/blog_migrate/3f18ec7316f2ad491d99d0ebfec79b51.jpeg)
负载均衡
feign已经集成了ribbon依赖和自动配置,所以不需要像上文ribbon那样,引入依赖和配置 restTemplate。直接配置即可,如下:
Product-01:
ribbon:
ConnectTimeout: 250 # Ribbon的连接超时时间
ReadTimeout: 1000 # Ribbon的数据读取超时时间
OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数
Hystix
Feign默认也有对Hystix的集成,所以启动Hystix也只需一行配置:
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
请求压缩
Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:
feign:
compression:
request:
enabled: true # 开启请求压缩
response:
enabled: true # 开启响应压缩
同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:
feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限
注:上面的数据类型、压缩大小下限均为默认值。
Zuul网关
简介
网关即是一个网络整体系统中的前置门户入口。请求首先通过网关,进行路径的路由,定位到具体的服务节点上。Spring Cloud Zuul是整合Netflflix公司的Zuul开源项目实现的微服务网关,它实现了请求路由、负载均衡、校验过虑等功能。
代码示例
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
配置
/consumer_test1/**
开头的请求,代理到http://127.0.0.1:10002,也就是请求到consumer_test1服务上
server:
port: 10008
spring:
application:
name: gateway
zuul:
routes:
# 这里是路由id,可随意写
consumer_test1:
# 这里是映射路径
path: /consumer_test1/**
# 映射路径对应的实际url地址
url: http://127.0.0.1:10002
添加@EnableZuulProxy注解
在启动类添加@EnableZuulProxy
注解
测试
![e74519c8ed2f08588ec8218d3115008f.png](https://i-blog.csdnimg.cn/blog_migrate/991f079675d9629927d6569dba034c93.jpeg)
优化
可以看到测试是通过了,但是地址却被写死了,那如果这个服务是多个实例的话,是不是要手动添加每一个地址。显然,这不应该。那么就应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对。
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
添加@EnableDiscoveryClient注解
在启动类添加@EnableDiscoveryClient
注解
添加配置
eureka配置
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10001/eureka,http://127.0.0.1:10003/eureka
instance:
# 当其它服务获取地址时提供ip而不是hostname
prefer-ip-address: true
# 指定自己的ip信息,不指定的话会自己寻找
ip-address: 127.0.0.1
zuul路由配置
因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。
zuul:
routes:
# 这里是路由id,随意写
consumer_test1:
# 这里是映射路径
path: /consumer_test1/**
# 映射路径对应的实际url地址
serviceId: consumer_test1
测试
![e74519c8ed2f08588ec8218d3115008f.png](https://i-blog.csdnimg.cn/blog_migrate/991f079675d9629927d6569dba034c93.jpeg)
路由规则配置
默认路由规则
默认情况下,一切服务的映射路径就是服务名本身。
- 例如服务名为:
consumer_test1
,则默认的映射路径就是:/consumer_test1/**
简化版路由规则
在上文的配置中,配置的规则是这样的:
zuul.routes.<route>.path=/xxx/**
: 来指定映射路径。<route>
是自定义的路由名zuul.routes.<route>.serviceId=/consumer_test1
:来指定服务名。
而大多数情况下,配置的<route>
路由名称往往和 服务名会写成一样的。因此Zuul就提供了一种简化的配置语法:zuul.routes.<serviceId>=<path>
上文的配置可以简化为一条:
zuul:
routes:
consumer_test1: /consumer_test1/**
路由前缀
通过zuul.prefix=/api
来指定了路由的前缀,这样在发起请求时,路径就要以/api开头。路径/api/consumer_test1/getTest
将会被代理到/consumer_test1/getTest
zuul:
prefix: /api # 添加路由前缀
routes:
consumer_test1: # 这里是路由id,随意写
path: /consumer_test1/** # 这里是映射路径
service-id: consumer_test1 # 指定服务名称
负载均衡和熔断
Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:
zuul:
retryable: true
ribbon:
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 2000 # 通信超时时间(ms)
OkToRetryOnAllOperations: true # 是否对所有操作重试
MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000 # 熔断超时时长:6000ms
过滤器
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个功能往往是通过Zuul提供的过滤器来实现的。
ZuulFilter
查看源码,可以看到ZuulFilter中有四个重要的方法,如下:
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
abstract public String filterType();
abstract public int filterOrder();
// 来自IZuulFilter
boolean shouldFilter();
// IZuulFilter
Object run() throws ZuulException;
}
shouldFilter
:返回一个Boolean
值,判断该过滤器是否需要执行。返回true执行,返回false不执行。run
:过滤器的具体业务逻辑。filterType
:返回字符串,代表过滤器的类型。包含以下4种:pre
:请求在被路由之前执行routing
:在路由请求时调用post
:在routing和errror过滤器之后调用error
:处理请求时发生错误调用
filterOrder
:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
过滤器执行生命周期
- 正常流程:
- 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
- 异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
![2ce1717e27ebb6129939dae297961d09.png](https://i-blog.csdnimg.cn/blog_migrate/fc736d2722c7cca34a74ae2b83184207.jpeg)
场景使用
- 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
- 异常处理:一般会在error类型和post类型过滤器中结合来处理。
- 服务调用时长统计:pre和post结合使用。
自定义过滤器
代码示例
package com.ao.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class LoginFilter extends ZuulFilter {
/*登录校验,前置拦截*/
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
/*返回true,代表过滤器生效。*/
@Override
public boolean shouldFilter() {
return true;
}
/*登录校验逻辑*/
@Override
public Object run() {
// 获取Zuul提供的请求上下文对象
RequestContext ctx = RequestContext.getCurrentContext();
// 从上下文中获取request对象
HttpServletRequest req = ctx.getRequest();
//从请求中获取token
String token = req.getParameter("my-token");
// 校验
if(token == null || "".equals(token.trim())){
// 没有token,登录校验失败,拦截
ctx.setSendZuulResponse(false);
// 返回401状态码
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
测试
没有token示例
![ef79f7e382c9fc5e41346a07a725473b.png](https://i-blog.csdnimg.cn/blog_migrate/c1525ee7bd24056d8730f839611b4803.png)
存在token示例
![8a73f5de19343136ce1494330db47132.png](https://i-blog.csdnimg.cn/blog_migrate/96e0a64c704d098fdd1f86e1fbfab65b.jpeg)