说明
分析 HZERO 网关(gateway)的常用功能、代码实现过程;
代码调用过程的记录方式参考:如何以纯文本方式快速记录java代码的调用过程
网关的作用:
filter 过滤器
HelperFilter
hzero 实现了一套过滤链系统, 可以自定义鉴权逻辑, 默认实现在:hzero-gateway-helper-default.jar: org.hzero.gateway.helper.filter
理解 gateway 的关键是掌握它的一系列 filter, filter 的调用先后顺序:
//HelperChain#doFilter0. PermissionDisableOrSkipFilter 是否启用权限校验和跳过权限校验路径的过滤器1. WhiteListFilter 白名单过滤器2. BlackListFilter 黑名单过滤3. GetRequestRouteFilter 根据请求前缀获取对应的zuul路由4. GetPermissionFilter 根据接口uri,method和service获取匹配到的权限5. CollectSpanFilter 接口调用统计6. PublicRequestFilter 公共接口的权限校验7. GetUserDetailsFilter 根据access_token获取对应的userDetails8. AddJwtFilter 给返回添加JWT token9. LoginAccessRequestFilter loginAccess请求的权限校验10. AdminUserPermissionFilter 超级管理员的权限校验11. MenuPermissionFilter 校验菜单是否分配了API12. 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#filterresponseContext = authenticationHelper.authentication(exchange);DefaultReactiveAuthenticationHelper#authenticationDefaultReactiveAuthenticationHelper#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#applyreturn resolver.resolve(exchange).flatMap(key -> limiter.isAllowed(route.getId(), key).flatMap(response -> {
复制代码
其中的关键是resolver和limiter, resolver从请求里提取一个 key, 比如用户名、角色名、url, limiter根据 key 决定能否访问;
hzero 对resolver和limiter做了扩展:KeyResolver实现的子类:
CombinedKeyResolverOriginKeyResolverRoleKeyResolverTenantKeyResolverUrlKeyResolverUserKeyResolver
复制代码
可以对用户名、角色名、租户 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/refreshGatewayRateLimitSiteController#refreshGatewayRateLimitServiceImpl#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#loadBalancerClientreturn new RibbonLoadBalancerClient(springClientFactory());RibbonLoadBalancerClient#choose
复制代码
gateway 的实例选择过程:
LoadBalancerClientFilter#filterfinal ServiceInstance instance = choose(exchange);
LoadBalancerClientFilter#choosereturn loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
RibbonLoadBalancerClient#chooseRibbonLoadBalancerClient#getServer(java.lang.String)RibbonLoadBalancerClient#getServer(com.netflix.loadbalancer.ILoadBalancer)ZoneAwareLoadBalancer#chooseServerreturn super.chooseServer(key);BaseLoadBalancer#chooseServerreturn rule.choose(key); //默认rule是ZoneAvoidanceRuleCustomMetadataRule#choose //自定义的rule
复制代码
启用CustomMetadataRule的配置:
@RibbonClients( defaultConfiguration = {CustomMetadataRule.class})...route.RibbonAutoConfiguration
复制代码
ribbonRule bean 的默认配置, 默认是 ZoneAvoidanceRule
org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonRule@ConditionalOnMissingBeanpublic IRule ribbonRule(IClientConfig config) { ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
复制代码
评论