• 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();
    
  • 相关阅读:
    DEDECMS5.5/5.6/5.7列表页调用TAG标签(热门标签)的两种方法
    DEDE列表页和内容页调用顶级栏目ID的方法
    解决dede图集上传图片时跳出302错误
    DEDE用{dede:sql}标签取出当前文档的附加表中的内容
    DEDE模板中如何运行php脚本和php变量的使用
    织梦DEDECMS {dede:arclist},{dede:list}获取附加表字段内容
    把DEDE的在线文本编辑器换成Kindeditor不显示问题
    ExtJS:文件上传实例
    ExtJS:GridPanel之renderer:function()和itemdblclick : function()方法参数详解
    ExtJS:菜单ComboBox及级联菜单应用
  • 原文地址:https://www.cnblogs.com/QIAOXINGXING001/p/15564064.html
Copyright © 2020-2023  润新知