Python的分布式锁管理器

时间:2014-04-12 22:39:42

标签: python python-2.7

我有一堆服务器,其中多个实例访问的资源对每秒请求有一个硬限制。

我需要一种机制来锁定此资源对所有正在运行的服务器和实例的访问权限。

我在github上找到了一个安静的分布式锁管理器:https://github.com/thefab/restful-distributed-lock-manager

不幸的是,似乎有一分钟。锁定时间为1秒,相对不可靠。在几次测试中,解锁1秒钟需要1到3秒钟。

有没有经过python接口测试的东西我可以用于此目的?

编辑:我需要在1秒内自动解锁的东西。锁永远不会在我的代码中释放。

4 个答案:

答案 0 :(得分:14)

我的第一个想法是使用Redis。但是有更多伟大的工具,有些甚至更轻,所以我的解决方案建立在zmq上。因此,您不必运行Redis,只需运行小型Python脚本即可。

要求审核

让我在描述解决方案前审核您的要求。

  • 在一段固定时间内限制某些资源对多个请求的请求数。

  • 自动解锁

  • 资源(自动)解锁应在短于1秒的时间内完成。

  • 应分发。我会假设,你的意思是消耗一些资源的多个分布式服务器应该能够并且只有一个更衣室服务(在结论上更多)

概念

限制时间段内的请求数

时间段可以是秒,更多秒或更短的时间。唯一的限制是Python中时间测量的精确度。

如果您的资源每秒定义了硬限制,则应使用时间段1.0

监控每个时隙的请求数,直到下一个时隙开始

首次请求访问您的资源时,请设置下一个时间段的开始时间并初始化请求计数器。

对于每个请求,增加请求计数器(对于当前时隙)并允许请求,除非您已达到当前时隙中允许的最大请求数。

使用zmq与REQ / REP

一起服务

您的消费服务器可以分布在更多计算机上。要提供对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()

运行您的服务器:

run_server.py:

from zmqlocker import LockerServer

svr = LockerServer(max_requests=5, in_seconds=0.8)

从命令行:

$ python run_server.py

这将开始在localhost上的默认端口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

你会看到&#34; go&#34;,&#34; go&#34;,&#34;抱歉&#34; ...打印回复。

尝试运行更多客户端。

一点压力测试

您可以先启动客户端,然后再启动服务器。客户端将阻塞,直到服务器启动,然后很高兴运行。

结论

  • 描述的要求得以实现
    • 请求数量有限
    • 无需解锁,只要有下一个时间段,就可以提供更多请求
    • LockerService可通过网络或本地套接字使用。
  • 它应该是可靠的,zmq是成熟的解决方案,python代码相当简单
  • 不需要跨所有参与者进行时间同步
  • 表现会很好

另一方面,您可能会发现,资源的限制并不像您假设的那样可预测,因此请准备好使用参数来找到适当的平衡,并始终为此方面的例外做好准备。

还有一些空间可以优化提供&#34;锁定&#34; - 例如如果储物柜用完了允许的请求,但当前时间段已经基本完成,你可能会考虑等待你的&#34;抱歉&#34;并在一小段时间后提供&#34;去&#34;。

将其扩展到真正的分布式锁管理器

分发&#34;分发&#34;我们也可能了解一起运行的多个更衣室服务器。这样做更难,但也有可能。 zmq允许非常容易地连接到多个URL,因此客户端可以非常轻松地连接到多个更衣室服务器。有一个问题,如何协调更衣室服务器不允许对您的资源提出太多请求。 zmq允许服务器间通信。一种模式可能是,每个储物柜服务器都会发布每个提供的&#34; go&#34;在PUB / SUB上。所有其他更衣柜服务器都将被订阅,并且每个服务器都会被使用&#34;去&#34;增加他们的本地请求计数器(用一点修改的逻辑)。

答案 1 :(得分:0)

您的要求似乎非常具体。我考虑编写一个简单的锁服务器,然后使用一个在创建锁时获取锁的类实现锁客户端,然后在超出范围时删除锁。

class Lock(object):
    def __init__(self,resource):
        print "Lock acquired for",resource
        # Connect to lock server and acquire resource

    def __del__(self):
        print "Lock released"
        # Connect to lock server and unlock resource if locked

def callWithLock(resource,call,*args,**kwargs):
    lock = Lock(resource)
    return call( *args, **kwargs )

def test( asdf, something="Else" ):
    return asdf + " " + something

if __name__ == "__main__":
    import sys
    print "Calling test:",callWithLock( "resource.test", test, sys.argv[0] )

示例输出

$ python locktest.py 
Calling test: Lock acquired for resource.test
Lock released
locktest.py Else

答案 2 :(得分:0)

对于我的集群,我使用带有python-kazoo库的ZooKeeper进行队列和锁定。

来自kazoo api文档的修改示例,用于您的目的: http://kazoo.readthedocs.org/en/latest/api/recipe/lock.html

zk = KazooClient()
lock = zk.Lock("/lockpath", "my-identifier")
if lock.acquire(timeout=1):

   code here

   lock.release()

但我记得,至少需要ZooKeeper的三个节点。

答案 3 :(得分:0)

分布式锁定管理器Taooka http://taooka.com的TTL精度为纳秒级。但它只有Golang客户端库。