我的第一个想法是使用 Redis。但是还有更多很棒的工具,有些甚至更轻,所以我的解决方案建立在 zmq 之上。因此,您不必运行 Redis,运行小型 Python 脚本就足够了。
需求审查
在描述解决方案之前,让我回顾一下您的要求。
概念
限制时隙内的请求数
时隙可以是一秒、更多秒或更短的时间。唯一的限制是 Python 中时间测量的精度。
如果您的资源具有每秒定义的硬限制,则应使用时隙 1.0
监控每个时隙的请求数,直到下一个开始
首次请求访问您的资源时,设置下一个时间段的开始时间并初始化请求计数器。
对于每个请求,增加请求计数器(针对当前时隙)并允许请求,除非您已达到当前时隙中允许的最大请求数。
使用带有 REQ/REP 的 zmq 服务
您的消费服务器可能分布在更多计算机上。要提供对 LockerServer 的访问,您将使用 zmq。
示例代码
zmqlocker.py:
import time
import zmq
class Locker():
def __init__(self, max_requests=1, in_seconds=1.0):
self.max_requests = max_requests
self.in_seconds = in_seconds
self.requests = 0
now = time.time()
self.next_slot = now + in_seconds
def __iter__(self):
return self
def next(self):
now = time.time()
if now > self.next_slot:
self.requests = 0
self.next_slot = now + self.in_seconds
if self.requests < self.max_requests:
self.requests += 1
return "go"
else:
return "sorry"
class LockerServer():
def __init__(self, max_requests=1, in_seconds=1.0, url="tcp://*:7777"):
locker=Locker(max_requests, in_seconds)
cnt = zmq.Context()
sck = cnt.socket(zmq.REP)
sck.bind(url)
while True:
msg = sck.recv()
sck.send(locker.next())
class LockerClient():
def __init__(self, url="tcp://localhost:7777"):
cnt = zmq.Context()
self.sck = cnt.socket(zmq.REQ)
self.sck.connect(url)
def next(self):
self.sck.send("let me go")
return self.sck.recv()
运行你的服务器:
运行服务器.py:
from zmqlocker import LockerServer
svr = LockerServer(max_requests=5, in_seconds=0.8)
从命令行:
$ python run_server.py
这将开始在本地主机上的默认端口 7777 上提供储物柜服务。
运行你的客户
run_client.py:
from zmqlocker import LockerClient
import time
locker_cli = LockerClient()
for i in xrange(100):
print time.time(), locker_cli.next()
time.sleep(0.1)
从命令行:
$ python run_client.py
您将看到“go”、“go”、“sorry”……打印的响应。
尝试运行更多客户端。
一点压力测试
您可以先启动客户端,然后再启动服务器。客户端会一直阻塞,直到服务器启动,然后才会愉快地运行。
结论
- 满足描述的要求
- 请求数量有限
- 无需解锁,只要有下一个可用的时隙,它就允许更多请求
- LockerService 可通过网络或本地套接字使用。
- 应该是可靠的,zmq是成熟的解决方案,python代码比较简单
- 它不需要所有参与者的时间同步
- 性能会很好
另一方面,您可能会发现,您的资源限制并不像您想象的那么可预测,因此请准备好使用参数以找到适当的平衡,并始终为这方面的例外情况做好准备。
还有一些空间可以优化提供“锁” - 例如,如果储物柜用完了允许的请求,但当前时间段已经几乎完成,您可能会考虑用“对不起”稍等片刻,然后在几分之一秒后提供“开始” ”。
将其扩展到真正的分布式锁管理器
通过“分布式”,我们也可以理解多个储物柜服务器一起运行。这更难做到,但也是可能的。zmq 允许非常轻松地连接到多个 url,因此客户端可以非常轻松地连接到多个储物柜服务器。有一个问题,如何协调储物柜服务器不允许对您的资源进行过多请求。zmq 允许服务器间通信。一种模型可能是,每个储物柜服务器将在 PUB/SUB 上发布每个提供的“go”。所有其他储物柜服务器都将被订阅,并使用每个“go”来增加它们的本地请求计数器(稍微修改逻辑)。