什麼是微服務網關
微服務網關是整個微服務API請求的入口,可以實現日誌攔截、權限控制、解決跨域問題、限流、熔斷、負載均衡、黑名單與白名單攔截、授權等。
過濾器與網關的區別
過濾器用於攔截單個服務
網關攔截整個的微服務
Zuul與Gateway有哪些區別
Zuul網關屬於netfix公司開源的產品屬於第一代微服務網關
Gateway屬於SpringCloud自研發的第二代微服務網關
相比來說SpringCloudGateway性能比Zuul性能要好:
注意:Zuul基於Servlet實現的,阻塞式的Api, 不支持長連接。
SpringCloudGateway基於Spring5構建,能夠實現響應式非阻塞式的Api,支持長連接,能夠更好的整合Spring體系的產品。
SpringCloudAlibaba整合GateWay
SpringCloud gateway基於webflux實現的,不是基於SpringBoot-web,所以應該刪除Springboot-web依賴組件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
新建application.yml
server:
port: 80
spring:
application:
name: service-gateway
cloud:
gateway:
# 路由策略
routes:
# 根據我們的服務名稱查找地址實現調用
- id: member
# lb代表負載均衡簡寫,loadbalanced,後面寫具體服務名稱
uri: lb://service-member/
filters:
- StripPrefix=1
# 匹配規則
predicates:
- Path=/memberxyy/**
- id: order
uri: lb://service-order/
filters:
- StripPrefix=1
predicates:
- Path=/orderzb/**
discovery:
locator:
# 允許通過註冊中心獲取地址調用
enabled: true
nacos:
discovery:
server-addr: 127.0.0.1:8848
編寫全局過濾器
@Component
public class TokenGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 獲取參數
String token = exchange.getRequest().getQueryParams().getFirst("token");
// // 獲取header
// String appKey = exchange.getRequest().getHeaders().getFirst("token");
if (StringUtils.isEmpty(token)) {
ServerHttpResponse response = exchange.getResponse();
// response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
// String msg = "token is null ";
JSONObject message = new JSONObject();
message.put("code", 1001);
message.put("msg", "token is null.");
DataBuffer buffer = response.bufferFactory().wrap(message.toJSONString().getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
// 直接轉發到我們真實服務
return chain.filter(exchange);
}
}
此時,以memberxyy開頭轉發到會員服務,以orderzb開頭則轉發到訂單服務 ~
【網關集羣】
在host文件配置:127.0.0.1 gateway.xyy.com
啓動兩個網關服務,端口號分別爲81,82
Nginx配置
upstream backserver {
server 127.0.0.1:81;
server 127.0.0.1:82;
}
server {
listen 80;
server_name gateway.xyy.com;
location / {
proxy_pass http://backserver/;
}
}
啓動Nginx,直接訪問http://nacos.xyy.com/orderzb/orderFeignToMember,則會輪訓81和82兩個網關服務 ~
【動態網關】
任何配置都實現不用重啓網關服務器都可以及時刷新網關配置。
實現的思路:
1. 分佈式配置中心 不建議使用 閱讀性差 需要定義json格式配置 閱讀性差 (類似於SpringCloud消息總線Bus)
2. 基於數據庫表結構設計 特別建議 閱讀比較高(本文只講解基於數據庫方式,類似於Zuul網關的/actuator/refresh)
新建數據表結構,並添加一條數據如下:
引入數據庫相關的依賴:
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1<ersion>
</dependency>
<!-- mysql 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 阿里巴巴數據源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.14<ersion>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
修改application.yml配置文件如下:
server:
port: 81
spring:
application:
name: service-gateway
datasource:
url: jdbc:mysql://127.0.0.1:3306/springcloud_alibaba?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
cloud:
gateway:
discovery:
locator:
# 允許通過註冊中心獲取地址調用
enabled: true
nacos:
discovery:
server-addr: 127.0.0.1:8848
mybatis:
configuration:
map-underscore-to-camel-case: true
@Data
public class GateWayEntity {
private Long id;
private String routeId;
private String routeName;
private String routePattern;
private String routeType;
private String routeUrl;
}
@Mapper
public interface GatewayMapper {
@Select("SELECT id, route_id, route_name,route_pattern,route_type,route_url FROM gateway_table")
public List<GateWayEntity> gateWayAll();
@Update("update gateway_table set route_url=#{routeUrl} where route_id=#{routeId};")
public Integer updateGateWay(@Param("routeId") String routeId, @Param("routeUrl") String routeUrl);
}
package com.xyy.service;
import com.xyy.entity.GateWayEntity;
import com.xyy.mapper.GatewayMapper;
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
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private GatewayMapper gatewayMapper;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
public String loadAllLoadRoute() {
List<GateWayEntity> gateWayEntities = gatewayMapper.gateWayAll();
for (GateWayEntity gb : gateWayEntities) {
loadRoute(gb);
}
return "success";
}
public 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);
URI uri = null;
if ("0".equals(gateWayEntity.getRouteType())) { //0寫在前面
// 如果配置路由type爲0的話 則從註冊中心獲取服務地址
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";
}
}
@RestController
public class GatewayController {
@Autowired
private GatewayService gatewayService;
// 同步網關配置,類似於Zuul網關的/actuator/refresh
@RequestMapping("/synGatewayConfig")
public String synGatewayConfig() {
return gatewayService.loadAllLoadRoute();
}
}
此時啓動網關服務端口爲81,此時會員,訂單接口都訪問不到,需要調用接口:http://127.0.0.1:81/synGatewayConfig加載數據庫配置進入Gateway服務緩存 !
這時候訪問會員服務可以訪問到,因爲數據只配了個member,訪問訂單訪問不到:
此時,在數據庫新增一條數據如下:(注意router_url必須爲在Nacos註冊的服務名,route_id可以隨便定義,route_pattern爲攔截路徑前綴,route_type爲0表示從註冊中心獲取服務地址,否則直接http調用)
此時直接請求訂單接口也是請求不到的,需要再次刷新配置:http://127.0.0.1:81/synGatewayConfig,此時請求訂單:
小結:
動態網關類似於Zuul網關的/actuator/refresh,每次在SpringCloudConfig修改配置後,需要手動調用該接口刷新配置,這樣與消息總線相比,能很大程度上提升性能,不用一直監聽。
實際開發中,我們可以做一個頁面(管理平臺)來維護我們的服務信息,可以在Gateway服務暴露一個接口,管理平臺操作的時候僞代碼爲:①先更新數據庫,修改配置信息 ②調用網關暴露的api進行刷新內存。
【GateWay解決跨域問題】:
原理與過濾器/攔截器一樣,直接在網關中加入跨域過濾器即可:
@Component
public class CrossOriginFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
return chain.filter(exchange);
}
}