【springcloud】快速搭建一套分布式服务springcloudalibaba(二)
第二篇 基于nacos搭建分布式项目 网关
项目所需 maven + nacos + java8 + idea + git + mysql(用户信息) + redis(用于限流)
本文通过网关实现用户登录拦截,网关系统/用户系统/商品系统 用户未带token请求除登录以外的任何操作都被拦截返回登录页面。
请先准备好环境,可以直接clone下来项目去部署。
基于nacos搭建分布式项目 网关
- 分布式系统为什么需要网关
- 项目结构
- 创建项目
- 路由参数解析
- 其实主要用到的就那几个就可以实现。(示例)
- spring.cloud.gateway.routes.filters
- 过滤器示例(熔断器与限流)
- 用户认证与日志监控
- 示例代码
- 熔断与限流
- 熔断
- 限流
- 自定义限流key
- 测试
- 灰度发布与流量控制
- 基于路径的流量分发
- 基于 Header 的流量分发
- 基于权重的流量分发
- 结尾
分布式系统为什么需要网关
在分布式系统中,网关(Gateway) 是一个非常重要的组件,它充当了系统的统一入口,负责处理外部请求并将其路由到内部服务。
- 统一入口(客户端只需与网关交互,而不需要直接访问内部服务)
- 路由与负载均衡(请求 /user/** 路由到用户服务,请求 /order/** 路由到订单服务)
- 安全与认证(网关可以集中处理身份验证(如 JWT、OAuth2)和授权(如权限校验)
- 限流与熔断(支持熔断和限流机制,当某个服务不可用或者超出瞬时流量限制时,快速失败并返回降级响应。)
- 日志与监控 (网关可以集中记录请求日志,便于问题排查和性能分析)
- 跨域支持(网关可以统一处理跨域请求(CORS),避免每个服务单独配置)
- 灰度发布与流量控制(网关可以根据请求的 Header、参数或其他条件,将流量分发到不同版本的服务。)
接下里我们一个一个实现看看
图内各个服务节点可以集群部署。
项目结构
快速部署分布式项目
git clone git@gitee.com:goodluckv/ali-cloud-common.git
git clone git@gitee.com:goodluckv/ali-cloud-gateway.git
git clone git@gitee.com:goodluckv/ali-cloud-user.git
git clone git@gitee.com:goodluckv/ali-cloud-goods.git
创建项目
快速创建,可参考第一篇快速搭建,加入spring-cloud-starter-gateway。gateway网关项目不需要spring-boot-starter-web包。Spring MVC 是基于阻塞式 I/O 的传统 Web 框架,通常用于构建同步的 Web 应用程序。
Spring Cloud Gateway 是基于 Spring WebFlux 的响应式编程框架,使用非阻塞式 I/O,适用于构建异步、高性能的网关服务。
nacos 中的config
命名gateway-service-dev.yaml
user:
name: 自动刷新
age: 30
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1
# 熔断
- name: CircuitBreaker
args:
name: systemBreaker
fallbackUri: forward:/system/fallback/tip
# 限流
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 每秒允许的请求数
redis-rate-limiter.burstCapacity: 5 # 每秒最大请求数
key-resolver: "#{@pathKeyResolver}" # 限流的键解析器
- id: login-service
uri: lb://user-service
predicates:
- Path=/account/**
路由参数解析
路由可以直接配置在nacos的动态配置中
*[HTML]:
配置分类 | 配置项 | 用途说明 | 示例值/配置 |
---|---|---|---|
路由配置 | spring.cloud.gateway.routes.id | 路由唯一标识符,用于区分不同路由规则 | user-service |
spring.cloud.gateway.routes.uri | 目标服务URI(支持http 、lb 负载均衡模式) | lb://user-service 或 http://localhost:8080 | |
spring.cloud.gateway.routes.predicates | 断言条件集合,用于匹配HTTP请求特征(如路径、请求头等) | - Path=/api/** - Method=GET | |
spring.cloud.gateway.routes.filters | 过滤器集合,用于修改请求/响应(如添加头、路径重写等) | - AddRequestHeader=X-Request-Id,123 - StripPrefix=2 (去除前缀) | |
全局配置 | spring.cloud.gateway.discovery.locator.enabled | 是否与服务发现组件集成(如Eureka),启用动态路由 | true |
spring.cloud.gateway.default-filters | 全局过滤器列表,作用于所有路由 | - DedupeResponseHeader=Access-Control-Allow-Origin | |
跨域配置 | spring.cloud.gateway.globalcors.cors-configurations | 配置跨域资源共享(CORS)规则 | '/**': allowedOrigins: "*" allowedMethods: ["GET", "POST"] |
HTTP客户端配置 | spring.cloud.gateway.httpclient.pool.max-connections | 设置HTTP连接池最大连接数(优化高并发场景) | 500 |
spring.cloud.gateway.httpclient.connect-timeout | 连接超时时间(毫秒) | 30000 | |
限流配置 | spring.cloud.gateway.ratelimiter.enabled | 是否启用请求速率限制(需配合Redis等存储) | true |
spring.cloud.gateway.ratelimiter.redis-rate-limiter.replenishRate | 每秒允许的请求数 | 10 | |
指标监控 | spring.cloud.gateway.metrics.enabled | 启用Prometheus等监控指标采集 | true |
超时配置 | spring.cloud.gateway.httpclient.response-timeout | 响应超时时间(毫秒) | 60000 |
其实主要用到的就那几个就可以实现。(示例)
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1
spring.cloud.gateway.routes.filters
关于过滤器 又有很多 参数 ,比如熔断与限流都可以在这里面配置
*[HTML]:
过滤器名称 | 用途说明 | 示例配置 | 来源 |
---|---|---|---|
AddRequestHeader | 添加请求头,常用于传递认证信息或自定义标识 | - AddRequestHeader=X-User-Id, 1001 | |
AddRequestParameter | 添加请求参数,常用于补充请求参数 | - AddRequestParameter=key1, value1 | |
PrefixPath | 在请求路径前添加前缀,用于统一接口前缀 | - PrefixPath=/api/v1 | |
StripPrefix | 去除路径前缀,将请求转发到后端时忽略指定层级的路径 | - StripPrefix=2 (移除路径前两个层级) | |
RewritePath | 重写请求路径,支持正则表达式匹配和替换 | - RewritePath=/old/(?<segment>.*), /new/$\{segment} | |
Retry | 配置请求重试策略(如超时重试次数、状态码触发条件) | - Retry=3 (默认重试3次)- Retry=retries=3,statuses=500,methods=GET | |
RequestRateLimiter | 基于令牌桶算法限流,需配合 Redis 或其他存储实现 | - RequestRateLimiter=10, 20, #{@userKeyResolver} (每秒10令牌,容量20) | |
SetStatus | 强制修改响应状态码,常用于统一异常处理 | - SetStatus=401 | |
RemoveRequestHeader | 移除指定请求头,用于敏感信息过滤 | - RemoveRequestHeader=Authorization | |
RemoveResponseHeader | 移除指定响应头,用于隐藏后端服务细节 | - RemoveResponseHeader=Server | |
SaveSession | 强制保存 WebSession,用于需要会话管理的场景 | - SaveSession | |
RedirectTo | 重定向请求,支持 HTTP 状态码和 URL 配置 | - RedirectTo=302, http://example.com | |
过滤器示例(熔断器与限流)
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
# 请求头为/user开头的uri都路由到userservice服务
- Path=/user/**
filters:
# StripPrefix 转发的路径第一个/user会被去掉 比如 请求/user/info 实际请求 http://user-service/info 这么配置是为了更好区分各个服务,
- StripPrefix=1
# 熔断
- name: CircuitBreaker
args:
name: systemBreaker
fallbackUri: forward:/system/fallback/tip
# 限流
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 每秒允许的请求数
redis-rate-limiter.burstCapacity: 5 # 每秒最大请求数
key-resolver: "#{@pathKeyResolver}" # 限流的键解析器
用户认证与日志监控
示例代码中 加入了jwt,我们只需要实现GlobalFilter, Ordered 这俩接口 ,然后重写filter 方法即可实现过滤。可以配置多个过滤器。
示例代码
order 数字越小执行优先级越高(可以写负数)。
@Configuration
@Slf4j
public class GatewayJwtTokenFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String rawPath = request.getURI().getRawPath();
HttpHeaders headers = request.getHeaders();
log.info("rawPath: {}", rawPath);
if (!rawPath.startsWith("/account")) {
// 检查用户是否登录,这里假设通过检查请求头中的某个字段来判断
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
Res<UserAccount> res = JwtUtil.getUser(token, UserAccountSecretInit.ACCOUNT_SECRETKEY);
if (!res.getCode().equals(Res.SUCCESS_CODE)) {
// 未登录,重定向到 /login 页面
exchange.getResponse().setStatusCode(HttpStatus.FOUND);
exchange.getResponse().getHeaders().set("Location", "/account/login.html");
return exchange.getResponse().setComplete();
}
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
熔断与限流
熔断
当某个服务或组件出现故障时,快速失败并停止对其的调用,避免故障扩散到整个系统。有好几种组件,本文用的reactor-resilience4j,阿里的spring-cloud-starter-alibaba-sentinel也不错。
默认情况下服务不可用的话页面返回503
需要使用到spring-cloud-starter-circuitbreaker-reactor-resilience4j
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
模拟用户登录服务崩溃的情况(user-service没有启动)
访问 http://127.0.0.1:8010/account/login.html 网关系统自动重定向到熔断页面,如果不配置熔断页面会提示503
限流
分布式系统限流 是一种用于控制系统中请求流量的技术,目的是在系统资源有限的情况下,确保系统能够稳定运行,避免因流量过大而导致系统崩溃或性能下降。
可以限制ip,url,不同用户,每秒请求次数,最大请求次数。
需要使用到redis用于存储key
<!-- Redis Reactive (用于限流) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
自定义限流key
@Bean
public KeyResolver pathKeyResolver() {
KeyResolver keyResolver = pathKeyGet();
log.info(keyResolver.toString());
log.info("创建限流key存入redis: {} ", JSON.toJSONString(keyResolver));
return keyResolver;
}
public KeyResolver pathKeyGet() {
return exchange -> {
// 获取请求路径
String path = exchange.getRequest().getPath().toString();
String hostAddress = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
String key = hostAddress + "_" + path;
log.info("compositeKey 限流key:{}", key);
return Mono.just(key);
};
}
测试
我设置的同一ip每秒请求同一url不能超过3次,在我本地测试第四次会出现该页面。计算方式根据控制台结果是按第一次请求时间开始算的,如果第一次请求过了一秒,按顺序从第二次请求时间开始算。
灰度发布与流量控制
基于路径的流量分发
可以直接将user服务前置一个版本的uri,比如/v1/user/xxx /v2/user/xxx
spring:
cloud:
gateway:
routes:
- id: v1_route
uri: lb://user-service-v1
predicates:
- Path=/v1/user/**
filters:
- StripPrefix=2
- id: v2_route
uri: lb://user-service-v2
predicates:
- Path=/v2/user/**
filters:
- StripPrefix=2
基于 Header 的流量分发
通过请求头中(如 version)将流量分发到不同版本的服务
spring:
cloud:
gateway:
routes:
- id: v1_route
uri: lb://user-service-v1
predicates:
- Header=version, v1 # 匹配请求头 version=v1
- id: v2_route
uri: lb://user-service-v2
predicates:
- Header=version, v2 # 匹配请求头 version=v2
基于权重的流量分发
Weight 过滤器用于按权重分发流量。
group1 是分组的名称,相同分组的权重总和应为 100。
spring:
cloud:
gateway:
routes:
- id: weight_route
uri: lb://user-service-v1
predicates:
- Path=/service/**
filters:
- Weight=group1, 80 # 80% 的流量
- id: weight_route_v2
uri: lb://user-service-v2
predicates:
- Path=/service/**
filters:
- Weight=group1, 20 # 20% 的流量
结尾
第一篇快速部署一套分布式服务
希望本文可以帮到你。