- 什么是网关
网关是整个微服务API请求的入口,负责拦截所有请求,分发到服务上去。可以实现日志拦截、权限控制、解决跨域问题、限流、熔断、负载均衡、黑名单与白名单拦截、授权等
- 过滤器与网关的区别
网关是拦截所有服务器请求进行控制
过滤器拦截某单个服务器请求进行控制 - Zuul与Gateway有那些区别
Zuul网关属于netfix公司开源的产品属于第一代微服务网关,是基于Servlet实现的,阻塞式的Api, 不支持长连接。
Gateway属于SpringCloud研发的第二代微服务网关,是基于Spring5构建,能够实现响应式非阻塞式的Api,支持长连接。
相比来说SpringCloudGateway性能比Zuul性能要好,Zuul是基于Servlet实现, gateway基于netty实现 - 网关与nginx区别
相同点:都是可以实现对api接口的拦截,负载均衡、反向代理、请求过滤等,可以实现和网关一样的效果。
不同点:Nginx采用C语言编写,Gateway属于Java语言编写的, 能够更好让我们使用java语言来实现对请求的处理。
Nginx 属于服务器端负载均衡器。
Gateway 属于本地负载均衡器。
- Gateway执行流程
- 流程
a. 客户端发送请求,会到达网关的DispatcherHandler处理,匹配到RoutePredicateHandlerMapping。
b. 根据RoutePredicateHandlerMapping匹配到具体的路由策略。
c. FilteringWebHandler获取的路由的GatewayFilter数组,创建 GatewayFilterChain 处理过滤请求
d. 执行我们的代理业务逻辑访问。 - Gateway常见路由策略
Spring Cloud Gateway
- 项目集成gateway
- 创建会员项目
项目创建参考: SpringCloudAlibaba基础学习--nacos服务注册与发现_xiaobo5264063的博客-CSDN博客
将会员服务注册到nacos, 服务名称为: pitch-member
提供会员服务集群地址: http://127.0.0.1:9001/obtain
http://127.0.0.1:9002/obtain
- 创建网关项目
在pom.xml引入依赖
SpringCloud gateway基于webflux实现的,不是基于SpringBoot-web,所以应该删除spring-boot-starter-web依赖<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>2.0.0.RELEASE</version> </dependency> <!-- nacos整合服务注册与发现--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.2.RELEASE</version> </dependency> </dependencies>
application.yml配置
常见路由策略: Spring Cloud Gatewayserver: port: 80 ####服务网关名称 spring: application: name: pitch-gateway cloud: gateway: discovery: locator: ####允许从注册中心获取地址 enabled: true routes: ###路由id 自定义唯一 - id: pitch ####pitch-member为会员服务的名称 uri: lb://pitch-member/ filters: - StripPrefix=1 ###路由匹配规则 表示当客户端访问http://ip:80/member/**地址时, gateway就会转发到pitch-member会员服务的具体地址 predicates: - Path=/member/** nacos: discovery: ###将网关服务注册到nacos server-addr: 127.0.0.1:8848 enabled: true
- 网关服务启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AppGateway { public static void main( String[] args ) { SpringApplication.run(AppGateway.class); } }
- 测试
网关地址 http://127.0.0.1/member/obtain
如图所示:当我们访问网关地址时,gateway会转发到具体的服务地址,并默认实现负载均衡。 - 实现自定义参数拦截
import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @author xiaobo * @Description MyFilter * @createTime 2020-03-30 20:20 */ @Component public class MyFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取token String token = exchange.getRequest().getQueryParams().getFirst("token"); // 判断的token是否为空 if (StringUtils.isEmpty(token)) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); String msg = "token not is null "; DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes()); return response.writeWith(Mono.just(buffer)); } // 直接转发到我们真实服务 return chain.filter(exchange); } }
- 测试
http://127.0.0.1/member/obtain?token=123 此时转发真实地址
http://127.0.0.1/member/obtain 提示token is null
- 使用全局过滤器解决跨域问题
import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @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); } }
- 基于数据库形式实现动态网关配置
- 基于上面个的项目
注释掉application.yml中gateway转发的配置server: port: 80 ####服务网关名称 spring: application: name: pitch-gateway cloud: gateway: discovery: locator: ####允许从注册中心获取地址 enabled: true nacos: discovery: ###将网关服务注册到nacos server-addr: 127.0.0.1:8848 enabled: true
- 转发服务
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.Map; import java.util.HashMap; /** * @author xiaobo * @Description GatewayService * @createTime 2020-03-30 21:27 */ @Service public class GatewayService implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; @Autowired private RouteDefinitionWriter routeDefinitionWriter; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } // 路由id 转发的注册中的服务 客户端的访问地址 // 表示当客户端访问gateway的url地址时,gateway就是转发到serverName的真实服务中 public String updateRoute(String id, String serverName, String url) { 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); // fromUriString表示以注册中心服务名作为地址 URI uri = UriComponentsBuilder.fromUriString("lb://" + serverName + "/").build().toUri(); // fromHttpUrl表示以http形式转发地址 // URI uri = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com").build().toUri(); // 定义的路由唯一的id definition.setId(id); predicate.setName("Path"); //路由转发地址 predicateParams.put("pattern", url); 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"; } }
- 提供刷新接口
这里需要改成从数据库获取import com.pitch.service.GatewayService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author xiaobo * @Description UpdateGatewayController * @createTime 2020-03-30 21:57 */ @RestController public class UpdateGatewayController { @Autowired private GatewayService gatewayService; @GetMapping("/update") public String update(){ // 路由id String id = "myId"; // 会员服务 String serverName = "pitch-member"; // 转发地址 String url = "/member/**"; // 这里可以改为从数据库获取 gatewayService.updateRoute(id, serverName, url); return "success"; } }
- 启动测试
访问: http://127.0.0.1/member/obtain?token=123 此时应该404
访问: http://127.0.0.1/update gateway加载转发地址
http://127.0.0.1/member/obtain?token=123
- 基于keepalived+nginx实现gataway高可用
- 原理