# ratelimit **Repository Path**: longyunfeigu/ratelimit ## Basic Information - **Project Name**: ratelimit - **Description**: No description available - **Primary Language**: Go - **License**: 0BSD - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-05-20 - **Last Updated**: 2024-05-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 设计一个支持各种算法的限流框架 针对每一个项目,我们一般会分成分析、设计、实现这三个部分。其实,任何开源项目的学习,我们不仅仅要学习其功能, 还要找到并学习背后的开发套路。 # 分析 ## 需求分析 现在,我们需要为公司的各个业务线提供公共服务。这个公共服务平台面临的问题包括调用方代码bug、不正确使用服务以及突然增加的业务流量等。这些问题可能导致接口请求响应时间增加,甚至超时。为了解决这个问题,我们提出开发接口限流功能。该功能能限制每个调用方的接口请求频率,并允许对单个接口访问频率进行精细控制。我们的目标是将这个限流功能开发成一个通用框架,使其可以在项目中使用,也可以集成到微服务治理平台上。这样可以展示我们在业务开发过程中的抽象思维和框架思维。 常见的需求分析方法有创建线框图、编写用户用例、实行测试驱动开发等。在此基础上,我们可以运用用户用例和测试驱动开发的思想来预测框架最终开发出来后的使用情况。我通常会选取一个适合的应用场景并针对此场景编写一个框架使用的Demo程序,这样便能直观地展示出框架的样子。 接下来我们来看限流框架的应用场景。首先,我们需要设置限流规则。为了能够在不修改代码的前提下调整规则,我们通常会将规则存放在配置文件中(比如XML或YAML)。当集成了限流框架的应用启动时,该框架会按照预先定义的语法解析和加载这些限流规则到内存中。 以下是我编写的一份限流规则的Demo配置: ```yaml configs: appId: app1 limits: - api: /v1/user limit: 100 - api: /v1/order limit: 200 ``` 在接收到接口请求后,应用会将请求发送给限流框架,如果请求超出了设定的限制,限流框架会告诉应用触发限流机制。 根据前面的Demo,我们可以看到,从用户角度来说,限流框架主要包含两个核心功能:配置限流规则和通过编程接口(例如RateLimiter类)验证请求是否受限。然而,对于一个通用框架来说,除了满足功能性需求之外,非功能性需求同样重要,有时甚至可能决定框架是否成功。这些非功能性需求包括易用性、扩展性、灵活性、性能和容错性。 * 在易用性方面,我们需要保证限流规则的配置和编程接口的使用都尽可能简单。我们希望提供多种不同的限流算法,如基于内存的单机限流算法和基于Redis的分布式限流算法,以便用户可以根据需要自由选择。此外,考虑到大部分项目都是基于Spring开发的,我们也希望限流框架能无缝集成到Spring项目中。 * 在扩展性和灵活性方面,我们需要框架能够灵活地支持各种限流算法的扩展,并支持不同格式(比如JSON、YAML、XML等)和不同数据源(例如本地文件配置或Zookeeper集中配置等)的限流规则配置。 * 在性能方面,我们必须考虑到每个接口请求都需要进行限流检查,这可能会加重系统负载并影响响应时间。因此,我们需要尽可能优化限流框架的性能,以减少其对响应时间的影响。 * 在容错性方面,我们使用限流框架的目标是提高系统的稳定性和可用性,任何由于限流框架引起的问题都不能对服务本身的可用性产生负面影响。例如,如果分布式限流依赖的Redis出现故障,导致无法正常执行限流逻辑,我们还需保证业务接口能继续正常运行。 总的来说,一个优秀的限流框架应当兼顾功能性和非功能性需求,为用户提供全面且高效的服务。 # 设计 这里的"设计"指的是系统设计,主要是划分模块,对模块进行设计, 这里我们分成限流规则、限流算法、限流模式、集成使用这4个模块。 ## 限流规则 框架需要定义限流规则的语法格式,包括调用方、接口、限流阈值、时间粒度这几个元素, 下面是个限流规则示例: ```yaml rateLimits: - appId: app1 api: /v1/user limit: 100 timeWindow: 60 - appId: app2 api: /v1/order limit: 200 timeWindow: 120 ``` 除了刚刚讲的本地文件的配置方式之外,我们还希望兼容从其他数据源获取配置的方式,比如 Zookeeper 或者自研的配置中心。 我们通常会设计一个抽象接口,然后针对不同的数据源(如本地文件、Zookeeper、自研配置中心等)实现这个接口。这种设计模式被称为"策略模式",可以使得代码更加模块化和易于扩展。 例如,你可以定义一个名为ConfigLoader的接口,该接口包含一个方法用于加载配置信息。然后,你可能会有FileConfigLoader、ZookeeperConfigLoader以及CustomConfigCenterLoader等类,它们都实现ConfigLoader接口,并根据具体的数据源进行配置加载。 在需要获取配置时,只需引用ConfigLoader接口即可,而具体使用哪个实现类,可以由实际情况或者配置来决定。这样做的好处是,你的主要逻辑代码不必关心配置是从何处加载的,降低了各部分之间的耦合度,并提高了代码的可维护性和可测试性。 ### COC 原则 "约定优于配置"(Convention over Configuration,简称CoC)是一种软件设计范式,旨在减少在应用程序开发中需要做出的决策数量,通过采用默认的约定来简化配置过程。这种方法试图减少配置文件的数量和复杂性,让开发人员能够快速进行开发,而不是花费大量时间决定和编写配置选项。 CoC的核心思想是,如果有一种合理的默认行为,那么系统就应该采用这种默认行为,仅在开发者需要与众不同的行为时,才需要明确配置。这样做的好处是显而易见的:它可以显著降低项目的初始化成本,让开发者更多地专注于业务逻辑的实现,而不是花费过多时间在环境配置上。 比如,在一个使用CoC理念的Web框架中,如果你放置一个名为`User`的模型在预定的位置,系统就能自动理解这是一个与用户数据表相关联的模型,无需额外的配置告诉框架这一点。除非你的数据表名不遵循框架的命名约定,这时你才需要额外配置来指定这种关系。 Ruby on Rails 是一个著名的采用了CoC理念的Web开发框架。它鼓励开发者遵循一套约定来命名方法和文件,从而减少配置文件的数量,让开发者能够快速上手和开发应用程序。这种“约定”包括但不限于:类名与数据库表名的映射、URL与控制器动作的映射等。 总的来说,"约定优于配置"这一设计范式的目的是为了简化开发过程,减少不必要的决策,让开发者能够更快地开始编写对用户真正有价值的代码。 "约定优于配置"(Convention over Configuration,CoC)原则在开发限流框架中的应用可以大大简化框架的使用和配置过程。以下是一些具体的应用方式和作用: 1. 默认行为:一般情况下,限流框架会有一些默认的行为,例如默认的限流算法(如令牌桶或漏桶算法)、默认的限流速率等。通过提供这些默认行为,开发者可以直接使用框架而无需花费大量时间进行配置。 2. 简化配置:对于需要特殊配置的场景,开发者只需要改变那些与默认值不同的配置项。这样不仅减少了配置项的数量,也使配置过程更加直观和简单。 3. 易于理解和使用:采用CoC原则的框架通常更容易被理解和使用。因为开发者不需要记住大量的配置项,只需知道如何改变默认行为就足够了。 4. 减少错误:由于减少了配置的复杂性,同时默认配置往往已经经过严格测试和优化,使用CoC原则可以降低由于配置错误导致的问题。 ## 限流算法 * 固定窗口 * 滑动窗口 * 漏桶 * 令牌桶 ## 限流模式 限流模式分为单机限流和分布式限流两种。单机限流是对单个服务实例的访问频率进行限制,而分布式限流则是对某个服务的多个实例的总访问频率进行限制。实现上,单机限流通过在单个实例中维护接口请求计数器来实现,分布式限流则需要集中管理计数器(如使用Redis),以实现对多个实例总访问频率的限制。 我们说框架要高容错,不能因为框架的异常,影响到集成框架的应用的可用性和稳定性。这对于分布式限流框架来说尤其重要。而处理和优化Redis访问超时问题是保证这两点的关键: * 高容错:为了避免框架的异常影响到应用的正常运行,错误处理机制是必不可少的。如果发生Redis相关的异常(例如访问超时或其他错误),框架应当能捕获并适当处理这些异常,比如默认放行所有请求。 * 低延迟:限流逻辑的执行时间应当非常短,以尽量减少对接口请求本身响应时间的影响。由于网络通信本身就具备一定的延迟,因此在设置Redis访问超时时间时需要尽可能地精确,既不能太大(否则会增加接口响应时间),也不能太小(否则可能导致过多的限流失效)。 ## 集成使用 考虑到侵入性和耦合程度是设计框架时的重要因素,让框架与业务代码尽可能地松耦合可以减少对业务代码的影响,降低维护成本,并使得替换或删除框架更为容易。 例如,一个低侵入性的限流框架可以通过AOP、过滤器或拦截器等方式实现,而无需修改业务代码。这样一来,如果需要替换或移除限流框架,只需要更改配置或删除相关的注解即可,不必修改业务逻辑。 # 实现 优秀的代码通过重构得来,而复杂的代码则是逐步堆砌而成。推崇小步快跑、逐步迭代的开发模式。在开发限流框架时,不必一开始就追求大而全,而是可以先实现一个包含核心功能的V1版本。比如V1版本仅支持HTTP接口的限流,限流规则仅支持本地文件配置且配置文件格式为YAML,限流算法采用固定时间窗口算法,限流模式仅支持单机限流。同时,开发时要考虑代码的扩展性,为后续版本的扩展留出空间。 在日常工作中,我们往往边写代码边设计,不会严格先设计后编码。追求完美会阻碍开始编程。建议的做法是先忽略设计和代码质量,完成基本功能,哪怕所有代码都在一个类中。之后,对这个最小原型代码进行优化重构,如将独立代码块抽离成类或函数。 > 面向对象设计与实现一般可以分为四个步骤:划分职责识别类、定义属性和方法、定义类之间的交互关系、组装类并提供执行入口。