什么是微服务网关
微服务网关是整个微服务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依赖组件
org.springframework.cloud
spring-cloud-starter-gateway
2.0.0.RELEASE
新建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 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)
新建数据表结构,并添加一条数据如下:
引入数据库相关的依赖:
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.1.1
mysql
mysql-connector-java
com.alibaba
druid
1.0.14
org.projectlombok
lombok
修改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 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 gateWayEntities = gatewayMapper.gateWayAll();
for (GateWayEntity gb : gateWayEntities) {
loadRoute(gb);
}
return "success";
}
public String loadRoute(GateWayEntity gateWayEntity) {
RouteDefinition definition = new RouteDefinition();
Map predicateParams = new HashMap<>(8);
PredicateDefinition predicate = new PredicateDefinition();
FilterDefinition filterDefinition = new FilterDefinition();
Map 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 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);
}
}