写点什么

【HZERO 微服务平台 2】源码分析之网关

作者:qiaoxingxing
  • 2021 年 12 月 13 日
  • 本文字数:3665 字

    阅读完需:约 12 分钟

【HZERO微服务平台2】源码分析之网关

说明

分析 HZERO 网关(gateway)的常用功能、代码实现过程;


代码调用过程的记录方式参考:如何以纯文本方式快速记录java代码的调用过程

网关的作用:

  • 认证, 判断 token 是否有效, 并转换为 jwt token 传递到后端服务

  • 鉴权, 判断当前用户是否有权限访问当前接口

  • 动态路由, 可以动态添加路由到后端服务

  • 限流, 分流, 调用统计等

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

  • 所以角色合并在网关这里是没什么用处的, 相当于一直是合并的;

  • 如果拥有超级管理员角色, 会跳过后面的权限校验: AdminRolePermissionFilterAdminUserPermissionFilter

  • 只要拥有平台级超级管理员角色(和当前角色无关), 就会被认为是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 -> {
复制代码


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


hzero 对resolverlimiter做了扩展: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();
复制代码


用户头像

qiaoxingxing

关注

还未添加个人签名 2021.12.07 加入

还未添加个人简介

评论

发布
暂无评论
【HZERO微服务平台2】源码分析之网关 .md