微服务之所以存在,就是把一个单体服务拆分成多个服务,可以让多台机器共同协作,提高处理请求的效率,从某种程度上说,微服务可以理解成一个个Service层,所以微服务的url不应该被暴露,从网络安全和服务效率方面来看,微服务需要一个统一的入口去处理,统一调用。
就是一个网关工具,提供一种简单有效的统一的 API 路由管理方式。并提供了网关基本的功能,例如:安全,监控和限流。GateWay官方说明文档
GateWay底层使用Netty通讯框架,
工作流程的核心就是路由转发+执行过滤链
版本对应规则 https://spring.io/projects/spring-cloud
由于本人使用的boot是2.2.6的,所以使用的gateway版本为Hoxton.SR8
父级项目引入
<properties>
<spring.cloud-version>Hoxton.SR8spring.cloud-version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring.cloud-version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
子级项目引入
注意,由于gateway的依赖和spring-boot-starter-web有冲突,因此需要注意删除依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
server:
port: 9999
spring:
application:
name: cloud-gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
#开启注册中心路由功能
gateway:
discovery:
locator:
enabled: true
# 路由
routes:
# 路由id
- id: nacos-provider
# 匹配提供服务的路由地址
uri: http://localhost:9001/nacos-provider
predicates: # 断言
- Path=/demo/** # 断言,路径相匹配进行路由
@RestController
@RequestMapping("demo")
public class DemoController {
@Value(value = "${server.port}")
private String serverPort;
@GetMapping("get")
public String getServerPort(){
return "Hello Nacos Discovery" + serverPort;
}
}
启动后可以通过gateway服务访问到真正的服务提供者。
Gateway除了可以使用yml配置文件来配置路由,也可以通过代码方式来进行配置,方法如下:
配置类
package com.cloud.cloudgateway9999.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
// 新建了一个路由规则构建对象,这个对象可以构建多个路由
RouteLocatorBuilder.Builder routes = builder.routes();
// ,就会
/**
* 如果满足路径http://localhost:9999/demo/customer断言条件,那么
* 请求就会被转发至http://localhost/9001/nacos-provider/demo/customer
*/
routes.route("path-custumer",r->r.path("/demo/customer")
.uri("http://localhost:9001/nacos-provider")).build();
// 返回路由规则
return routes.build();
}
}
访问时会有相同的效果,但是这样配置比较麻烦,建议优先使用yml文件配置。
添加配置:
# 开启自动路由功能,根据服务名称自动创建routes
gateway:
discovery:
locator:
enabled: true
关于这个配置:默认为关闭状态,如果这个配置为开启状态,则说明:gateway与服务可以通过serviceId完成默认转发,如果多个服务集群的话,可以自定进行负载均衡
对比nacos自带的负载均衡Dubbo,在启动类配置
@Bean
// @Bean注解表示将restTemplate装配到容器中来
@LoadBalanced
// 负载均衡,表示这个restTemplate在调用服务提供者接口时经过负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
就可以实现,而nacos可以直接通过discovery.locator.enabled开关来做到。
discovery.locator.enabled开关可以根据serviceId自动转发,但是会导致服务名称暴露,因此,实际应用中不会打开该开关。
可以在yml配置负载均衡的规则:
# 路由
routes:
# 路由id
- id: routeNo1
# 负载均衡:如果符合断言,则按照服务名为nacos-provider的服务进行负载均衡访问,并且,例如,访问gateway的请求地址为:http://9999/demo/get,会被路由成http://localhost:9001/demo/get和http:/9002/demo/get
uri: lb://nacos-provider
predicates:
- Path=/demo/** # 断言,路径相匹配进行路由
断言是指一种匹配规则,可以对路径、请求头、请求发送的时间等属性进行匹配,断言通过以后,才能够被转发到其他微服务。GateWay断言官方说明文档
routes:
# 路由id
- id: routeNo1
uri: lb://nacos-provider
predicates:
routes:
# 路由id
- id: routeNo1
# 匹配提供服务的路由地址
- Path=/demo/**
# 必须在该时间之后发起的请求才能被处理
- After=2022-07-18T19:18:44.635+08:00[Asia/Shanghai]
# 匹配Cookie的key和value(正则表达式),cookie中一定要有一key为username的键值对,并且值必须匹配正则表达式,此处必须为字母,不能为数字
- Cookie=username,[a-z]+
# 匹配请求头,请求头必须含有X-Request-Id,并且必须是数字
- Header=X-Request-Id,\d+
# 匹配当前的主机地址发出的请求host
- Host=**.cloud.com
# 可以设置一个或多个参数,匹配HTTP请求,比如GET、POST
- Method=GET,POST
# 匹配请求参数,这里如果需要匹配多个参数,可以写多个Query
- Query=id,.+
时间类的标签必须是ZonedDateTime类型,可以参考以下代码获取具体写法:
public static void main(String[] args){
ZonedDateTime time = ZonedDateTime.now();
System.out.println(time);
}
weight规则如下:
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
该路由会将约 80% 的流量转发到weighthigh.org,将约 20% 的流量到weightlow.org。
GateWay内置的Filter有两种生命周期,一种是pre,作用于业务逻辑之前,一种是post,作用于业务逻辑之后;其中,Gateway自带的Filter有两种,一种是GateWayFilter,单一过滤器,有32种,一种是GlobalFilter,全局过滤器,由9种,详见GateWay过滤器官网
如果在9001的配置文件中配置了context-path,那么,按照原来的地址就访问不到服务了,而是需要在原地址上拼接一部分内容。
server:
port:
9001
servlet:
context-path: /nacos-provider
例如,原来访问http://localhost:9999/demo/get就会转发成http://9001/demo/get,但是现在,访问的地址变为http://localhost://9001/nacos-provider/demo/get,如此这样的话就访问不到资源了。
StripPrefix过滤器的作用就是给访问路径去除一部分
# 路由
routes:
# 路由id
- id: routeNo1
# 匹配提供服务的路由地址
uri: lb://nacos-provider
predicates:
- Path=/demo/** # 路径相匹配进行路由
# 必须在该时间之后发起的请求才能被处理
- After=2022-07-18T19:18:44.635+08:00[Asia/Shanghai]
filters:
# 去掉uri的第一部分
- StripPrefix=1
访问路径为:http://localhost:9999/demo/nacos-provider/demo/get
转换为:http://localhost:9001/nacos-provider/demo/get
就能顺利访问到资源了
package com.cloud.cloudgateway9999.filter;
import com.alibaba.csp.sentinel.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class MyFilter implements Ordered,GlobalFilter {
/**
*
* @param exchange 可以拿到一个方法的请求和响应
* @param chain 过滤器链
* @return 是否放行
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求里请求参数,第一个username的值
String username = exchange.getRequest().getQueryParams().getFirst("username");
log.info("---------进入GateWay过滤器-------------");
if(StringUtil.isEmpty(username)){
log.info("---------非法用户名,请求被拒绝-------------");
//返回状态码为406,不可接受的请求
exchange.getResponse().setRawStatusCode(HttpStatus.SC_NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 加载过滤器的顺序
* @return 返回一个整数,数字越小优先级越高
*/
@Override
public int getOrder() {
return 0;
}
}