一、Gateway 网关描叙及说明
1、微服务网关描叙
微服务网关是整个微服务API请求的入口,可以实现日志拦截、权限控制、解决跨域问题、
限流、熔断、负载均衡、黑名单与白名单拦截、授权等。
2、Gateway与Zuul的区别?
Zuul网关属于netfix公司开源的产品属于第一代微服务网关
Gateway属于SpringCloud自研发的第二代微服务网关
相比来说SpringCloudGateway性能比Zuul性能要好:
注意:Zuul基于Servlet实现的,阻塞式的Api, 不支持长连接。
SpringCloudGateway基于Spring5构建,能够实现响应式非阻塞式的Api,支持长连接,能够更好的整合Spring体系的产品。
3、过滤器与网关的区别
过滤器用于拦截单个服务
网关拦截整个的微服务
二、Gateway 网关环境搭建(Nacos+Gateway)
1、创建spring boot项目(2.0.1)
2、pom.xml 依赖
注意使用的是 webflux,不是web
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>sprng-cloud-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sprng-cloud-gateway</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!-- web组件 webflux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 监控中心,监控系统健康 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- nacos 注册中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
<!-- gateway 网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3、application.yml 配置
### 网关端口号
server:
port: 80
spring:
application:
### 服务名称,不能使用下划线
name: alibaba-gateway
cloud:
### 注册中心
nacos:
discovery:
server-addr: 192.168.177.128:8848
### 网关
gateway:
### 开启基于注册中心的路由表。gateway可以通过开启以下配置来打开根据服务的serviceId来匹配路由,
### 默认是大写,如果需要小写serviceId,则配置# spring.cloud.gateway.locator.lowerCaseServiceId:true
discovery:
locator:
enabled: true
###路由策略
routes:
### 配置方式一:绝对路径
### 路由id, 如果不写的话默认是uuid 唯一值
- id: baidu
####转发http://www.mayikt.com/
uri: http://www.baidu.com/
### 匹配规则
predicates:
- Path=/baidu/**
### 配置方式二:根据serviceId 动态获取url路径
- id: member
#### 基于lb负载均衡形式转发, 而是lb://开头,加上serviceId
uri: lb://alibaba-server
### 这个是过滤器,对应的是filters 配置,有写好的过滤器,应该也可以自定义
filters:
- StripPrefix=1
### 匹配规则,可以配置多个,使用正则匹配,请求地址携带***(/***/)跳转到我们配置的uri,如:uri/***
predicates:
- Path=/alibaba-server/**
4、启动类
@SpringBootApplication
class SprngCloudGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(SprngCloudGatewayApplication.class, args);
}
}
5、测试效果
配置方式一:访问 127.0.0.1/baidu 会直接跳转到百度首页
配置方式二:
alibaba-server 是我本地的服务,已注册到nacos,并提供了getUserId 接口
如下:使用网关80端口+ 服务id ,转发到具体的服务
nacos 服务列表
三、Gateway 网关过滤器
gateway 过滤器分为俩种。GatewayFilter 与 GlobalFilter。
GlobalFilter :全局过滤器
GatewayFilter :将应用到单个路由或者一个分组的路由上。
还有内置的过滤器断言机制
1、全局过滤器(token 验证)
有token 参数放行,无返回错误信息
/**
* TODO 全局过滤器
*
* @return
*/
@Component
public class TestFilter implements GlobalFilter, Ordered {
@Bean
public GlobalFilter TestFilter() {
return new TestFilter();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("============执行全局过滤器==============");
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (token == null || token.isEmpty()) {
// 验证不通过,返回错误信息
String msg = "token not is null ";
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.BAD_REQUEST);
DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
return response.writeWith(Mono.just(buffer));
}
// 验证通过,放行
return chain.filter(exchange);
}
/**
* TODO 过滤顺序指定,过滤Web处理程序会将的所有实例GlobalFilter和所有特定GatewayFilter于路由的实例添加到过滤器链中。
*/
@Override
public int getOrder() {
return -1;
}
}
网关集群,那么具体的服务怎么获取到是哪个网关转发过来的呢
2、网关传递自定义参数到转发的服务
/**
* TODO 全局过滤器
*
* @return
*/
@Component
public class TestFilter implements GlobalFilter{
@Value("${server.port}")
private String serverPort;
@Bean
public GlobalFilter TestFilter() {
return new TestFilter();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 添加当前网关消息request 在转发到具体的服务,如:ip,端口
ServerHttpRequest request = exchange.getRequest().mutate().header("serverPort", serverPort).build();
exchange.mutate().request(request).build();
// 验证通过,放行
return chain.filter(exchange);
}
}
3、自定义过滤器(暂不说明)
四、Gateway 网关集群
如需要添加黑名单,白名单功能,自行配置可获取用户真实ip,默认无法获取
1、使用Nginx/集群
可使用Nginx 或者 lvs虚拟vip 访问增加系统的高可用
2、nginx 相关配置nginx.conf
hosts 文件添加配置
127.0.0.1 a80.www.baidu.com
hosts 文件路径 : C:\windows\system32\drivers\etc
nginx.conf 配置参考
upstream mysvr {
server 127.0.0.1:8080; # 这里定义可以加 http://
server 127.0.0.1:8081;
}
server {
listen 80;
server_name a80.www.baidu.com;
location ~*^.+$ { # 请求的url过滤,正则匹配,~为区分大小写,~*为不区,区分大小写。 默认 /
proxy_pass http://mysvr; # 请求转向mysvr 定义的服务器列表
}
配置说明:拦截服务地址为 a80.www.baidu.com,端口为 80的 url ,
通过 mysvr 找到----> upstream mysvr 对应的配置
并轮询upstream mysvr 下配置的服务器
服务器的请求顺序为:ABABABABAB…
五、Gateway 动态配置
5.1、基于配置中心实现动态配置
直接添加配置信息到 配置中心即可
5.2、基于数据库 (数据层框架采用Jpa)实现动态配置
下面介绍数据库配置方式
1、添加数据表(mysql)
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for gateway_config
-- ----------------------------
DROP TABLE IF EXISTS `gateway_config`;
CREATE TABLE `gateway_config` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`route_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '路由Id',
`route_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '名称',
`route_pattern` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '规则',
`route_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '类型(1:绝对路径,0:根据服务serverId匹配)',
`route_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'url地址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
参考配置
INSERT INTO `gateway_config` VALUES (1, 'baidu', '跳转百度首页', '/baidu/**', '1', 'https://www.baidu.com');
INSERT INTO `gateway_config` VALUES (2, 'alibaba-server', '生产者测试服务', '/alibaba-server/**', '0', 'lb://alibaba-server');
2、pom.xml 添加数据源等依赖
注意这里druid 的版本为1.1.10 版本会报错
<!-- 阿里巴巴数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
<scope>runtime</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
3、yml 新添加数据源配置
spring:
### 数据库连接信息
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://12.7.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=GMT%2B8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
#最大活跃数
maxActive: 20
#初始化数量
initialSize: 1
#最大连接等待超时时间
maxWait: 60000
#打开PSCache,并且指定每个连接PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
#通过connectionProperties属性来打开mergeSql功能;慢SQL记录
#connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 1 from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
#配置监控统计拦截的filters,去掉后监控界面sql将无法统计,'wall'用于防火墙
filters: stat, wall, log4j
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
jpa:
hibernate:
ddl-auto: update # 第一次建表create 后面用update
show-sql: true
4、GateWayEntity
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Entity
@Table(name = "gateway_config")
public class GateWayEntity {
@Id
private Long id;
private String routeId;
private String routeName;
private String routePattern;
private String routeType;
private String routeUrl;
}
5、dao层
import com.example.alibabaclient.entity.GateWayEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Component;
@Component
public interface GatewayConfigDao extends JpaRepository<GateWayEntity,Long>, JpaSpecificationExecutor<GateWayEntity> {
}
6、service层
import com.example.alibabaclient.dao.GatewayConfigDao;
import com.example.alibabaclient.entity.GateWayEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class GatewayService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Autowired
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private GatewayConfigDao gatewayConfigDao;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
public void initAllRoute() {
// 从数据库查询配置的网关配置
List<GateWayEntity> gateWayEntities = gatewayConfigDao.findAll();
for (GateWayEntity gw : gateWayEntities) {
loadRoute(gw);
}
}
/**
* TODO 配置更新
*/
private String loadRoute(GateWayEntity gateWayEntity) {
RouteDefinition definition = new RouteDefinition();
Map<String, String> predicateParams = new HashMap<>(8);
PredicateDefinition predicate = new PredicateDefinition();
FilterDefinition filterDefinition = new FilterDefinition();
Map<String, String> filterParams = new HashMap<>(8);
// 如果配置路由type为0的话,则从注册中心获取服务
URI uri = null;
if (gateWayEntity.getRouteType().equals("0")) {
uri = uri = UriComponentsBuilder.fromUriString("lb://" + gateWayEntity.getRouteUrl() + "/").build().toUri();
} else {
uri = UriComponentsBuilder.fromHttpUrl(gateWayEntity.getRouteUrl()).build().toUri();
}
// 定义的路由唯一的id
definition.setId(gateWayEntity.getRouteId());
predicate.setName("Path");
//路由转发地址
predicateParams.put("pattern", gateWayEntity.getRoutePattern());
predicate.setArgs(predicateParams);
// 名称是固定的, 路径去前缀
filterDefinition.setName("StripPrefix");
filterParams.put("_genkey_0", "1");
filterDefinition.setArgs(filterParams);
definition.setPredicates(Arrays.asList(predicate));
definition.setFilters(Arrays.asList(filterDefinition));
definition.setUri(uri);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
}
6、controller 层
import com.example.alibabaclient.service.GatewayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO 路由配置更新
*/
@RestController
public class RouteController {
@Autowired
private GatewayService gatewayService;
@GetMapping("/route")
public String route(){
gatewayService.initAllRoute();
return "success";
}
}
7、测试配置
1、添加数据库数据(如第一步添加了忽视)
INSERT INTO `gateway_config` VALUES (1, 'baidu', '跳转百度首页', '/baidu/**', '1', 'https://www.baidu.com');
INSERT INTO `gateway_config` VALUES (2, 'alibaba-server', '生产者测试服务', '/alibaba-server/**', '0', 'lb://alibaba-server');
加载配置,访问接口:http://127.0.0.1/route ,访问: http://127.0.0.1/baidu 直接跳到百度首页,表示配置成功
8、测试新添加/修改
1、数据库添加一条新数据
INSERT INTO `gateway_config` VALUES (3, 'gitee', '跳转码云', '/gitee/**', '1', 'https://gitee.com/');
加载配置,访问接口:http://127.0.0.1/route ,访问: http://127.0.0.1/gitee 直接跳到码云首页,表示动态配置成功
到此就结束了,该配置直接修改、添加、删除配置信息后,调用接口 /route 后都会立即生效。