Python实现服务端限流器:单进程与分布式场景解析

远山来客
远山来客
发布时间:2026-10-13| 2人浏览过
原创

本文介绍了一种基于Python的手写服务端限流器,涵盖单进程多线程与多进程分布式两种场景。通过抽象基类定义接口,分别采用线程安全队列和Redis存储实现限流逻辑。文章详细阐述了限流器的优缺点,如非平滑限流的特性,并提供了完整的代码实现与测试示例,适用于高并发场景下的流量控制。

在项目中应用了限流机制,由于现有实现方式存在局限,因此手动编写了一个简易的服务端限流器。

服务端限流与客户端限流的核心区别在于:

1)服务端限流

针对接口请求进行流量控制,限制单位时间内的请求数量,旨在通过牺牲部分可用性来保障系统的高可用。

以具体场景为例,某服务接收请求处理后,将数据批量写入Elasticsearch进行索引存储。由于bulk索引操作资源消耗巨大,若遭遇流量激增,可能导致Elasticsearch被压垮(如队列阻塞、内存激增),因此需要对流量峰值进行限制。

2)客户端限流

主要限制客户端的访问频率。

例如,线程池本身就是一种天然的限流器,它限制了最大并发数max_connection,超出部分放入缓冲队列排队,若队列容量超过queue_size则丢弃请求。

本文重点讨论服务端限流器的实现。

该限流器的优势在于:

1)实现简单 2)功能有效

不足之处包括:

1)无法实现平滑限流

常见的令牌桶算法和漏桶算法(两者本质相似)可实现平滑限流。所谓平滑限流,例如限制5秒内访问不超过1000次,平滑限流能确保每秒约200次,分布均匀;而非平滑限流可能在第一秒内处理完1000次请求,随后4秒全部拒绝。•2)灵活性不足

仅实现了秒级限流。

支持两种运行场景:

1)单进程多线程场景(使用线程安全的Queue作为全局变量)

此种场景下仅部署一个实例,对该实例进行限流,但在生产环境中较少使用。

2)多进程分布式场景(使用Redis作为全局变量)

多实例部署通常是生产环境的标准配置。

在此场景下,需对整体流量进行把控。例如user服务部署了三个实例,对外暴露query接口,目标是限制该接口的整体峰值流量,而不关心具体负载分配到哪个实例。

补充说明,此功能也可通过nginx实现。

接下来介绍限流器的具体实现。

1、接口BaseRateLimiter

按照设计思路,首先定义一个接口或抽象类。

初始化时需配置rate参数,即限流器的限速值。

提供一个抽象方法acquire(),调用该方法以判断是否限制流量。

class BaseRateLimiter(object):

  __metaclass__ = abc.ABCMeta

  @abc.abstractmethod
  def __init__(self, rate):
    self.rate = rate

  @abc.abstractmethod
  def acquire(self, count):
    return

2、单进程多线程场景的限流ThreadingRateLimiter

继承BaseRateLimiter抽象类,使用线程安全的Queue作为全局变量,以消除竞态条件的影响。

后台有一个进程每秒清空一次queue;

当请求到达时,调用acquire函数,使queue计数加1,若超过限速值则返回限制,否则允许访问。

class ThreadingRateLimiter(BaseRateLimiter):

  def __init__(self, rate):
    BaseRateLimiter.__init__(self, rate)
    self.queue = Queue.Queue()
    threading.Thread(target=self._clear_queue).start()

  def acquire(self, count=1):
    self.queue.put(1, block=False)
    return self.queue.qsize() < self.rate

  def _clear_queue(self):
    while 1:
      time.sleep(1)
      self.queue.queue.clear()

2、分布式场景下的限流DistributeRateLimiter

继承BaseRateLimiter抽象类,使用外部存储作为共享变量,通过cache方式访问外部存储。

class DistributeRateLimiter(BaseRateLimiter):

  def __init__(self, rate, cache):
    BaseRateLimiter.__init__(self, rate)
    self.cache = cache

  def acquire(self, count=1, expire=3, key=None, callback=None):
    try:
      if isinstance(self.cache, Cache):
        return self.cache.fetchToken(rate=self.rate, count=count, expire=expire, key=key)
    except Exception, ex:
      return True

为实现解耦和灵活性,实现了Cache类,并提供抽象方法getToken()。

若使用Redis,则继承Cache抽象类,实现通过Redis获取令牌的方法。

若使用MySQL,则继承Cache抽象类,实现通过MySQL获取令牌的方法。

Cache抽象类定义如下:

class Cache(object):

  __metaclass__ = abc.ABCMeta

  @abc.abstractmethod
  def __init__(self):
    self.key = "DEFAULT"
    self.namespace = "RATELIMITER"

  @abc.abstractmethod
  def fetchToken(self, rate, key=None):
    return

提供一个Redis实现类RedisTokenCache。

每秒创建一个key,并对请求进行计数incr,当该秒计数值超过限速rate时,无法获取token,即限制流量。

对每秒创建的key设置超时expire,确保key不会持续占用存储空间。

实现过程无难点,此处利用Redis事务保证incr和expire操作同时执行成功。

class RedisTokenCache(Cache):

  def __init__(self, host, port, db=0, password=None, max_connections=None):
    Cache.__init__(self)
    self.redis = redis.Redis(
      connection_pool=
        redis.ConnectionPool(
          host=host, port=port, db=db,
          password=password,
          max_connections=max_connections
        ))

  def fetchToken(self, rate=100, count=1, expire=3, key=None):
    date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    key = ":".join([self.namespace, key if key else self.key, date])
    try:
      current = self.redis.get(key)
      if int(current if current else "0") > rate:
        raise Exception("to many requests in current second: %s" % date)
      else:
        with self.redis.pipeline() as p:
          p.multi()
          p.incr(key, count)
          p.expire(key, int(expire if expire else "3"))
          p.execute()
          return True
    except Exception, ex:
      return False

多线程场景下的测试代码示例:

limiter = ThreadingRateLimiter(rate=10000)

def job():
  while 1:
    if not limiter.acquire():
      print '限流'
    else:
      print '正常'

threads = [threading.Thread(target=job) for i in range(10)]
for thread in threads:
  thread.start()

分布式场景下的测试代码示例:

token_cache = RedisTokenCache(host='10.93.84.53', port=6379, password='bigdata123')
limiter = DistributeRateLimiter(rate=10000, cache=token_cache)
r = redis.Redis(connection_pool=redis.ConnectionPool(host='10.93.84.53', port=6379, password='bigdata123'))

def job():
  while 1:
    if not limiter.acquire():
      print '限流'
    else:
      print '正常'

threads = [multiprocessing.Process(target=job) for i in range(10)]
for thread in threads:
  thread.start()

读者可自行运行测试。

说明:

本文中的限速均为秒级,例如限制每秒400次请求。可能出现前100ms内涌入400次请求,后900ms全部被限制的情况,即无法实现平滑限流。

若后台逻辑包含队列或线程池等缓冲机制,这种非平滑限流的影响通常不大。

以上内容为本文全部,希望对读者学习有所帮助,也欢迎大家支持本站。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。