SpringCloudAlibaba - 新一代服務網關Gateway

什麼是微服務網關 

微服務網關是整個微服務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);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章