一、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 後都會立即生效。