HZERO微服务平台04: 代码分析之网关filter、调用统计、限流、灰度发布

网关的作用

  • 鉴权, 判断当前用户是否有权限访问当前接口
  • 认证, 判断token是否有效, 并转换为jwt token传递到后端服务
  • 动态路由, 限流, 分流, 调用统计等

代码调用过程阅读方法: 如何以纯文本方式简单快速记录java代码的调用过程

filter 过滤器

HelperFilter

hzero实现了一套过滤链系统, 可以自定义鉴权逻辑, 默认实现在:
hzero-gateway-helper-default.jar: org.hzero.gateway.helper.filter

理解gateway的关键是掌握它的一系列filter, filter的调用先后顺序:

//HelperChain#doFilter
0. PermissionDisableOrSkipFilter 是否启用权限校验和跳过权限校验路径的过滤器
1. WhiteListFilter 白名单过滤器
2. BlackListFilter 黑名单过滤
3. GetRequestRouteFilter 根据请求前缀获取对应的zuul路由
4. GetPermissionFilter 根据接口uri,method和service获取匹配到的权限
5. CollectSpanFilter 接口调用统计
6. PublicRequestFilter 公共接口的权限校验
7. GetUserDetailsFilter 根据access_token获取对应的userDetails
8. AddJwtFilter 给返回添加JWT token
9. LoginAccessRequestFilter loginAccess请求的权限校验
10. AdminUserPermissionFilter 超级管理员的权限校验
11. MenuPermissionFilter 校验菜单是否分配了API
12. AdminRolePermissionFilter 超级角色的权限校验,超级角色不校验角色-API关系
13. CommonRequestCheckFilter 普通接口(除公共接口,loginAccess接口,内部接口以外的接口)普通用户(超级管理员之外用户)的权限校验

理解:

  • 校验权限的是最后一个filter: CommonRequestCheckFilter, 检查权限时没有限定当前角色, 任一角色有权限就能访问;
    • 即使检查角色, 也是检查某层级的角色集合, 而不是当前某一个角色;
    • hzero.gateway.helper.filter.common-request.checkCurrentRole默认值为false
    • 所以角色合并在网关这里是没什么用处的, 相当于一直是合并的;
  • 如果拥有超级管理员角色, 会跳过后面的权限校验: AdminRolePermissionFilterAdminUserPermissionFilter
  • 只要拥有平台级超级管理员角色(和当前角色无关), 就会被认为是isSiteSuperRole, 就不能调用租户级api

调用链图示

token获取

token可以从header的authorization或者queryParams的access_token里传递:

GateWayHelperFilter#filter
responseContext = authenticationHelper.authentication(exchange);
DefaultReactiveAuthenticationHelper#authentication
DefaultReactiveAuthenticationHelper#parse 

api调用统计

配置:

hzero.gateway.helper.filter.collect-span.enabled: true

代码位置:

...gateway.helper.filter.CollectSpanFilter

存储结构:

redis db4:
gateway:span:{日期} :某日期下所有服务的调用总数;
gateway:span:{日期}:{服务} : 某日期某服务下各个接口的调用次数, 求和等于上面的总数;
比如:

gateway:span:2021-02-05:hzero-admin

rate limit 限流

基于spring cloud gateway的限流

hzero的限流基于spring cloud gateway的RequestRateLimiter:
Spring Cloud Gateway

如果超过限流, 返回HTTP 429 - Too Many Requests, response的添加了一些header: X-RateLimit-Remaining、X-RateLimit-Replenish-Rate等;

限流的FilterFactory初始化:

GatewayAutoConfiguration#requestRateLimiterGatewayFilterFactory
@Bean
@ConditionalOnBean({RateLimiter.class, KeyResolver.class})
public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter, PrincipalNameKeyResolver resolver) {
    return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
}

spring cloud gateway限流的核心是:

RequestRateLimiterGatewayFilterFactory#apply
return resolver.resolve(exchange).flatMap(key -> 
    limiter.isAllowed(route.getId(), key).flatMap(response -> {

其中的关键是resolverlimiter, resolver从请求里提取一个key, 比如用户名、角色名、url, limiter根据key决定能否访问;

hzero对resolverlimiter做了扩展:
KeyResolver实现的子类:

CombinedKeyResolver
OriginKeyResolver
RoleKeyResolver
TenantKeyResolver
UrlKeyResolver
UserKeyResolver

可以对用户名、角色名、租户id、url、或者复合信息做限流;
key对应的请求限流多少, 保存在 gateway.ratelimit.limiter.EnhancedRedisRateLimiter.Config#replenishRateMap, 这个数据序列化为json, 保存在数据库路由配置的extend_config_map里:

hzero的org.hzero.gateway.ratelimit.limiter.EnhancedRedisRateLimiter类是从spring cloud gateway里复制粘贴出来的;

限流动态配置的实现

spring cloud gateway的限流是在application.yml里进行配置, hzero里application.yml里关于RequestRateLimiter的配置全部注释了, 配置来自于数据库hadm_service_route;
通过动态修改gateway的路由, 添加RequestRateLimiterfilter;

刷新限流接口:

hadm/v1/gateway-rate-limits/config/refresh
GatewayRateLimitSiteController#refresh
GatewayRateLimitServiceImpl#refresh
//step1:匹配路由,将限流配置合并到路由的额外配置(extend_config_map)中
//step2:通知更新服务

配置示例:

{"filters":
  [ "{\"name\":\"RequestRateLimiter\",
   \"args\":{\"rate-limiter\":\"#{@enhancedRedisRateLimiter}\",
   \"key-resolver\":\"#{new org.hzero.gateway.ratelimit.dimension.CombinedKeyResolver(new org.hzero.gateway.ratelimit.dimension.UrlKeyResolver(\\\"{1}\\\"))}\",
   \"redis-rate-limiter.replenishRate\":\"1\",
   \"redis-rate-limiter.replenishRateMap.\\\"url.hfle.v1.files.summary0._size-{1}\\\"\":\"1\"}}"
 ]}

数据库模型

  • hadm_gw_rate_limit 网关限流设置, 限流方式
  • hadm_gw_rate_limit_line 网关限流设置行明细, 限流路由
  • hadm_gw_rate_limit_dim 网关限流设置行明细, 限流规则
  • 关系(根据界面上的名称)
    • 限流方式 - 限流路由: 1-n
    • 限流路由 - 限流规则: 1-n

UrlKeyResolver详解

//org.hzero.gateway.ratelimit.dimension.UrlKeyResolver
//
//构造函数入参的url部分, 问号?前的部分
private String interestUrl;
//
//构造函数入参的参数部分, queryparm的名称
private String[] interestParamKeys;
//
//interestUrl把{i}替换为\\S+, 并且Pattern.compile, 所以只匹配path, 不匹配参数
private Pattern interestUrlPattern;

//入参格式: /v1/{1}/invoke?namespace={2}&serverCode={3}&interfaceCode={4}
//`{i}`表示变量, 从1开始; 

public UrlKeyResolver(String interestUrlTemplate) {}

url替换:

return PREFIX + urlKey
        .replaceAll("\\?", "._")
        .replaceAll("/", ".")
        .replaceAll("&", "_")
        .replaceAll("=", "-");

示例:

/demo/test?name={1}
url.demo.test._name-{1}

灰度发布

hzero-starter-dynamic-route并未开源, 结合网上的文章自行实现;

资料

动态路由组件

Spring Cloud Gateway 扩展支持多版本控制及灰度发布

实现原理

  • 核心思想是通过修改 Ribbon 选择节点的方法,因为不论是网关层、还是 feign 调用、还是使用 LoadBalanced 标注的 RestTemplate,最终都会通过 Ribbon 来从注册中心选择一个节点进行服务请求。
  • 自定义 CustomMetadataRule,继承自 ZoneAvoidanceRule,覆盖 choose 方法。在 choose 方法中首先拉取所有服务节点,然后根据当前规则所匹配的节点组ID来获取匹配的节点组。

ribbon代码

ribbon自动配置:

RibbonAutoConfiguration#loadBalancerClient
return new RibbonLoadBalancerClient(springClientFactory());
RibbonLoadBalancerClient#choose

gateway的实例选择过程:

LoadBalancerClientFilter#filter
final ServiceInstance instance = choose(exchange);

LoadBalancerClientFilter#choose
return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());

RibbonLoadBalancerClient#choose
RibbonLoadBalancerClient#getServer(java.lang.String)
RibbonLoadBalancerClient#getServer(com.netflix.loadbalancer.ILoadBalancer)
ZoneAwareLoadBalancer#chooseServer
return super.chooseServer(key);
BaseLoadBalancer#chooseServer
return rule.choose(key); //默认rule是ZoneAvoidanceRule
CustomMetadataRule#choose //自定义的rule

启用CustomMetadataRule的配置:

@RibbonClients(
   defaultConfiguration = {CustomMetadataRule.class}
)
...route.RibbonAutoConfiguration

ribbonRule bean的默认配置, 默认是ZoneAvoidanceRule

org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonRule
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
    ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
原文地址:https://www.cnblogs.com/QIAOXINGXING001/p/15564064.html