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);
    }
}
发布了45 篇原创文章 · 获赞 20 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/AkiraNicky/article/details/104080588