目录
1、网关作用
2、基本使用
3、配置说明
3.1、Predicate 断言
3.1.1、系统断言
3.1.2、自定义断言
3.2、Filter 过滤
3.2.1、AddRequestParameterGatewayFilterFactory:添加请求头
3.2.2、RequestRateLimiterGatewayFilterFactory:限流
3.2.3、RetryGatewayFilterFactory:重试
3.2.4、自定义过滤器
4、实现负载均衡
5、动态路由实现
5.1、内存中的路由缓存
5.2、持久化路由
1、网关作用
在微服务场景下,前端访问不同服务的时候,需要做重复的工作,如鉴权,日志等功能,为了解决统一问题,就增加微服务网关进行整合,如限流,鉴权,缓存,熔断,日志,协议转换。
2、基本使用依照之前的项目进行构建,依旧采用spring脚手架,但需要注意的是,spring cloud gateway和web-starter的jar包是冲突的,主要因为gateway采用的框架和mvc有冲突,因此需要在父pom中移除web-starter。
构建完成后,记得删除父依赖中的web-starter。
之后进行环境配置,这次采用yml的方式进行配置。
你问我为什么不用properties?
我不会(微笑)
依旧要配置服务名称,端口号和eureka地址,除此之外,需要配置gateway。
在这里有一个大致的说明,详细配置会在后面进行讲解。
这个配置的意思是,所有localhost:8087/gateway/**的访都会被转接到localhost:8083/**上,StripPrefix的作用是忽略path后的几个路由字段,这里的1,就是忽略/gateway。
spring: application: name: spring-cloud-gateway cloud: gateway: routes: - predicates: - Path=/gateway/** filters: - StripPrefix=1 uri: http://localhost:8083/ server: port: 8087 eureka: client: service-url: defaultZone: http://localhost:8081/eureka
启动完成后,尝试访问:http://localhost:8087/gateway/default,即可得到和访问http://localhost:8083/default一样的结果。
3、配置说明可以看到,上文中有几个很重要的配置
- Route:路由,包含predicate,
- Predicate:断言
- Filter:过滤器
Predicate的断言和java8的并无区别,简单来说,就是和配置好的字符进行匹配,如果符合,就会进入到Filter中进行过滤。
3.1.1、系统断言除了path,还有很多其他的断言实现,这些断言实现只要一起配置,就可以同时生效。
- Path:进行路径匹配,如- Path=/gateway/**
- Query:进行正则匹配,或者参数与参数值匹配,如- Query=/name,lily,需要http://localhost:8087/gateway/default?name=lily,增加参数name=lily之后,就可以正常访问了。
- Method:访问方式,post或get,如:-Method=Get
- Header:访问请求头限制,形式类似Query,也是key,value形式的,正则表达式同样适用
- cookie:访问cookie限制,形式类似Query,也是key,value形式的,正则表达式同样适用
通过查看gateway包内的文件,可以看到,想要自定义断言,只需要继承AbstractRoutePredicateFactory抽象类,但需要注意的是,类的名称一定是xxx+RoutePredicateFactory,xxx是任意一个名字,这将作为你的自定义断言名称,后半部分不能更改,因为系统会默认加载后面为RoutePredicateFactory的类作为断言。
AuthRoutePredicateFactory 类必须被IOC托管,所以增加@Component注解。
继承AbstractRoutePredicateFactory类,实现对应方法。
添加内部类Config作为传递参数的类,并加入到AbstractRoutePredicateFactory的泛型定义中,super实现去掉参数,将Config内部类作为指定参数。
shortcutFieldOrder方法,是作为简化配置的方式实现的,意思是将配置后的参数名称直接作为name,不需要额外声明。
apply是核心断言方法,返回值为Boolean类型,这里的方法是需要在Header中添加设置好的参数名,如果没有得到,就返回错误。
@Component public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory { public AuthRoutePredicateFactory() { super(Config.class); } private static final String NAME_KEY = "name"; @Override public ListshortcutFieldOrder() { return Arrays.asList(NAME_KEY); } @Override public Predicate apply(Config config) { String name = config.getName(); return serverWebExchange -> { HttpHeaders httpHeaders = serverWebExchange.getRequest().getHeaders(); List strings = httpHeaders.get(name); return !(strings==null) && (strings.size() > 0); }; } public static class Config{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } }
在配置中,增加Auth=Authorization
spring: application: name: spring-cloud-gateway cloud: gateway: routes: - id: route1 predicates: - Path=/gateway/** - Method=Get - Auth=Authorization filters: - StripPrefix=1 uri: http://localhost:8083/ server: port: 8087 eureka: client: service-url: defaultZone: http://localhost:8081/eureka
增加完成后,需要在Postman中进行测试
可以看到,如果没有添加对应的参数Authorization,就无法进入断言。
3.2、Filter 过滤下面列举一些常见的过滤器
3.2.1、AddRequestParameterGatewayFilterFactory:添加请求头这个来自于类AddRequestParameterGatewayFilterFactory,其中GatewayFilterFactory是固定后缀,AddRequestParameter作为name来简化配置,作用是限定header中要有一对键值对。
- id: add_request_paraemter uri: http://localhost:8083/ filter: #在Header中增加一对键值对,name和lily - AddRequestParameter=name,lily3.2.2、RequestRateLimiterGatewayFilterFactory:限流
这个来自于类RequestRateLimiterGatewayFilterFactory,其中GatewayFilterFactory是固定后缀。
限流器使用了redis作为计数器,使用令牌桶进行限流。
- burstCapacity ,令牌桶上限 。
- replenishRate ,令牌桶填充平均速率,单位:秒。
- keyResolver ,限流键解析器 Bean 对象名字 ,使用 SpEL 表达式,从 Spring 容器中获取 Bean 对象
- id: request_rate_limiter uri: http://localhost:8083/ predicates: - Path=/limiter/** filters: - StripPrefix=1 - name: RequestRateLimiter args: keyResolver: '#{@ipAddressKeyResolver}' redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 2
增加配置redis地址:
spring: application: name: spring-cloud-gateway redis: host: 127.0.0.1
增加继承KeyResolver的类,这个类的作用就是参数KeyResolver的限流地址
@Component public class IpAddressKeyResolver implements KeyResolver { @Override public Monoresolve(ServerWebExchange exchange) { return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); } }
在pom文件中添加redis依赖:
3.2.3、RetryGatewayFilterFactory:重试org.springframework.boot spring-boot-starter-data-redis-reactive
这个来自于类RetryGatewayFilterFactory,其中GatewayFilterFactory是固定后缀,其常见参数有四个:
private int retries = 3; private Listseries; private List statuses; private List methods;
- retries,重试次数,默认为3
- status,重试状态码,取值来自HttpStatus
- series,重试状态码范围,取值来自Series
- methods,重试方法类型,取值来自HttpMethod
如果不用复数形式,可以直接使用数值进行设置
- id: retry_route uri: http://localhost:8083/ predicates: - Path=/retry/** filters: - StripPrefix=1 - name: Retry args: retries: 3 statuses: SERVICE_UNAVAILABLE methods: GET series: SERVER_ERROR3.2.4、自定义过滤器
可以通过类似定义断言的方式自定义过滤器。
继承AbstractGatewayFilterFactory,实现类MyDefineGatewayFilterFactory。
打印的两个日志意味着前置拦截和后置拦截。
@Component public class MyDefineGatewayFilterFactory extends AbstractGatewayFilterFactory{ Logger logger = LoggerFactory.getLogger(MyDefineGatewayFilterFactory.class); public MyDefineGatewayFilterFactory() { super(Config.class); } private static final String NAME_KEY = "name"; @Override public List shortcutFieldOrder() { return Arrays.asList(NAME_KEY); } @Override public GatewayFilter apply(Config config) { return ((exchange, chain) -> { logger.info("pre My Filter Request, Name:" + config.getName()); return chain.filter(exchange).then(Mono.fromRunnable(() -> { String statusCode = exchange.getResponse().getStatusCode().toString(); logger.info("post statusCode is, code" + statusCode); })); }); } public static class Config { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } }
设置配置文件环境。
- id: my_define uri: http://localhost:8083/ predicates: - Path=/define/** filters: - StripPrefix=1 - MyDefine=lily
访问路径:http://localhost:8087/define/default
ReactiveLoadBalancerClientFilter不同于自定义的前置后置过滤器,它是全局过滤器,无需配置,自动生效,它实现的是GlobalFilter, Ordered,因此自定义全局过滤器时也可以这样。
@Component public class MyGlobalFilter implements GlobalFilter, Ordered { Logger logger = LoggerFactory.getLogger(MyGlobalFilter.class); @Override public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange).then(Mono.fromRunnable(() -> { logger.info("MyGlobalFilter is starting"); })); } @Override public int getOrder() { return 0; } }
无需配置,直接执行任意一个请求,即可发现打印出的日志
4、实现负载均衡想要实现负载均衡,只需要特殊的配置:
将uri的地址配置成lb(load balance)+服务名称即可,即:lb://spring-cloud-xxx
discovery配置的lower-case-service-id是服务ID是否小写,enable是否启动
配置完成后,既可达成负载均衡。
spring: application: name: spring-cloud-gateway redis: host: 127.0.0.1 cloud: gateway: routes: - id: gateway_route uri: lb://spring-cloud-user-provide predicates: - Path=/gateway/** filters: - StripPrefix=1 discovery: locator: lower-case-service-id: true enabled: true5、动态路由实现 5.1、内存中的路由缓存
gateway组件本身就提供了一些能改变路由配置的接口,但是需要添加配置:
management: endpoints: web: exposure: include: "*"
这里的*表示任何路由都可以访问,但是这样的访问很不安全,不过这里只是演示。
根据官方文档,根据访问类型不同,访问/actuator/gateway/routes达成的效果是不同的。
get方式可以得到当前全部的gateway配置:http://localhost:8087/actuator/gateway/routes
post方式可以根据routeid增加或修改路由配置:
这里是body的内容:
{ "uri":"https://www.baidu.com", "predicates":[{ "name":"Path", "args":{ "pattern":"/baidu/**" } }], "filters":[{ "name":"StripPrefix", "args":{ "_genkey_0":1 } }] }
执行完成后,还需要执行方法进行刷新,方式也是post:http://localhost:8087/actuator/gateway/refresh
这时新配置才会生效。
访问:http://localhost:8087/baidu/s
就可以发现,已经跳转到百度网页上了。
但是这样的方式是将新的配置缓存到内存上,只要重启服务,就会失效,我们需要新的持久化的配置方式。
5.2、持久化路由其实上文中提到的全部可执行url,都存在于类GatewayControllerEndpoint之中,这个类起到的作用就是controller的作用。
在它的父类之中,有修改和保存路由的方法:
@PostMapping({"/routes/{id}"}) public Mono> save(@PathVariable String id, @RequestBody RouteDefinition route) { return Mono.just(route).doonNext(this::validateRouteDefinition).flatMap((routeDefinition) -> { return this.routeDefinitionWriter.save(Mono.just(routeDefinition).map((r) -> { r.setId(id); log.debug("Saving route: " + route); return r; })).then(Mono.defer(() -> { return Mono.just(ResponseEntity.created(URI.create("/routes/" + id)).build()); })); }).switchIfEmpty(Mono.defer(() -> { return Mono.just(ResponseEntity.badRequest().build()); })); }
可以看到,这个保存路由的基本方法,就是routeDefinitionWriter.save,这是一个接口,有一个实现类,那么其实只要自定义一个实现类,就可以实现动态路由的持久化。
protected RouteDefinitionWriter routeDefinitionWriter;
使用redis作为持久化的数据存储方式,加入对应的jar包
org.springframework.boot spring-boot-starter-data-rediscom.alibaba fastjson
增加新的自定义路由访问方式。
将类托管到IOC中,自动注入RedisTemplate,重写方法,包括获取,保存和删除,都依靠redis进行。
@Component public class MyRedisRouteDefinitionRepository implements RouteDefinitionRepository { private final static String GATEWAY_KEY = "GATEWAY_ROUTE"; @Autowired RedisTemplateredisTemplate; @Override public Flux getRouteDefinitions() { List routeDefinitionList = new ArrayList<>(); redisTemplate.opsForHash().values(GATEWAY_KEY).stream().forEach(route -> { routeDefinitionList.add(JSON.parseObject(route.toString(), RouteDefinition.class)); }); return Flux.fromIterable(routeDefinitionList); } @Override public Mono save(Mono route) { return route.flatMap(routeDefinition -> { redisTemplate.opsForHash().put(GATEWAY_KEY, routeDefinition.getId(), JSON.toJSonString(routeDefinition)); return Mono.empty(); }); } @Override public Mono delete(Mono routeId) { return routeId.flatMap(id -> { if (redisTemplate.opsForHash().hasKey(GATEWAY_KEY, id)) { redisTemplate.opsForHash().delete(GATEWAY_KEY, id); return Mono.empty(); } return Mono.defer(() -> (Mono.error(new Exception()))); }); } }
RouteDefinition是用来保存路由信息的类,此处展示部分内容
@Validated public class RouteDefinition { private String id; @NotEmpty @Valid private Listpredicates = new ArrayList(); @Valid private List filters = new ArrayList(); @NotNull private URI uri; private Map metadata = new HashMap(); private int order = 0; public RouteDefinition() { } }
GatewayAutoConfiguration起到自动装配的作用。
@Bean @ConditionalOnMissingBean public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) { return new PropertiesRouteDefinitionLocator(properties); }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)