zoukankan      html  css  js  c++  java
  • Sentinel实现热点数据限流

    问题

    在Sentinel社区里看到一个问题,CommonFilter是否支持热点限流?
    问题链接:https://github.com/alibaba/Sentinel/issues/2014

    答案是不支持。
    因为CommonFilter源码里标记资源SphU.entry(String, int, EntryType),并没有像sentinel-dubbo-adaptor里的SentinelDubboProviderFilter那样通过4个参数重载的方法SphU.entry(java.lang.String, int, EntryType, java.lang.Object[])来标记资源,即传递接口相关的参数,因此它不能使用热点参数规则。

    场景

    实际项目中对热点数据限流的需求很常见。比如电商业务里的商品详情页,通过流量高峰来源于热点商品,如做促销活动的商品、预约抢购的商品、最近关注度高卖的很火的商品等,它的某时段访问量会比普通商品高很多。

    假设商品详情页接口为/product/detail
    我们对它设置限流规则,保护接口不被突发的流量击垮。
    如果对整个接口设置,假定接口支持最大qps=1000/s,那么有2个问题:

    1. 当流量高峰来临,接口达到或者超过1000/s时,这时部分请求会被限流然后快速失败,但因为是对整个接口做的限流,这时访问非热点商品,也可能出现限流,影响了用户体验
    2. 可能项目里会对热点商品查询做单独的优化,比如缓存等,它比普通商品详情接口而言能承担更高的qps阈值,对整个接口设置限流阈值粒度太粗,设高了可能应用拖垮,设低了优化后的程序没利用起来

    原因

    Sentinel的热点限流规则本来是用于热点数据场景的,但目前对sentinel-web-servlet(基于普通servlet)和sentinel-spring-webmvc-adapter(基于springmvc)两种适配都不支持。
    不支持的原因可能是:
    对于http request请求,不同项目可能获取参数的方式不一样。比如:
    有的是get请求,参数在url里;
    有的是post请求,参数在body里;
    有的参数是form data形式;
    有的参数是json格式;
    有的参数就一个,比如body里有个data参数,data里面是具体的json格式参数;
    有的不区分get/post;
    理论上说,如果项目的请求参数格式统一,应该可以按某个标准统一获取参数,最后转换为Object[] args形式。

    思路

    sentinel-web-servlet模块提供了UrlCleaner扩展,
    参考:https://github.com/alibaba/Sentinel/wiki/主流框架的适配#web-servlet

    它可用于清洗或者过滤资源(比如将满足 /foo/:id 的 URL 都归到 /foo/* 资源下,比如通过返回""排除某个URL)
    如果换个思路,基于它扩展也可实现热点参数限流。比如想对某个热点商品限流,实现一个自定义的UrlCleaner接口,
    里面获取到热点商品id参数,返回带上商品id的特定URL,这样生成新的资源,结合控制台就可以单独设置该URL的限流规则
    如:普通商品详情页的URL为:/product/detail,热点商品详情页URL为:/product/detail?id=xxx
    然后对两个URL设置普通流控规则就好。
    因为热点商品是单独的资源了,也可设置其它规则,比如降级规则。

    实战

    定义接口UrlParser:

    /**
     * @author cdfive 
     */
    public interface UrlParser {
        
        /**
         * 需要处理的url
         * 这里返回一个列表,因为可能一个业务对应多个接口,而处理逻辑一致
         * 如商品详情页,APP呈现该页面调了多个商品详情相关的接口,如:
         * /product/detail 获取商品详情信息
         * /prdouct/detail/promotion 获取商品可参与的促销活动
         * /product/detail/evalution 获取商品的评论
         */    
        List<String> getUrls();
    
        /**
         * 解析url生成需要的资源名
         * 这里可根据业务情况灵活处理,如调另接口查询哪些是热点商品,从缓存中取数据等
         */
        String parseUrl(String originUrl);
    }
    

    定义抽象类AbstractUrlParser,包括获取HttpServletRequest对象,拼接参数等公共方法:

    /**
     * base class for UrlParser
     *
     * @author cdfive
     */
    public abstract class AbstractUrlParser implements UrlParser {
    
        protected Logger log = LoggerFactory.getLogger(getClass());
    
        /**
         * separator between url and parameter
         */
        protected static final String URL_SEPERATOR = "#";
    
        /**
         * separator between parameter name and value
         */
        protected static final String PARAM_VALUE_SEPERATOR = "=";
    
        /**
         * separator between different parameters
         */
        protected static final String OTHER_PARAM_SEPERATOR = "&";
    
        /**
         * append parameters after url, including parameter name and parameter value
         */
        protected String appendUrlParam(String originUrl, String paramName, String paramValue) {
            return originUrl + URL_SEPERATOR + paramName + PARAM_VALUE_SEPERATOR + paramValue;
        }
    
        /**
         * batch append parameters after url, including parameter name and parameter value
         */
        protected String appendUrlParams(String originUrl, List<String> paramNames, List<String> paramValues) {
            StringBuilder newUrl = new StringBuilder(originUrl).append(URL_SEPERATOR);
            for (int i = 0; i < paramNames.size(); i++) {
                if (i > 0) {
                    newUrl.append(OTHER_PARAM_SEPERATOR);
                }
                newUrl.append(paramNames.get(i)).append(PARAM_VALUE_SEPERATOR).append(paramValues.get(i));
            }
            return newUrl.toString();
        }
    
        /**
         * get HttpServletRequest object
         */
        protected HttpServletRequest getHttpServletRequest() {
            try {
                return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            } catch (Exception e) {
                log.error("get HttpServletRequest error", e);
                return null;
            }
        }
    }
    

    对商品详情页接口定制处理,调商品服务ProductService查询哪些是热点商品:

    /**
     * Custom UrlParser for urls of product detail.
     *
     * @author cdfive
     */
    @Component
    public class ProductDetailUrlParser extends AbstractUrlParser {
    
        private static final String URL_PRODUCT_DETAIL = "/product/detail";
        private static final String URL_PRODUCT_DETAIL_PROMOTION = "/product/detail/promotion";
        private static final String URL_PRODUCT_DETAIL_EVALUTION = "/product/detail/evalution";
    
        private static final List<String> URLS = new ArrayList<String>() {
            {
                add(URL_PRODUCT_DETAIL);
                add(URL_PRODUCT_DETAIL_PROMOTION);
                add(URL_PRODUCT_DETAIL_EVALUTION);
            }
        };
    
        private static final String PARAM_NAME_PRODUCT_ID = "productId";
        private static final String PARAM_URL_PRODUCT_ID = "id";
    
        @Autowired
        private ProductService productService;
    
        @Override
        public List<String> getUrls() {
            return URLS;
        }
    
        @Override
        public String parseUrl(String originUrl) {
            HttpServletRequest request = super.getHttpServletRequest();
            if (request == null) {
                return originUrl;
            }
    
            String productIdStr = request.getParameter(PARAM_NAME_PRODUCT_ID);
            if (StringUtil.isBlank(productIdStr)) {
                log.error("ProductDetailUrlParser parameter productId is blank");
                return originUrl;
            }
    
            Long productId;
            try {
                productId = Long.parseLong(productIdStr);
            } catch (NumberFormatException e) {
                log.error("ProductDetailUrlParser parameter productId is invalid");
                return originUrl;
            }
    
            if (!productService.isHotProduct(productId)) {
                return originUrl;
            }
    
            return super.appendUrlParam(originUrl, PARAM_URL_PRODUCT_ID, String.valueOf(productId));
        }
    
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        private static class IdVo implements Serializable {
    
            private String id;
        }
    }
    

    定义CustomUrlCleaner类,实现UrlCleaner接口,里面通过UrlParser来处理:

    /**
     * @author cdfive
     */
    public class CustomUrlCleaner implements UrlCleaner {
    
        private static final Logger log = LoggerFactory.getLogger(CustomUrlCleaner.class);
    
        private List<UrlParser> urlParsers = new ArrayList<>();
    
        @Override
        public String clean(String originUrl) {
            if (urlParsers.isEmpty()) {
                return originUrl;
            }
    
            for (UrlParser urlParser : urlParsers) {
                if (urlParser.getUrls() != null && urlParser.getUrls().contains(originUrl)) {
                    try {
                        return urlParser.parseUrl(originUrl);
                    } catch (Exception e) {
                        log.error("urlParser[{}] parse url[{}] error", urlParser.getClass().getSimpleName(), originUrl, e);
                        return originUrl;
                    }
                }
            }
    
            return originUrl;
        }
    
        public List<UrlParser> getUrlParsers() {
            return urlParsers;
        }
    
        public void setUrlParsers(List<UrlParser> urlParsers) {
            this.urlParsers = urlParsers;
        }
    }
    

    通过以上步骤后,再访问商品详情页接口(假设热点商品id=2001,普通商品id=2002),在sentinel控制台的簇点链路菜单里可以看到,
    当商品是热点商品时生成了单独的资源/product/detail#id=2001
    当商品是普通商品时资源名为/prdouct/detail/
    我们可对/product/detail#id=2001单独设置流控、降级等流控规则,并不会影响到普通商品。

    跟sentinel的热点参数限流相比,缺点是需要编码优点是处理时灵活,通过UrlParser的抽象,不同业务可单独实现自己需要的定制逻辑而相互
    不影响,如商品详情用ProductDetailUrlParser实现,提交订单用SubmitOrderUrlParser实现。

    因为sentinel-web-servletsentinel-spring-webmvc-adapter本身也不支持热点参数限流,我们换一种思路通过扩展UrlCleaner
    也实现了对热点数据的限流,对保障业务稳定提供支持。

  • 相关阅读:
    grafana里prometheus查询语法
    Linux 高频工具快速教程
    国内开源镜像站点汇总
    Oracle DBLINK 简单使用
    启动OpenOffice服务
    使用openoffice转pdf,详细
    ORACLE数据库误操作执行了DELETE,该如何恢复数据?
    一个 介绍 superset Kylin 以及大数据生态圈的 博文
    Kylin介绍 (很有用)
    找到一些经验,关于使用thymeleaf时遇到的一些问题
  • 原文地址:https://www.cnblogs.com/cdfive2018/p/14704992.html
Copyright © 2011-2022 走看看