{"id":31626320,"url":"https://github.com/wwjwell/tap","last_synced_at":"2025-10-06T19:50:32.533Z","repository":{"id":37172297,"uuid":"161443856","full_name":"wwjwell/tap","owner":"wwjwell","description":"tap, a rate limiter system for java","archived":false,"fork":false,"pushed_at":"2022-09-13T07:06:38.000Z","size":79,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-03-04T09:11:45.207Z","etag":null,"topics":["cluster-limiter","leaky-bucket","limiter","standalong-limiter","token-bucket"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wwjwell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-12-12T06:38:06.000Z","updated_at":"2022-09-13T07:05:58.000Z","dependencies_parsed_at":"2023-01-17T13:52:57.683Z","dependency_job_id":null,"html_url":"https://github.com/wwjwell/tap","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"purl":"pkg:github/wwjwell/tap","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwjwell%2Ftap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwjwell%2Ftap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwjwell%2Ftap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwjwell%2Ftap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wwjwell","download_url":"https://codeload.github.com/wwjwell/tap/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwjwell%2Ftap/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278671749,"owners_count":26025743,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cluster-limiter","leaky-bucket","limiter","standalong-limiter","token-bucket"],"created_at":"2025-10-06T19:50:28.005Z","updated_at":"2025-10-06T19:50:32.517Z","avatar_url":"https://github.com/wwjwell.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"#限流系统-Tap\n\t一个灵活的限流系统，可根据需要扩展丰富功能，支持基本的单机限流\n * 更简单、灵活的限流系统\n * 支持动态打开关闭、更改限流策略，即时更改，即时生效\n * 支持单机限流：限频、限QPS、黑名单、白名单、百分比\n * \u003e 集群需要使用Redis，暂无实现（支持集群限流：限频、限QPS、限额）\n\n# 整体设计\n## 基本概念\n| 系统概念   |      名称      |  具体描述 |\n| ---- | ---- | ------ | \n| channel | 限流通道| 一个业务系统分为多个channel，一个channel可以多个strategy.\u003cbr/\u003e业务系统对某个具体的资源进行限流，会根据当前资源找到对应的chennel，然后执行channel下的限流策略strategy，如果有一个strategy限制了请求，则整个channel限制请求 |\n| resource | 资源 | channel下面的一个属性，用于channel的区分。两种类型\u003cbr/\u003e 1、url-pattern类型：必须以/开头，主要应用于url匹配，如对http请求，比如/** 表示所有的URL， 可以支持：？匹配一个字、*匹配0个或多个字符、* *匹配0个或多个目录，于springMVC的url匹配模式一致，主要用于url限流方式 \u003cbr/\u003e2、key类型:不以/开头，只匹配一个，主要应用于具体的某个resource，常用于注解方式 |\n| strategy | 限流策略 | 具体的限流方式，如限额、限频、黑白名单等等。。。 |\n\n## 系统设计\n![系统设计](docs/tap-xmind.png)\n\n以web系统为例：\n\n​       系统对3类接口进行限流，用户/user/**、订单/order/**、支付/pay/**，则需要建立3个channel。\n\n​       对各个channel可以制定限流策略，限流策略可以是多个\n\n\n\n# 配置文件\n\n基于以上设计，得出tap的配置文件，JSON格式。\n\n```json\n{\n//项目\n\"name\":\"${name}\",\n//3个限流通道\n\"channels\":{\n\t\t[\n\t\t\t// 资源为/user/**的限流通道\n\t\t\t{\n\t\t\t\t\"resource\":\"/user/**\",\n\t\t\t\t\"strategies\":[\n\t\t\t\t\t{QPS限流策略},\n\t\t\t\t\t{限流策略2},\n\t\t\t\t\t...,\n\t\t\t\t\t{限流策略N}\n\t\t\t\t]\n\t\t\t},\n\t\t\t// 资源为/order/**的限流通道\n\t\t\t{\"resource\":\"/order/**\",\"strategies\":{[{限频策略1},{限流策略2},...,{限流策略N}]}}\n\t\t\t// 资源为/pay/**的限流通道\n\t\t\t{\"resource\":\"/user/**\",\"strategies\":{[{限额策略1},{限流策略2},...,{限流策略N}]}}\n\t\t]\n\t}\n}\n```\n\n# 接入指南\n\n## 主配置文件描述\n\n| 字段 | 父级 | 类型   | 值   | 描述                             |\n| ---- | ---- | ------ | ---- | -------------------------------- |\n| name |      | string |      | 主要用于二次校验，防止配置错误 |\n| enable |      | boolean | true/false | 项目总开关                   |\n| gatedIps |      | string |      | 灰度发布的ip,多个以英文逗号分隔，如果为空，则表示全部打开 |\n| channels |      | json数组  | []     | channel数组 |\n| channel |   channels   | json类型  |     {} | channel信息 |\n| resource |   channel   | string |      | 一组资源可以是以/开头的url,也可以是具体的字符串\u003cbr/\u003e以/开头的url，支持匹配，符合springmvc的路径匹配\u003cbr/\u003e普通字符串，在对应具体resource时使用equal的方式 |\n| enable |   channel   | boolean |      | channel开关 |\n| strategies |  channel    | json数组 |   []   | channel限流策略数组 |\n| strategy |   strategies   | json类型 |    {}  | strategy信息 |\n| strategyClassName |  strategy    | string |      | className |\n| config |  strategy    | string |      | 限流策略具体配置 |\n\n配置文件详解：\n```json\n{\n  \"name\": \"test.tap\", ## 项目名称\n  \"enable\":true, ## 项目总开开关\n  \"gateIps\":\"172.18.183.29\", ##灰度发布的ipv4地址，多个以英文逗号分隔，如果为空则表示日志\n  \"channels\":[  ## 限流通道\n    {\n      \"resource\":\"/**\",  ## 通道/** resource\n      \"enable\":true,\t ## 通道开关\n      \"strategies\":[     ## 通道限流策略\n        {\n\t\t   ##策略类名  \n          \"strategyClassName\":\"com.github.wwjwell.tap.strategy.cluster.ClusterFrequencyStrategy\",\n           ##策略配置\n          \"config\":{\n\t\t\t## 具体该策略值配置\n            \"interval\": 200\n          }\n        },\n\t\t{\n  \t\t\t\"strategyClassName\":\"com.github.wwjwell.tap.strategy.cluster.ClusterQuotaStrategy\",\n  \t\t\t\"config\":{\n    \t\t\t\"key\": \"uid\",\n    \t\t\t\"period\": 20,\n    \t\t\t\"quota\":10\n  \t\t\t}\n\t\t}\n      ]\n    }\n  ]\n}\n```\n## 如何初始化Tap\n### Spring配置初始化\n\n```xml\n\u003c!-- 系统以及初始化redisStoreClient，可以直接注入 --\u003e\n\u003cbean id=\"tapContext\" class=\"com.github.wwjwell.tap.spring.TapContext\"\u003e\n    \u003cproperty name=\"configLocation\" value=\"config_Frequency.json\"/\u003e\n \u003c/bean\u003e\n \n```\n\n### 普通方式初始化\n\n```java\n/** 使用系统的redisStoreClient */\nTap tap = new Tap.Builder()\n    .configLocation(\"/config_Frequency.json\")\n    .build();\n\n```\n## 如何使用\n### 普通方式使用\n```java\nHashMap\u003cString, Object\u003e attachment = new HashMap();\nattachment.put(key,value);\n//acquire 来对 resource=\"/hello\" 做限流控制\nRateLimitResult result = tap.rateLimiter().acquire(\"/hello\", attachment);\nif(result.isReject()){\n\tSystem.err.println(\"reject\");\n}else{\n\tSystem.out.println(\"pass\"):\n}\n```\n### spring使用方式\n```java\n@Autowired\nprivate RateLimiter ratelimter;\n \n/**\n * spring中使用ratelimiter.acquire进行限流控制\n */\npublic boolean doAcquire(String resource,Map\u003cString,Object\u003e attachment){\n\treturn ratelimiter.acquire(resource,attachment).isAccess();\n}\n```\n\n\n### WEB系统在filter中使用限流\n```java\npublic class RateLimiterFilter implements Filter {\n    private Logger logger = LoggerFactory.getLogger(RateLimiterFilter.class);\n    private RateLimiter rateLimiter;\n    @Override\n    public void init(FilterConfig filterConfig) throws ServletException {\n        ServletContext servletContext = filterConfig.getServletContext();\n        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);\n        assert context!=null;\n        rateLimiter = context.getBean(RateLimiter.class);\n    }\n\n    @Override\n    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {\n        HttpServletRequest request = (HttpServletRequest) servletRequest;\n\n        //用法\n        HashMap\u003cString, Object\u003e map = Maps.newHashMap();\n        String uid = request.getParameter(\"uid\");\n        map.put(\"uid\", uid);\n        map.put(\"uuid\", UUID.randomUUID().toString());\n        RateLimitResult rateLimitResult = rateLimiter.acquire(request.getRequestURI(), map);\n        if (rateLimitResult.isReject()) {\n            logger.warn(\"reject, reason={}\", rateLimitResult);\n            return;\n        } else {\n            logger.info(\"access\");\n        }\n\n        filterChain.doFilter(servletRequest,servletResponse);\n    }\n    @Override\n    public void destroy() {\n\n    }\n}\n```\n\n### 注解方式使用\n注解方式必须使用spring方式初始化\n```java\n@RateLimiter(resource = \"sayHelloResource\",rejectType = RateLimiter.RejectType.FallBack, fallBackMethod = \"sayHelloCallBack\")\npublic String sayHello(@Attachment(key = \"uid\")String uid){\n    System.out.println(\"sayHello\" + uid);\n    return \"ok Hello\";\n}\n\npublic String sayHelloCallBack(String uid) {\n    System.out.println(\"sayHelloCallBack\" + uid);\n    return \"callback hello\";\n}\n```\n\n# 限流策略\n\n## 单机-限频\n\n* 描述：单机限制每次请求间隔不小于某个值\n* 适用场景：下单、秒杀抢购等场景\n* 提示: 由于使用本地LRU缓存，如果在配置时间间隔内 比如说24*3600*1000（一天），导致系统缓存存储超过10万个 key的请求，系统为了防止OOM，会删除最早的key，这样会影响这部分key的限流\n* strategyClassName:\"com.github.wwjwell.tap.strategy.local.FrequencyStrategy\"\n* 配置描述\n\n  | 字段                 | 含义                         | 是否必须 | 默认值 | 描述                                                         |\n  | -------------------- | ---------------------------- | -------- | ------ | ------------------------------------------------------------ |\n  | key                  | attachment的key              | 否       | 无     | 如果key为空，则按照resource来限频，如果key不为空，按照key的value来限频 |\n  | keyAppendResource    | 是否将resource带入acqurieKey | 否       | true   | 如果为true，限流acquireKey = resource + attachment.get(key) 。false = attachment.get(key) |\n  | intervalMilliseconds | 时间间隔                     | 否       | 无     | 单位毫秒，每次请求最小时间间隔                               |\n  | maxCacheSize         | 缓存大小                     | 否       | 100000 | 缓存对了大小，在intervalMilliseconds间隔时间内，请求量不允许超过该值，否则该策略将不准确，如果资源特别多，请选择集群限频 |\n* Demo\n```json\n{\n  \"name\": \"test.tap\",\n  \"enable\":true,\n  \"channels\":[\n    {\n      \"resource\":\"/**\",\n      \"enable\":true,\n      \"strategies\":[\n        {\n          \"strategyClassName\":\"com.github.wwjwell.tap.strategy.local.FrequencyStrategy\",\n          \"config\":{\n\t\t\t\"key\":\"uid\",\n            \"intervalMilliseconds\": 100\n          }\n        }\n      ]\n    }\n  ]\n}\n```\n## QPS限流\n* 描述 ：单机限制请求的QPS,基于谷歌guava的令牌桶算法\n* 适用场景：允许一定量的流量突发的QPS限流\n* 提示：强烈建议使用这种方式，系统吞吐量会根据集群数量而增加\n* strategyClassName:\"com.github.wwjwell.tap.strategy.local.ThresholdStrategy\" \n* 配置描述\n\n| 字段 | 含义 | 是否必须 | 默认值 | 描述 |\n| ------------------- | -------------- | -------- | ------ | ---------------------- |\n| timeoutMilliseconds | 超时时间       | 否       | 200    | 超时时间，最长等待时间 |\n| qps                 | qps            | 是       | 无     | 每秒最大请求数         |\n| permits             | 每次需要令牌数 | 否       | 1      | 每个请求消耗令牌数量   |\n* Demo\n```json\n{\n  \"name\": \"test.tip\",\n  \"enable\":true,\n  \"channels\":[\n    {\n      \"resource\":\"/**\",\n      \"enable\":true,\n      \"strategies\":[\n        {\n          \"strategyClassName\":\"com.github.wwjwell.tap.strategy.local.ThresholdStrategy\",\n          \"config\":{\n            \"qps\": 10\n          }\n        }\n      ]\n    }\n  ]\n}\n```\n\n## 限百分比\n\n* 描述 ：限制某个关键字的百分比，对attachment.get(key)的值做hash运算 % 100 ，如果范围超过指定范围，则拒绝\n* 适用场景：AB发布。\n* strategyClassName:\"com.github.wwjwell.tap.strategy.local.KeyPercentStrategy\" \n* 配置描述\n\n| 字段                | 含义           | 是否必须 | 默认值 | 描述                   |\n| ------------------- | -------------- | -------- | ------ | ---------------------- |\n| key | attachment的key      | 是      | 无    | key.value.做hash运算 |\n| keyAppendResource | 是否将resource带入acqurieKey | 否      | true | 如果为true，限流acquireKey = resource + attachment.get(key) 。false = attachment.get(key) |\n| percent      | 百分比 | 是      | 无     | 超出此值以外的请求，将会被拒绝 |\n* Demo :\n```json\n{\n  \"name\": \"test.tip\",\n  \"enable\":true,\n  \"channels\":[\n    {\n      \"resource\":\"/**\",\n      \"enable\":true,\n      \"strategies\":[\n        {\n          \"strategyClassName\":\"com.github.wwjwell.tap.strategy.local.KeyPercentStrategy\",\n          \"config\":{\n            \"key\": \"uid\",\n            \"percent\":1.55\n          }\n        }\n      ]\n    }\n  ]\n}\n```\n##黑名单\n* 描述 ：黑名单，在黑名单的请求，直接拒绝\n* 适用场景：封禁。\n* strategyClassName:\"com.github.wwjwell.tap.strategy.local.BlackListStrategy\" \n* 配置描述\n\n| 字段                | 含义           | 是否必须 | 默认值 | 描述                   |\n| ------------------- | -------------- | -------- | ------ | ---------------------- |\n| key | attachment的key      | 是      | 无    | key.value.做hash运算 |\n| keyAppendResource | 是否将resource带入acqurieKey | 否      | true | 如果为true，限流acquireKey = resource + attachment.get(key) 。false = attachment.get(key) |\n| blacklist      | 黑名单列表 | 是      | 无     | 黑名单列表，多个以英文逗号分隔 |\n* Demo：\n```json\n{\n  \"name\": \"test.tip\",\n  \"enable\":true,\n  \"gatedIps\":\"172.18.183.29\",\n  \"channels\":[\n    {\n      \"resource\":\"/**\",\n      \"enable\":true,\n      \"strategies\":[\n        {\n          \"strategyClassName\":\"com.github.wwjwell.tap.strategy.local.BlackListStrategy\",\n          \"config\":{\n            \"key\":\"uid\",\n\t\t\t\"keyAppendResource\":\"false\",\n            \"blackList\": \"1,2,10,10\"\n          }\n        }\n      ]\n    }\n  ]\n}\n```\n##白名单\n* 描述 ：黑名单，在黑名单的请求，直接拒绝\n* 适用场景：封禁。\n* strategyClassName:\"com.github.wwjwell.tap.strategy.local.BlackListStrategy\" \n* 配置描述\n\n| 字段                | 含义           | 是否必须 | 默认值 | 描述                   |\n| ------------------- | -------------- | -------- | ------ | ---------------------- |\n| key | attachment的key      | 是      | 无    | key.value.做hash运算 |\n| keyAppendResource | 是否将resource带入acqurieKey | 否      | true | 如果为true，限流acquireKey = resource + attachment.get(key) 。false = attachment.get(key) |\n| whiteList | 白名单列表 | 是      | 无     | 白名单列表，多个以英文逗号分隔 |\n| ignoreAfterStrategy | 忽略后续策略 | 否     | false | 是否忽略后续策略验证，如果为true,白名单通过的请求，后续策略将不会做限制 |\n* Demo：\n```json\n{\n  \"name\": \"test.tip\",\n  \"enable\":true,\n  \"gatedIps\":\"172.18.183.29\",\n  \"channels\":[\n    {\n      \"resource\":\"/**\",\n      \"enable\":true,\n      \"strategies\":[\n        {\n          \"strategyClassName\":\"com.github.wwjwell.tap.strategy.local.WhiteListStrategy\",\n          \"config\":{\n            \"key\":\"uid\",\n\t\t\t\"keyAppendResource\":\"false\",\n            \"whiteList\": \"1,2,10\",\n            \"ignoreAfterStrategy\":true\n          }\n        }\n      ]\n    }\n  ]\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwwjwell%2Ftap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwwjwell%2Ftap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwwjwell%2Ftap/lists"}