简介
由于有如此众多的客户端和服务器,在云体系结构中包括一个 API 网关通常会很有帮助。网关可以负责保护和路由消息,隐藏服务,限制负载以及许多其他有用的事情。
Spring Cloud Gateway 为您提供对 API 层的精确控制,集成了 Spring Cloud 服务发现和客户端负载平衡解决方案,以简化配置和维护。
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控 / 指标,和限流。
特征
- 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
- 动态路由
- Predicates 和 Filters 作用于特定路由
- 集成 Hystrix 断路器
- 集成 Spring Cloud DiscoveryClient
- 易于编写的 Predicates 和 Filters
- 限流
- 路径重写
术语
- Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
- Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
- Filter(过滤器):这是 org.springframework.cloud.gateway.filter.GatewayFilter 的实例,我们可以使用它修改请求和响应。
官方源码示例
https://github.com/spring-cloud/spring-cloud-gateway/tree/master/spring-cloud-gateway-sample
快速入门
idea 创建 demo 工程,建立 gateway-server 网关和 consul-producer 生产者模块
依赖 consul 注册中心
安装版本:consul:1.7.2
idea 创建工程
jdk:1.8
spring-boot:2.2.1.RELEASE
spring-cloud:Hoxton.SR1
consul:1.7.2
注意版本组合:
Spring-Cloud Spring-Boot
================= ==============
Hoxton.SR4 2.2.6.RELEASE
Hoxton.SR1 2.2.1.RELEASE
Greenwich.SR3 2.1.1.RELEASE
Finchley.RELEASE 2.0.3.RELEASE
parent -?pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR1</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>
创建 gateway-server 网关
gateway-server 网关服务向外部暴露,提供转发与接口调用服务
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
</dependencies>
GatewayApplication.java
package com.forezp.demo;
import com.forezp.demo.resolver.HostAddrKeyResolver;
import com.forezp.demo.resolver.UriKeyResolver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import reactor.core.publisher.Mono;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication application =new SpringApplication(GatewayApplication.class);
//NONE,SERVLET,REACTIVE
//application.setWebApplicationType(WebApplicationType.REACTIVE);
application.run(args);
}
//对请求基于ip的访问进行限流
// @Bean
public KeyResolver hostAddrKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
//根据uri限流
@Bean
public KeyResolver uriKeyResolver(){
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}
//根据user限流
// @Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}
}
GatewayController.java
package com.forezp.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//熔断机制测试控制器
@RestController
@RequestMapping("/fallback")
public class GatewayController {
@Autowired
private RedisTemplate redisTemplateCustomize;
//触发熔断机制的回调方法
@RequestMapping("")
public String fallback() {
return "error fallback method , " + System.currentTimeMillis();
}
//触发熔断机制的回调方法
@RequestMapping("/test1")
public String fallbackTest1(){
return "error fallbackTest1 method , " + System.currentTimeMillis();
}
}
RouteConfig.java
package com.forezp.demo;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//配置路由规则
//@Configuration
public class RouteConfig {
//通过Bean实例化,配置route规则
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route(r->r.
path("/route/producer/test").
filters(f->f.stripPrefix(1).hystrix(config -> {
config.setName("testHystrix");
config.setFallbackUri("forward:/fallback");
})).uri("lb://consul-producer")).build();
}
}
application.yml
server:
port: 8089
address: 0.0.0.0
servlet:
context-path: \
# 启用服务健康检测,consul将通过http://host:port/actuator/health 检测服务的存活,默认10s一次
management:
health:
refresh:
enabled: true
# 配置日志
logging:
level:
# log 级别
org.springframework.cloud.gateway: debug
spring:
application:
# 服务名称
name: gateway-service
# 限流依赖redis做策略
redis:
host: localhost
port: 6379
database: 0
cloud:
gateway:
discovery:
locator:
# 启用本地化网关
enabled: true
# 将服务名转换为小写
lower-case-service-id: true
# 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters),filters 不是必需参数)
routes:
- id: limiter-test-id
# 转发到指定url,lb://表示匹配consul注册中的服务名所表示的根
uri: lb://consul-producer
predicates:
#- After=2017-01-20T17:42:47.789-07:00[America/Denver]
# 只限GET请求
- Method=GET
# 路径断言匹配
- Path=/limiter/producer/**
filters:
# StripPrefix=1表示过滤Path第一段(截取/test)
- StripPrefix=1
# 在转发url后加参数version=test1
- AddRequestParameter=version,test
# 添加熔断机制,当服务不可用时或超过了指定超时长,则转发到fallback
- name: Hystrix
args:
# 熔断器名称,默认为fallbackcmd
name: fallbackcmd
# 触发熔断的回调路径,需加前缀forward:
fallbackUri: forward:/fallback
# 添加限流机器,基于令牌桶算法,当服务超出限流约束,则直接拒决请求
- name: RequestRateLimiter
args:
# 用于限流的键的解析器的 Bean 对象的名字
key-resolver: '#{@uriKeyResolver}'
# 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 令牌桶总容量
redis-rate-limiter.burstCapacity: 1
- id: test1-id
# 转发到指定url
uri: http://192.168.11.45:8087
predicates:
- Method=GET
# 路径断言匹配
- Path=/route/producer/test1
filters:
# StripPrefix=1表示过滤Path第一段(截取/test)
- StripPrefix=1
# 在转发url后加参数version=test1
- AddRequestParameter=version,test1
# 添加熔断机制,当服务不可用时或超过了指定超时长,则转发到fallback
- name: Hystrix
args:
name: test1Hystrix
fallbackUri: forward:/fallback/test1
# consul注册中心
consul:
# 注册中心host
host: 192.168.110.35
# 注册中心端口
port: 8500
# 服务配置
discovery:
# 启用consul注册服务
register: true
# 注册到consul的服务名称
service-name: ${spring.application.name}
# 注册到consul的hostname(主机名称,consul是通过hostname提供给客户端连接的)
hostname: 192.168.11.45
# 注册到consul的标识
tags: version=1.0,author=jianglong
# 注册到consul中的本地服务IP
ip-address: 192.168.11.45
# 熔断器配置
hystrix:
command:
default:
execution:
timeout:
# enabled表示是否启用超时检测,默认为true
enabled: true
isolation:
thread:
# 全局熔断器超时
timeoutInMilliseconds: 5000
# test1Hystrix为熔断器名称,对应:filters.args.name
test1Hystrix:
execution:
timeout:
# enabled表示是否启用超时检测,默认为true
enabled: true
isolation:
thread:
# 特定服务熔断器超时
timeoutInMilliseconds: 2000
创建 consul-producer 生产者模块
创建生产者模块,向 consul 注册服务,由 gateway-server 网关服务提供转发
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
application.yml
server:
port: 8087
address: 0.0.0.0
servlet:
context-path: /
spring:
application:
name: consul-producer
cloud:
consul:
host: 192.168.11.45
port: 8500
discovery:
service-name: consul-producer
hostname: 192.168.11.45
tags: version=1.0,author=jianglong
# 生成唯一ID
#instance-id: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
# 开启服务健康检测
management:
health:
refresh:
enabled: true
ProducerApplication.java
package com.forezp.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Configuration;
//生产者服务
@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerApplication {
//注意:1.如果使用consul来替换eureka,而你的项目中又依赖了eureka的jar包,最好将eureka的自动配置从启动类里排除掉
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
ProducerController.java
package com.forezp.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.util.Enumeration;
//生产者控制器
@RestController
@RequestMapping("/producer")
public class ProducerController {
@Value("${server.port}")
private String port;
@Autowired
private MultipartHttpServletRequest request;
@RequestMapping(value = "/test")
public String test() {
print();
return "test api server port: " + port + ",ok:" + System.currentTimeMillis();
}
@RequestMapping(value = "/test0")
public String test0() {
print();
return "test0 api server port: " + port + ",ok:" + System.currentTimeMillis();
}
@RequestMapping(value = "/test1")
public String test1() {
print();
//测试熔断超时
try{
Thread.sleep(7 * 1000);
}catch(Exception e){
e.printStackTrace();
}
return "test1 api server port: " + port + ",ok:" + System.currentTimeMillis();
}
private void print(){
String queryString = request.getQueryString();
System.out.println("queryString:" + queryString);
System.out.println(" ===============httpHeaders================ ");
Enumeration<String> enumeration = request.getHeaderNames();
if (enumeration != null){
while(enumeration.hasMoreElements()){
String key = enumeration.nextElement();
System.out.println(key + "=" + request.getHeader(key));
}
}
}
}
限流测试
http://192.168.11.45:8089/limiter/producer/test
熔断测试
http://192.168.11.45:8089/limiter/producer/test1
http://192.168.11.45:8089/route/producer/test1
浏览器测试
|