4

python-memcached源码小窥

 2 years ago
source link: https://www.hi-roy.com/posts/python-memcached%E6%BA%90%E7%A0%81%E5%B0%8F%E7%AA%A5/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

python-memcached源码小窥

2015-08-13

以前经常使用python-memcached对内存进行操作,但应用都比较简单,最近需要有一个分布式缓存系统于是看了看关于twemproxy 以及 mcrouter 这两款分别由twitter和facebook开源的软件文档。这2个软件都能容易的扩展缓存节点以及自动删除问题节点,并且提供不同的算法把数据缓存到各个节点中。这时候我想起来使用python-memcached的时候,也可以使用多个节点,并且某个节点挂掉后并不影响整个缓存程序的使用,那么它是怎么将数据分配到不同的节点呢?以及怎么处理的故障节点呢? python-memcached的源码只有一个文件,不管是get或者set,取得服务节点Ip的函数如下:

_SERVER_RETRIES = 10  # how many times to try finding a free server.
def _get_server(self, key):
    print(key)
    if isinstance(key, tuple):
        serverhash, key = key
    else:
        serverhash = serverHashFunction(key)
    if not self.buckets:
        return None, None
    for i in range(Client._SERVER_RETRIES):
        print(self.buckets)
        print (serverhash)
        print(serverhash % len(self.buckets))
        server = self.buckets[serverhash % len(self.buckets)]
        if server.connect():
            print("(using server %s)" % server,)
            return server, key
        serverhash = str(serverhash) + str(i)
        if isinstance(serverhash, six.text_type):
            serverhash = serverhash.encode('ascii')
        serverhash = serverHashFunction(serverhash)
    return None, None

其中print语句是我添加的,为了更清晰的看出原因。首先我们创建一个对象并且设置一个值:

In [4]: mc = meme.Client(['127.0.0.1:11211','192.168.0.202:11211','192.168.0.203:11211'])
In [5]: mc.set("asdf","asdf")
asdf
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
20777
2
(using server inet:192.168.0.203:11211)
Out[5]: True

可以看到,这个"asdf"被放到了节点3中。再来多几个数据:

In [23]: mc.set("a","test")
a
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
26807
2
(using server inet:192.168.0.203:11211)
Out[23]: True
In [24]: mc.set("b","test")
b
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
29118
0
(using server inet:127.0.0.1:11211)
Out[24]: True
In [25]: mc.set("c","test")
c
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
1721
2
(using server inet:192.168.0.203:11211)
Out[25]: True
In [26]: mc.set("d","test")
d
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
6365
2
(using server inet:192.168.0.203:11211)
Out[26]: True
In [27]: mc.set("e","test")
e
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
28634
2
(using server inet:192.168.0.203:11211)
Out[27]: True
In [28]: mc.set("f","test")
f
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
30419
2
(using server inet:192.168.0.203:11211)
Out[28]: True
In [29]: mc.set("g","test")
g
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
468
0
(using server inet:127.0.0.1:11211)
Out[29]: True
In [30]: mc.set("h","test")
h
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
4459
1
(using server inet:192.168.0.202:11211)
Out[30]: True

好像并没有什么规律,但有心的读者肯定发现了,程序的流程就是根据key算出一个hash值,再根据这个hash值对总节点数进行取余数。换言之,数据被存储在哪个节点完全是由key来决定的!在某些极端情况下,可能出现所有的数据都存储在同一个节点的情况。

用于hash的函数如下:

def cmemcache_hash(key):
    return (
        (((binascii.crc32(key) & 0xffffffff) >> 16) & 0x7fff) or 1)
serverHashFunction = cmemcache_hash

首先对key进行crc32操作,这里注意python2.x版本中crc计算后得到的是有符号整数(- 2^31—-2^31-1),所以需要使用位操作& 0xffffffff将其转成无符号整数,然后在向右位移16位截取高16位,再与0x7fff进行位操作将值变成正数。(比较疑惑,不知道这么做后key转换出来的值真的不会重复吗?算法渣渣啊….)

那么,如果一台节点挂了,似乎并没有影响整个缓存的使用啊?比如上面key=h的数据应该存放在第二个节点上,现在关闭第二个节点(192.168.0.202),再取值:

In [35]: mc.get('h')
h
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
4459
1
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
26423
2
(using server inet:192.168.0.203:11211)

可以看出,就算某个节点挂了,对于mc来说,backets还是有3个节点的,它并不会把失效节点从“池子”中移除。不过节点2链接不上,于是程序把上次得到的hash值于循环次数进行拼接后再hash,看其能否得到一个可以链接的节点。这次结果是得到了节点3并且可以链接,于是程序去节点3寻找key=h的值,但第三个节点并没有,所以返回none。换言之,即使节点1有key=h的值,程序也不会理会的。 那么,我再设置一个key=h的数据试试:

In [36]: mc.set("h","test")
h
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
4459
1
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
26423
2
(using server inet:192.168.0.203:11211)

结果不出所料,程序还是先去找节点2,发现链接不上就继续hash,直到找到一个能链接的节点为止。极端情况下就循环10次都没碰到可用节点的话,程序就认为所有节点都挂了。

这种逻辑下,假设程序经过2次hash后决定把数据存在节点3后,节点2恢复正常,再进行get操作会发现数据还是取不到的。

python-memcached无法动态进行节点的扩展或者删除,简单的应用或者只有一个缓存节点时,python-memcached还是很给力的,但如果需要的是一个分布式缓存集群的话,还是使用上面提到的那2个程序更高效、灵活。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK