说明
分析 HZERO 网关(gateway)的常用功能、代码实现过程;
代码调用过程的记录方式参考:如何以纯文本方式快速记录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
所以角色合并在网关这里是没什么用处的, 相当于一直是合并的;
如果拥有超级管理员角色, 会跳过后面的权限校验: AdminRolePermissionFilter
、AdminUserPermissionFilter
只要拥有平台级超级管理员角色(和当前角色无关), 就会被认为是isSiteSuperRole
, 就不能调用租户级 api
调用链图示
token 获取
token 可以通过 header 的 authorization 或者 queryParams 的 access_token 传递, 实现过程:
GateWayHelperFilter#filter
responseContext = authenticationHelper.authentication(exchange);
DefaultReactiveAuthenticationHelper#authentication
DefaultReactiveAuthenticationHelper#parse
复制代码
调用过程的阅读方式: 如何以纯文本方式快速记录java代码的调用过程
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 -> {
复制代码
其中的关键是resolver
和limiter
, resolver
从请求里提取一个 key, 比如用户名、角色名、url, limiter
根据 key 决定能否访问;
hzero 对resolver
和limiter
做了扩展: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 的路由, 添加RequestRateLimiter
filter;
刷新限流接口:
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();
复制代码
评论