若是使用Zuul做为网关,Spring Cloud Zuul 访问后端服务时,服务发现的默认路由是:http://zuul_host:zuul_post/微服务在Eureka上的/servicesId/**
,Spring Cloud Gateway 在不一样的注册中心下的基于服务发现的路由规则以下所示:java
注册中心 | 描述 |
---|---|
Eureka | 经过网关转发服务调用,访问网关URL是http://Gateway_HOST:Gateway_PORT/大些ServiceId/*,服务名默认必须大写,不然会抛404错误 ,若是服务名要用小写,可在属性配置文件中添加spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true 配置解决 |
Zookeeper | 经过网关转发服务调用,服务名默认小写,无需特殊处理 |
Consul | 经过网关转发服务调用,服务名默认小写,无需特殊处理 |
经过Eureka做为注册中心实现Gateway服务发现路由规则。react
包 | 工程 | 端口 | 描述 |
---|---|---|---|
cloud-gty-eureka-case | 父包 | ||
case-consumer | 8000 | 服务消费者 | |
case-eureka | 8761 | Eureka注册中心 | |
case-gateway | 9000 | 基于Spring Cloud Gateway的网关server | |
case-provider | 8001 | 服务提供者 |
访问地址:localhost:9000/case-consumer/hello/frank
浏览器返回:git
Hello ' frank Thu Jan 24 16:35:58 CST 2019
application.yml
)..... locator: # 是否与服务发现组件进行结合,经过serviceId转发到具体的服务实例。默认为false,为true表明开启基于服务发现的路由规则。 enabled: true # 配置以后访问时无需大写 lower-case-service-id: true ......
程序启动时将服务都注册到EurekaServer中,Gateway做为入口,访问consumer,consumer经过feign向eureka-server发现对应的provider
github
Spring Cloud Gateway Filter从接口实现上分为两种:Gateway Filter
和Global Filter
。web
Gateway Filter
Gateway Filter经过复制Web Filter实现,是一个Filter过滤器,能够对访问的URL过滤,进行横切处理(切面处理),应用场景:超时、安全等。redis
Global Filter
Spring Cloud Gateway 定义了Global Filter接口,用户能够自定义实现本身的Global Filter。Global Filter是一个全局的Filter,做用于全部路由。算法
应用场景:spring
全局系通通计,例如请求服务、请求时长、请求过滤等
应用在有针对性服务(例如用户服务、订单服务、商品服务),单业务自定义过滤时
public class CustomGatewayFilter implements GatewayFilter, Ordered { private static final Log log = LogFactory.getLog(GatewayFilter.class); private static final String COUNT_Start_TIME = "countStartTime"; /** * 对路由转发的耗时进行统计 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { exchange.getAttributes().put(COUNT_Start_TIME, System.currentTimeMillis()); return chain.filter(exchange).then( Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(COUNT_Start_TIME); Long endTime=(System.currentTimeMillis() - startTime); if (startTime != null) { log.info(exchange.getRequest().getURI().getRawPath() + ": " + endTime + "ms"); } }) ); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }
Gateway Filter配置到路由断言
)@Bean public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path("/test") .filters(f -> f.filter(new CustomGatewayFilter())) .uri("http://localhost:8001/customFilter?name=xujin") .order(0) .id("custom_filter") ) .build(); }
2019-01-25 16:01:17.274 INFO 32023 --- [ctor-http-nio-5] o.s.cloud.gateway.filter.GatewayFilter : /test: 5633ms
定义一个全局过滤器
,对请求到网关的URL进行权限校验,对请求到网关的URL进行权限校验,判断请求的URL是不是合法请求。例子 AuthSignatureFilter.java 中全局过滤器处理逻辑是经过从Gateway上下文ServerWebExchange对象中获取authToken对应的值进行判Null处理,实际投产时要按实际生产环境逻辑编写。
express
@Component public class AuthSignatureFilter implements GlobalFilter, Ordered { /** * 拦截请求,获取authToken,并校验 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("authToken"); if (null==token || token.isEmpty()) { //当请求不携带Token或者token为空时,直接设置请求状态码为401,返回 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return -400; } }
包 | 工程 | 端口 | 描述 |
---|---|---|---|
cloud-gty-gateway-global-filter | 父包 | ||
filter | 9000 | 过滤器程序,代码在package com.sc.gty.gg.filter 下 |
|
filter-provider | 8001 | 路由断言成功匹配后跳转的项目 |
WeightRoutePredicateFactory 是一个路由断言工厂,下面经过使用WeightRoutePredicateFactory对URL进行权重路由。
后端
权重最多见的状况就以下图所示,在作金丝雀灰度测试时,旧系统接收95%的流量,新系统接收5%的流量,须要经过网关动态实时推送路由权重信息。
包 | 工程 | 端口 | 描述 |
---|---|---|---|
cloud-gty-wcmp | 父包 | 权重多路径路由 | |
gateway | 8080 | 配置权重路由的应用程序 | |
provider | 8081 | 路由到的程序,为了演示只有一个,能够设置多端口或多应用体现效果 |
@SpringBootApplication public class WcmpGatewayApplication { public static void main(String[] args) { SpringApplication.run(WcmpGatewayApplication.class, args); } }
server: port: 8080 spring: application: name: gty_wcmp cloud: gateway: routes: - id: service1_v1 # 这里能够配置为其它要路由的地址 uri: http://localhost:8081/v1 predicates: - Path=/test # 设置权重 为95% - Weight=service1, 95 - id: service1_v2 # 这里能够配置为其它要路由的地址 uri: http://localhost:8081/v2 predicates: - Path=/test # 设置权重 为5% - Weight=service1, 5 logging: level: org.springframework.cloud.gateway: TRACE org.springframework.http.server.reactive: DEBUG org.springframework.web.reactive: DEBUG reactor.ipc.netty: DEBUG
@RestController public class ServiceController { @GetMapping(value = "/v1") public String v1() { return "v1"; } @GetMapping(value = "/v2") public String v2() { return "v2"; } }
Spring Cloud Gateway还能够将https证书配置在网关中
,之前咱们都是配置在Nginx中,若是咱们要用网关来做为全部API和程序的入口的话,即可使用这种技术来实现此目的
。
包 | 工程 | 端口 | 描述 |
---|---|---|---|
cloud-gty-https | 父包 | gateway https协议实例 | |
cloud-gty-eureka | 8761 | 注册中心 | |
cloud-gty-gateway-https | 8080 | 含https证书的网关,须要用https访问。 |
|
cloud-gty-controller-sourece | 8071 | 目标客户端程序 | |
cloud-gty-controller-ectype | 8072 | source的副本,用于负载均衡 |
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <!-- spring cloud gateway的核心依赖starter--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>2.0.0.RELEASE</version> </dependency> <!--Eureka Client Starter用于将gateway注册到Eureka上--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.3.17.RELEASE</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
spring: application: name: gateway-https cloud: gateway: discovery: locator: lowerCaseServiceId: true enabled: true server: ssl: # 在生成证书时键入的别名 key-alias: certificatekey enabled: true # 证书密码,我设置的相同的 key-password: 12979613 # 证书,与application.yml统计目录下 key-store: classpath:shfqkeystore.jks key-store-type: JKS key-store-provider: SUN # 证书存储密码,我设置的相同的 key-store-password: 12979613 port: 8080 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ logging: level: org.springframework.cloud.gateway: TRACE org.springframework.http.server.reactive: DEBUG org.springframework.web.reactive: DEBUG reactor.ipc.netty: DEBUG
【请注意阅读yml的注释】
常规Eureka-Server,将Gateway与Controller注册到Eureka,这样经过Gateway端口便可访问到Controller,详细请查看代码。
这两个controller是除端口不一样之外,其它都相同,都注册到Eureka-Server当中。
路由到的Controller并非https,https转发调用http了,因此会出现此问题
,详细代码我就不粘贴来,能够把源码中的两种解决方案Filter,Gateway Filter删除,再访问便可体现
。
URI uri = exchange.getRequest().getURI(); String overrideScheme = null; if (schemePrefix != null) { //这里获取访问策略 overrideScheme = url.getScheme(); } URI requestUrl = this.loadBalancer.reconstructURI(new LoadBalancerClientFilter.DelegatingServiceInstance(instance, overrideScheme), uri); //直接使用策略 log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
/** * 在LoadBalancerClientFilter执行以后将Https修改成http */ @Component public class HttpSchemeFilter implements GlobalFilter, Ordered { private static final int HTTPS_TO_HTTP_FILTER_ORDER = 10101; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { Object uriObj = exchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR); if (uriObj != null) { URI uri = (URI) uriObj; uri = this.upgradeConnection(uri, "http"); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); } return chain.filter(exchange); } private URI upgradeConnection(URI uri, String scheme) { UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri).scheme(scheme); if (uri.getRawQuery() != null) { // When building the URI, UriComponentsBuilder verify the allowed characters and does not // support the '+' so we replace it for its equivalent '%20'. // See issue https://jira.spring.io/browse/SPR-10172 uriComponentsBuilder.replaceQuery(uri.getRawQuery().replace("+", "%20")); } return uriComponentsBuilder.build(true).toUri(); } /** * 因为LoadBalancerClientFilter的order是10100, * 因此设置HttpSchemeFilter的的order是10101, * 在LoadBalancerClientFilter以后将https修改成http * @return */ @Override public int getOrder() { return HTTPS_TO_HTTP_FILTER_ORDER; } }
/** * 在LoadBalancerClientFilter执行以前将Https修改成Http * https://github.com/spring-cloud/spring-cloud-gateway/issues/378 */ @Component public class HttpsToHttpFilter implements GlobalFilter, Ordered { private static final int HTTPS_TO_HTTP_FILTER_ORDER = 10099; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI originalUri = exchange.getRequest().getURI(); ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest.Builder mutate = request.mutate(); String forwardedUri = request.getURI().toString(); if (forwardedUri != null && forwardedUri.startsWith("https")) { try { URI mutatedUri = new URI("http", originalUri.getUserInfo(), originalUri.getHost(), originalUri.getPort(), originalUri.getPath(), originalUri.getQuery(), originalUri.getFragment()); mutate.uri(mutatedUri); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } } ServerHttpRequest build = mutate.build(); return chain.filter(exchange.mutate().request(build).build()); } /** * 因为LoadBalancerClientFilter的order是10100, * 要在LoadBalancerClientFilter执行以前将Https修改成Http,须要设置 * order为10099 * @return */ @Override public int getOrder() { return HTTPS_TO_HTTP_FILTER_ORDER; } }
Swagger可视化API测试工具。
我用paw和postman,因此这里就先空着,将来补全。
名称 | 描述 |
---|---|
缓存 | 提高系统访问速度和增大系统处理容量,解决高并发流量所带来的缺陷。 |
降级 | 当服务出现问题或者影响到核心流程时,须要暂时将其屏蔽掉,待高峰过去以后或者问题解决后再打。 |
限流 | 应用在不便于使用缓存和降级技术时,如秒杀 ,抢购 ,评论 ,下单 ,频繁复杂查询 ,等稀缺资源时 |
兜底数据
或默认数据
)常见的限流场景如天猫双11,双12高并发场景
Spring Cloud Gateway实现限流只需实现一个过滤器Filter便可。
【Google Guava】中的RateLimiter、Bucket4j、RateLimitJ都是基于令牌桶算法实现的限流工具。
包 | 包 | 工程 | 端口 | 描述 |
---|---|---|---|---|
cloud-gty-limiting | gateway 限流实例 | |||
cloud-gty-custom-filter-limiting | 自定义过滤器实现限流(令牌桶) | |||
custom-filter | 8080 | 自定义过滤器实现限流实现 |
||
cloud-gty-redis-lua-limiting | 经过内置过滤器实现限流(令牌桶) | |||
redis-lua-request-rate | 8081 | 内置过滤器(redis + lua),经过yml配置 |
||
cloud-gty-cpu-limiting | 判断CPU使用状况,限流 | |||
cpu-limiting | 8082 | 经过spring-boot-starter-actuator获取CPU信息,并设置CPU最大阀值,作到限流 |
||
limiting-provider | 8000 | 用于过滤路由断言转发节点(公用provider ) |
cloud-gty-custom-filter-limiting
<dependencies> <!-- Spring Cloud Gateway的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- Bucket4j限流依赖--> <dependency> <groupId>com.github.vladimir-bukhtoyarov</groupId> <artifactId>bucket4j-core</artifactId> <version>4.0.0</version> </dependency> </dependencies>
...... /** * 经过流式API配置路由规则,当访问/test/rateLimit时,自动转发到http://localhost:8000/hello/rateLimit * @param builder * @return */ @Bean public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path("/test/rateLimit") .filters(f -> f.filter(new GatewayRateLimitFilterByIp(10,1, Duration.ofSeconds(1)))) .uri("http://localhost:8000/hello/rateLimit") .id("rateLimit_route") ).build(); } ......
核心过滤器,实现接口GatewayFilter, Ordered,经过过滤器的方式实现令牌桶限流
public class GatewayRateLimitFilterByIp implements GatewayFilter, Ordered { private final Logger log = LoggerFactory.getLogger(GatewayRateLimitFilterByIp.class); /** * 单机网关限流用一个ConcurrentHashMap来存储 bucket, * 若是是分布式集群限流的话,能够采用 Redis等分布式解决方案 */ private static final Map<String, Bucket> LOCAL_CACHE = new ConcurrentHashMap<>(); /** * 桶的最大容量,即能装载 Token 的最大数量 */ int capacity; /** * 每次 Token 补充量 */ int refillTokens; /** *补充 Token 的时间间隔 */ Duration refillDuration; public GatewayRateLimitFilterByIp() { } public GatewayRateLimitFilterByIp(int capacity, int refillTokens, Duration refillDuration) { this.capacity = capacity; this.refillTokens = refillTokens; this.refillDuration = refillDuration; } private Bucket createNewBucket() { Refill refill = Refill.of(refillTokens, refillDuration); Bandwidth limit = Bandwidth.classic(capacity, refill); return Bucket4j.builder().addLimit(limit).build(); } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); Bucket bucket = LOCAL_CACHE.computeIfAbsent(ip, k -> createNewBucket()); System.out.println("IP:{} ,令牌桶可用的Token数量:{} " + "ip -" +ip + "tokens - " + bucket.getAvailableTokens()); if (bucket.tryConsume(1)) { return chain.filter(exchange); } else { //当可用的令牌书为0是,进行限流返回429状态码 exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); return exchange.getResponse().setComplete(); } } @Override public int getOrder() { return -1000; } public static Map<String, Bucket> getLocalCache() { return LOCAL_CACHE; } public int getCapacity() { return capacity; } public void setCapacity(int capacity) { this.capacity = capacity; } public int getRefillTokens() { return refillTokens; } public void setRefillTokens(int refillTokens) { this.refillTokens = refillTokens; } public Duration getRefillDuration() { return refillDuration; } public void setRefillDuration(Duration refillDuration) { this.refillDuration = refillDuration; } }
访问
limiting-provider,custom-filter
,访问地址 http://localhost:8080/test/rateLimit,我经过jmeter压测1秒并发的方式访问的
基于RequestRateLimiterGatewayFilterFactory
作到限流,要结合Redis,运用Lua脚本。
当项目实际投产时,利用网关限流要考虑多方面状况,例如经过CPU使用率来进行限流,Spring Boot Actuator
提供的
Metrics
获取当前CPU的使用状况
,当CPU使用率高于某个阀值就开启限流,反之不开启。
spring: application: name: case-gateway-server cloud: gateway: # 设置路由:全部的请求都会路由到http://127.0.0.1:8081/** routes: - id: cloud-client-app uri: http://127.0.0.1:8081 order: 0 predicates: - Path=/**
全部的请求都会路由到8081端口到API应用上
概念 | 描述 |
---|---|
路由配置 | 配置某请求路径路由到指定的目的地址。 |
路由规则 | 匹配到路由配置以后,再根据路由规则进行转发处理。 |