本篇文章我们将实现下图所示的架构
我们可以接着使用前面《SpringCloud之Feign使用篇》中的项目环境,当然也也可以自己重新搭建都是可以的,这里我们使用的springcloud的版本是:Greenwich.RELEASE
新建module spring-cloud-gateway-server
需要我们在spring-cloud-gateway-server 项目pom.xml添加依赖,这里 父工程我没有使用之前的,而是选择了spring-boot,因为在springcloud gateway中,web使用的webflux 没有使用spring mvc ,而之前的父工程中引入mvc。
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.6.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-commonsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webfluxartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
在网关服务 application.yml 中配置网关路由规则
server:
port: 8020
spring:
application:
name: spring-cloud-gateway-server
cloud:
gateway:
# 配置路由,可以配置多个
routes:
- id: order-microservice-router # id 自定义路由的id
uri: http://127.0.0.1:7070 # uri就是 目标服务地址
predicates: # 断言,也就是路由条件 ,这里使用了path作为路由条件
- Path=/order/**
- id: user-microservice-router
uri: http://127.0.0.1:8081
predicates:
- Path=/user/**
#eureka 配置
eureka:
client:
service-url:
# eureka server url
defaultZone: http://EurekaServerA:9090/eureka,http://EurekaServerB:9091/eureka
register-with-eureka: true
fetch-registry: true
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
其中我们配置了2个路由,path=/order 开头的转到http://127.0.0.1:7070 服务上,也就是咱们的订单服务,path=/user 开头的转到http://127.0.0.1:8081 服务上,也就是我们的用户服务。
我们首先将 Eureka Server集群 9090与9091提起来,然后再将用户服务与订单服务提起来,在起订单服务之前为了测试方便 需要给订单服务里面添加个接口:
我们在订单服务spring-cloud-order-service-provider 的controller里面添加个接口(之前的getTodayFinishOrderNum方法不用动,用户服务还要调用它)
@RestController
@RequestMapping("/order/data")
public class OrderStatisticServiceController {
@Value("${server.port}")
private Integer port;
@Value("${spring.application.name}")
private String appName;
/**
* 根据用户id获取今日完单数
* @param id 用户ID
* @return 完单数
*/
@GetMapping("/getTodayFinishOrderNum/{id}")
public Integer getTodayFinishOrderNum(@PathVariable("id") Integer id){
System.out.println("我是"+port);
if (port==7070){
try {// 睡眠10s
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return port;
}
// 获取appName
@GetMapping("/getAppName/{id}")
public String getAppName(@PathVariable("id") Integer id){
return appName;
}
}
然后将服务提起来,可以看到已经全部起来了,将gateway项目也提起来。
浏览器请求:http://127.0.0.1:8020/order/data/getAppName/100 ,注意咱们这个ip与port是网关服务的
这时候可以看到网关将咱们的请求打到order服务上了。
浏览器再请求:http://127.0.0.1:8020/user/data/getTodayStatistic/100
网关就把咱们这次请求打到user服务上了。
在springcloud gateway 中内置了很多 Predicates 功能,实现了很多路由匹配规则匹配到对应的路由。
断言类型 | 描述 |
---|---|
DateTime 时间断言 | 根据请求时间在配置的时间的前面,后面,之前 |
Cookie 类断言 | 指定Cookie正则匹配指定值 |
Header 请求头 | 指定Header正则匹配指定值,请求头中是否包含某个属性 |
Host请求主机断言 | 请求Host匹配指定值 |
Method请求方式断言 | 请求Method匹配指定请求方式 |
Path 请求路径类断言 | 请求路径正则匹配指定值 |
QueryParam 请求参数 | 查询参数正则匹配指定值 |
RemoteAddr远程地址类断言 | 请求远程地址匹配指定值 |
在上面第二节的简单使用中,我们把uri 这个目标服务器写死了,固定到了某个ip:port上面,其实springcloud gateway支持从注册中心获取服务列表进行访问,能够实现动态路由。下面我们就演示下动态路由
这里我们需要把uri 配置成 lb://在注册中心注册的服务名
server:
port: 8020
spring:
application:
name: spring-cloud-gateway-server
cloud:
gateway:
# 配置路由,可以配置多个
routes:
- id: order-microservice-router # id 自定义路由的id
# uri就是目标服务地址,这里使用服务名的方式,gateway会帮我们去注册中心中获取服务列表
uri: lb://spring-cloud-order-service-provider
predicates: # 断言,也就是路由条件 ,这里使用了path作为路由条件
- Path=/order/**
- id: user-microservice-router
uri: lb://spring-cloud-user-service-consumer
predicates:
- Path=/user/**
#eureka 配置
eureka:
client:
service-url:
# eureka server url
defaultZone: http://EurekaServerA:9090/eureka,http://EurekaServerB:9091/eureka
register-with-eureka: true
fetch-registry: true
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
在项目启动类上面添加@EnableDiscoveryClient的注解,表示开启Eureka
重启网关服务,在测试前,我们为了测试方便还是需要修改订单服务的controller方法,将端口加到返回值中。
@RestController
@RequestMapping("/order/data")
public class OrderStatisticServiceController {
@Value("${server.port}")
private Integer port;
@Value("${spring.application.name}")
private String appName;
/**
* 根据用户id获取今日完单数
* @param id 用户ID
* @return 完单数
*/
@GetMapping("/getTodayFinishOrderNum/{id}")
public Integer getTodayFinishOrderNum(@PathVariable("id") Integer id){
System.out.println("我是"+port);
if (port==7070){
try {// 睡眠10s
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return port;
}
// 获取appName
@GetMapping("/getAppName/{id}")
public String getAppName(@PathVariable("id") Integer id){
// appName +port
return appName+String.valueOf(port);
}
}
我们重启下订单服务,然后进行测试。
浏览器访问:http://127.0.0.1:8020/order/data/getAppName/100,多访问几次
我们可以看到有7070的也有7071的,说明我们配置的动态路由生效了。
在springchoud gateway中 过滤器可以分为Global过滤器与Gateway过滤器
过滤器类型 | 说明 |
---|---|
Global过滤器 | 这种就是作用在所有的路由上 |
Gateway过滤器 | 这种就是作用在指定的路由上面 |
然后根据过滤器的生命周期分可以分为 pre过滤与post过滤
生命周期点 | 介绍 |
---|---|
pre | 这个就是请求来的时候,可以对请求做一些动作,我们可利用这种过滤器实现身份验证,在集群中选择请求的微服务,记录调试信息等。 |
post | 这个就是响应过来的时候,可以对响应做某些过滤动作,这种过滤器可用来为响应添加标准的 HTTP Header,收集统计信息和指标,将响应从微服务发送给客户端等。 |
我们这里自定义一个全局过滤器,然后实现一个黑白名单的功能。
这里我们写一个BlackListFilter类然后实现GlobalFilter, Ordered 接口
@Component
public class BlackListFilter implements GlobalFilter, Ordered {
// 这里模拟下黑名单
private static final List<String> blackList=new ArrayList<>();
static {
blackList.add("127.0.0.1");// 模拟本机地址
}
/**
* 进行过滤操作
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求
ServerHttpRequest request = exchange.getRequest();
// 获取响应
ServerHttpResponse response = exchange.getResponse();
// 获取客户端ip
String host = request.getRemoteAddress().getHostString();
System.out.println("remote host:"+host);
if (blackList.contains(host)){ // 这个客户端ip在黑名单里面
// 设置响应码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
String data = "拒绝访问";
DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
HttpHeaders headers = response.getHeaders();
// 设置中文乱码
headers.add("content-type", "text/html;charset=utf-8");
return response.writeWith(Mono.just(wrap));
}
// 放行到下一个过滤器
return chain.filter(exchange);
}
/**
* 主要是多个过滤器的时候,需要对过滤器排序,
* 先经过哪个,后经过哪个,数值越小,这个优先级越高
* @return order优先级
*/
@Override
public int getOrder() {
return 0;
}
}
实现这个GlobalFilter 接口主要是说明我这个是个全局过滤器并在定义一些过滤的动作,
然后实现这个Ordered 接口主要是对咱们自定义过滤器的优先级做一个设置,数值越小,filter就越早执行,优先级就越高
重启网关项目,然后测试一下:
浏览器输入http://127.0.0.1:8020/order/data/getAppName/100 ,可以看到请求被拦截了。
我们要想对网关做高可用,就得依赖网关上层的nginx来实现,接下来我们简单配置下nginx 来实现gateway的高可用。
修改网关服务的appliction.yml
spring:
application:
name: spring-cloud-gateway-server
cloud:
gateway:
# 配置路由,可以配置多个
routes:
- id: order-microservice-router # id 自定义路由的id
# uri就是目标服务地址,这里使用服务名的方式,gateway会帮我们去注册中心中获取服务列表
uri: lb://spring-cloud-order-service-provider
predicates: # 断言,也就是路由条件 ,这里使用了path作为路由条件
- Path=/order/**
- id: user-microservice-router
uri: lb://spring-cloud-user-service-consumer
predicates:
- Path=/user/**
#eureka 配置
eureka:
client:
service-url:
# eureka server url
defaultZone: http://EurekaServerA:9090/eureka,http://EurekaServerB:9091/eureka
register-with-eureka: true
fetch-registry: true
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
---
spring:
profiles: g1
server:
port: 8020
---
spring:
profiles: g2
server:
port: 8021
这样启动的时候可以配置一下idea
这样我们就有了8020 与8021 两个网关实例
如果没有nginx ,可以去 http://nginx.org/en/download.html 下载一个,这个地方选择自己系统下载就好了。
然后就是nginx安装,window 解压就能用,linux系统稍微麻烦些也可以使用yum安装,mac系统可以直接使用brew 安装 brew install nginx
接着就是nginx的配置文件了,修改nginx.conf
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
upstream gateway {
server 127.0.0.1:8020;
server 127.0.0.1:8021;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://gateway;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
这里主要是
upstream gateway {
server 127.0.0.1:8020;
server 127.0.0.1:8021;
}
location / {
proxy_pass http://gateway;
}
接着启动一下nginx
因为咱们nginx是80端口,所以直接在浏览器访问:http://127.0.0.1/order/data/getAppName/100
接着咱们模拟一个网关服务挂了(手动关闭一个8021)
然后访问,还是能访问到