【设计模式】限流框架案例
参考:极客时间 设计模式之美 项目实战章节
重点考虑可扩展性:基于接口编程、提供抽象接口
优秀的代码是重构出来的,复杂的代码是慢慢堆砌出来的。小步快跑、逐步迭代是我比较推崇的开发模式。追求完美主义会让我们迟迟无法下手。所以,为了克服这个问题,一方面,我们可以规划多个小版本来开发,不断迭代优化;另一方面,在编程实现的过程中,我们可以先实现MVP代码,以此来优化重构。
我们站在Code Reviewer的角度,结合SOLID、DRY、KISS、LOD、基于接口而非实现编程、高内聚松耦合等经典的设计思想和原则,以及编码规范,从代码质量评判标准的角度,来剖析代码在可读性、扩展性、可维护性、灵活、简洁、复用性、可测试性等方面的表现,并且针对性地去优化不足。
包和类结构设计
限流规则解析parse:支持多种格式的规则文件解析
规则文件数据源: 支持数据库和本地文件类型多种限流算法
// 重构前: com.xzg.ratelimiter --RateLimiter com.xzg.ratelimiter.rule --ApiLimit --RuleConfig --RateLimitRule com.xzg.ratelimiter.alg --RateLimitAlg // 重构后: com.xzg.ratelimiter --RateLimiter(有所修改)
com.xzg.ratelimiter.rule // 限流规则内部模型 --ApiLimit(不变) --RuleConfig(不变) --RateLimitRule(抽象接口) --TrieRateLimitRule(实现类,就是重构前的RateLimitRule)
com.xzg.ratelimiter.rule.parser // 限流规则解析 --RuleConfigParser(抽象接口) --YamlRuleConfigParser(Yaml格式配置文件解析类) --JsonRuleConfigParser(Json格式配置文件解析类)
com.xzg.ratelimiter.rule.datasource --RuleConfigSource(抽象接口) --FileRuleConfigSource(基于本地文件的配置类)
com.xzg.ratelimiter.alg // 实际限流算法,时间窗口,令牌桶(支持以后扩展),策略模式优化 --RateLimitAlg(抽象接口) --FixedTimeWinRateLimitAlg(实现类,就是重构前的RateLimitAlg)
RateLimiter类:通过RuleConfigSource 实现配置文件加载
public class RateLimiter {
private static final Logger log = LoggerFactory.getLogger(RateLimiter.class);
// 为每个api在内存中存储限流计数器
private ConcurrentHashMap<String, RateLimitAlg> counters = new ConcurrentHashMap<>();
private RateLimitRule rule;
public RateLimiter() {
//改动主要在这里:调用RuleConfigSource类来实现配置加载
RuleConfigSource configSource = new FileRuleConfigSource();
RuleConfig ruleConfig = configSource.load();
this.rule = new TrieRateLimitRule(ruleConfig);
}
public boolean limit(String appId, String url) throws InternalErrorException, InvalidUrlException {
ApiLimit apiLimit = rule.getLimit(appId, url);
if (apiLimit == null) { return true; }
// 获取api对应在内存中的限流计数器(rateLimitCounter)
String counterKey = appId + ":" + apiLimit.getApi();
RateLimitAlg rateLimitCounter = counters.get(counterKey);
if (rateLimitCounter == null) {
RateLimitAlg newRateLimitCounter = new RateLimitAlg(apiLimit.getLimit());
rateLimitCounter = counters.putIfAbsent(counterKey, newRateLimitCounter);
if (rateLimitCounter == null) {
rateLimitCounter = newRateLimitCounter; }
}
// 判断是否限流
return rateLimitCounter.tryAcquire();
}
}
各个Parser和RuleConfigSource类的设计有点类似策略模式,如果要添加新的格式的解析,只需要实现对应的Parser类,并且添加到FileRuleConfig类的PARSER_MAP中就可以了。
com.xzg.ratelimiter.rule.parser --RuleConfigParser(抽象接口) --YamlRuleConfigParser(Yaml格式配置文件解析类) --JsonRuleConfigParser(Json格式配置文件解析类)
com.xzg.ratelimiter.rule.datasource --RuleConfigSource(抽象接口) --FileRuleConfigSource(基于本地文件的配置类) public interface RuleConfigParser { RuleConfig parse(String configText); RuleConfig parse(InputStream in); } public interface RuleConfigSource { RuleConfig load(); } public class FileRuleConfigSource implements RuleConfigSource { private static final Logger log = LoggerFactory.getLogger(FileRuleConfigSource.class); public static final String API_LIMIT_CONFIG_NAME = "ratelimiter-rule"; public static final String YAML_EXTENSION = "yaml"; public static final String YML_EXTENSION = "yml"; public static final String JSON_EXTENSION = "json"; private static final String[] SUPPORT_EXTENSIONS = new String[] {YAML_EXTENSION, YML_EXTENSION, JSON_EXTENSION};
private static final Map<String, RuleConfigParser> PARSER_MAP = new HashMap<>(); static { PARSER_MAP.put(YAML_EXTENSION, new YamlRuleConfigParser()); PARSER_MAP.put(YML_EXTENSION, new YamlRuleConfigParser()); PARSER_MAP.put(JSON_EXTENSION, new JsonRuleConfigParser()); } @Override public RuleConfig load() { for (String extension : SUPPORT_EXTENSIONS) { InputStream in = null; try { in = this.getClass().getResourceAsStream("/" + getFileNameByExt(extension)); if (in != null) { RuleConfigParser parser = PARSER_MAP.get(extension); return parser.parse(in); } } finally { if (in != null) { try { in.close(); } catch (IOException e) { log.error("close file error:{}", e); } } } } return null; } private String getFileNameByExt(String extension) { return API_LIMIT_CONFIG_NAME + "." + extension; } }
规则定义
configs:
- appId: app-1
limits:
- api: /v1/user
limit: 100
unit:60
- api: /v1/order
limit: 50
- appId: app-2
limits:
- api: /v1/user
limit: 50
- api: /v1/order
limit: 50
规则模型
public class RuleConfig { private List<AppRuleConfig> configs; public List<AppRuleConfig> getConfigs() { return configs; } public void setConfigs(List<AppRuleConfig> configs) { this.configs = configs; } public static class AppRuleConfig { private String appId; private List<ApiLimit> limits; public AppRuleConfig() {} public AppRuleConfig(String appId, List<ApiLimit> limits) { this.appId = appId; this.limits = limits; } //...省略getter、setter方法... } } public class ApiLimit { private static final int DEFAULT_TIME_UNIT = 1; // 1 second private String api; private int limit; private int unit = DEFAULT_TIME_UNIT; public ApiLimit() {} public ApiLimit(String api, int limit) { this(api, limit, DEFAULT_TIME_UNIT); } public ApiLimit(String api, int limit, int unit) { this.api = api; this.limit = limit; this.unit = unit; } // ...省略getter、setter方法... }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)