Initial implementation of the hash client
This commit is contained in:
@@ -7,12 +7,29 @@ Basic Usage
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from pymemcache.client import Client
|
from pymemcache.client.base import Client
|
||||||
|
|
||||||
client = Client(('localhost', 11211))
|
client = Client(('localhost', 11211))
|
||||||
client.set('some_key', 'some_value')
|
client.set('some_key', 'some_value')
|
||||||
result = client.get('some_key')
|
result = client.get('some_key')
|
||||||
|
|
||||||
|
Using a memcached cluster
|
||||||
|
-------------------------
|
||||||
|
This will use a consistent hashing algorithm to choose which server to
|
||||||
|
set/get the values from. It will also automatically rebalance depending
|
||||||
|
on if a server goes down.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pymemcache.client.hash import HashClient
|
||||||
|
|
||||||
|
client = HashClient([
|
||||||
|
('127.0.0.1', 11211),
|
||||||
|
('127.0.0.1', 11212)
|
||||||
|
])
|
||||||
|
client.set('some_key', 'some value')
|
||||||
|
result = client.get('some_key')
|
||||||
|
|
||||||
|
|
||||||
Serialization
|
Serialization
|
||||||
--------------
|
--------------
|
||||||
@@ -20,7 +37,7 @@ Serialization
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from pymemcache.client import Client
|
from pymemcache.client.base import Client
|
||||||
|
|
||||||
def json_serializer(key, value):
|
def json_serializer(key, value):
|
||||||
if type(value) == str:
|
if type(value) == str:
|
||||||
|
|||||||
0
pymemcache/client/__init__.py
Normal file
0
pymemcache/client/__init__.py
Normal file
@@ -20,6 +20,16 @@ import six
|
|||||||
|
|
||||||
from pymemcache import pool
|
from pymemcache import pool
|
||||||
|
|
||||||
|
from pymemcache.exceptions import (
|
||||||
|
MemcacheError,
|
||||||
|
MemcacheClientError,
|
||||||
|
MemcacheUnknownCommandError,
|
||||||
|
MemcacheIllegalInputError,
|
||||||
|
MemcacheServerError,
|
||||||
|
MemcacheUnknownError,
|
||||||
|
MemcacheUnexpectedCloseError
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
RECV_SIZE = 4096
|
RECV_SIZE = 4096
|
||||||
VALID_STORE_RESULTS = {
|
VALID_STORE_RESULTS = {
|
||||||
@@ -56,49 +66,6 @@ STAT_TYPES = {
|
|||||||
b'slab_automove': lambda value: int(value) != 0,
|
b'slab_automove': lambda value: int(value) != 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MemcacheError(Exception):
|
|
||||||
"Base exception class"
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MemcacheClientError(MemcacheError):
|
|
||||||
"""Raised when memcached fails to parse the arguments to a request, likely
|
|
||||||
due to a malformed key and/or value, a bug in this library, or a version
|
|
||||||
mismatch with memcached."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MemcacheUnknownCommandError(MemcacheClientError):
|
|
||||||
"""Raised when memcached fails to parse a request, likely due to a bug in
|
|
||||||
this library or a version mismatch with memcached."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MemcacheIllegalInputError(MemcacheClientError):
|
|
||||||
"""Raised when a key or value is not legal for Memcache (see the class docs
|
|
||||||
for Client for more details)."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MemcacheServerError(MemcacheError):
|
|
||||||
"""Raised when memcached reports a failure while processing a request,
|
|
||||||
likely due to a bug or transient issue in memcached."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MemcacheUnknownError(MemcacheError):
|
|
||||||
"""Raised when this library receives a response from memcached that it
|
|
||||||
cannot parse, likely due to a bug in this library or a version mismatch
|
|
||||||
with memcached."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MemcacheUnexpectedCloseError(MemcacheServerError):
|
|
||||||
"Raised when the connection with memcached closes unexpectedly."
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Common helper functions.
|
# Common helper functions.
|
||||||
|
|
||||||
def _check_key(key, key_prefix=b''):
|
def _check_key(key, key_prefix=b''):
|
||||||
@@ -131,7 +98,7 @@ class Client(object):
|
|||||||
Values must have a __str__() method to convert themselves to a byte
|
Values must have a __str__() method to convert themselves to a byte
|
||||||
string. Unicode objects can be a problem since str() on a Unicode object
|
string. Unicode objects can be a problem since str() on a Unicode object
|
||||||
will attempt to encode it as ASCII (which will fail if the value contains
|
will attempt to encode it as ASCII (which will fail if the value contains
|
||||||
code points larger than U+127). You can fix this will a serializer or by
|
code points larger than U+127). You can fix this with a serializer or by
|
||||||
just calling encode on the string (using UTF-8, for instance).
|
just calling encode on the string (using UTF-8, for instance).
|
||||||
|
|
||||||
If you intend to use anything but str as a value, it is a good idea to use
|
If you intend to use anything but str as a value, it is a good idea to use
|
||||||
95
pymemcache/client/hash.py
Normal file
95
pymemcache/client/hash.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import socket
|
||||||
|
import zlib
|
||||||
|
from pymemcache.client.base import Client, PooledClient
|
||||||
|
from clandestined import RendezvousHash as RH
|
||||||
|
|
||||||
|
|
||||||
|
class RendezvousHash(RH):
|
||||||
|
def get_node(self, key):
|
||||||
|
return self.find_node(key)
|
||||||
|
|
||||||
|
|
||||||
|
class HashClient(object):
|
||||||
|
"""
|
||||||
|
A client for communicating with a cluster of memcached servers
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
servers,
|
||||||
|
hasher=None,
|
||||||
|
serializer=None,
|
||||||
|
connect_timeout=None,
|
||||||
|
timeout=None,
|
||||||
|
no_delay=False,
|
||||||
|
ignore_exc=False,
|
||||||
|
socket_module=socket,
|
||||||
|
key_prefix=b'',
|
||||||
|
max_pool_size=None,
|
||||||
|
lock_generator=None,
|
||||||
|
use_pooling=False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
servers: list(tuple(hostname, port))
|
||||||
|
serializer: optional class with ``serialize`` and ``deserialize``
|
||||||
|
functions.
|
||||||
|
hasher: optional class three functions ``get_node``, ``add_node``, and
|
||||||
|
``remove_node``
|
||||||
|
|
||||||
|
defaults to crc32 hash.
|
||||||
|
use_pooling: use py:class:`.PooledClient` as the default underlying
|
||||||
|
class. ``max_pool_size`` and ``lock_generator`` can
|
||||||
|
be used with this. default: False
|
||||||
|
|
||||||
|
Further arguments are interpreted as for :py:class:`.Client`
|
||||||
|
constructor.
|
||||||
|
"""
|
||||||
|
self.clients = {}
|
||||||
|
|
||||||
|
if hasher is None:
|
||||||
|
self.hasher = RendezvousHash()
|
||||||
|
|
||||||
|
for server, port in servers:
|
||||||
|
key = '%s:%s' % (server, port)
|
||||||
|
kwargs = {
|
||||||
|
'connect_timeout': connect_timeout,
|
||||||
|
'timeout': timeout,
|
||||||
|
'no_delay': no_delay,
|
||||||
|
'ignore_exc': ignore_exc,
|
||||||
|
'socket_module': socket_module,
|
||||||
|
'key_prefix': key_prefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
if serializer is not None:
|
||||||
|
kwargs['serializer'] = serializer.serialize
|
||||||
|
kwargs['deserializer'] = serializer.deserialize
|
||||||
|
|
||||||
|
if use_pooling is True:
|
||||||
|
kwargs.update({
|
||||||
|
'max_pool_size': max_pool_size,
|
||||||
|
'lock_generator': lock_generator
|
||||||
|
})
|
||||||
|
|
||||||
|
client = PooledClient(
|
||||||
|
(server, port),
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
client = Client((server, port))
|
||||||
|
|
||||||
|
self.clients[key] = client
|
||||||
|
self.hasher.add_node(key)
|
||||||
|
|
||||||
|
def _get_client(self, key):
|
||||||
|
server = self.hasher.get_node(key)
|
||||||
|
print('got server %s' % server)
|
||||||
|
client = self.clients[server]
|
||||||
|
return client
|
||||||
|
|
||||||
|
def set(self, key, *args, **kwargs):
|
||||||
|
client = self._get_client(key)
|
||||||
|
return client.set(key, *args, **kwargs)
|
||||||
|
|
||||||
|
def get(self, key, *args, **kwargs):
|
||||||
|
client = self._get_client(key)
|
||||||
|
return client.get(key, *args, **kwargs)
|
||||||
40
pymemcache/exceptions.py
Normal file
40
pymemcache/exceptions.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
class MemcacheError(Exception):
|
||||||
|
"Base exception class"
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MemcacheClientError(MemcacheError):
|
||||||
|
"""Raised when memcached fails to parse the arguments to a request, likely
|
||||||
|
due to a malformed key and/or value, a bug in this library, or a version
|
||||||
|
mismatch with memcached."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MemcacheUnknownCommandError(MemcacheClientError):
|
||||||
|
"""Raised when memcached fails to parse a request, likely due to a bug in
|
||||||
|
this library or a version mismatch with memcached."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MemcacheIllegalInputError(MemcacheClientError):
|
||||||
|
"""Raised when a key or value is not legal for Memcache (see the class docs
|
||||||
|
for Client for more details)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MemcacheServerError(MemcacheError):
|
||||||
|
"""Raised when memcached reports a failure while processing a request,
|
||||||
|
likely due to a bug or transient issue in memcached."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MemcacheUnknownError(MemcacheError):
|
||||||
|
"""Raised when this library receives a response from memcached that it
|
||||||
|
cannot parse, likely due to a bug in this library or a version mismatch
|
||||||
|
with memcached."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MemcacheUnexpectedCloseError(MemcacheServerError):
|
||||||
|
"Raised when the connection with memcached closes unexpectedly."
|
||||||
|
pass
|
||||||
@@ -19,10 +19,15 @@ import socket
|
|||||||
import unittest
|
import unittest
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pymemcache.client import PooledClient
|
from pymemcache.client.base import PooledClient, Client
|
||||||
from pymemcache.client import Client, MemcacheUnknownCommandError
|
from pymemcache.exceptions import (
|
||||||
from pymemcache.client import MemcacheClientError, MemcacheServerError
|
MemcacheClientError,
|
||||||
from pymemcache.client import MemcacheUnknownError, MemcacheIllegalInputError
|
MemcacheServerError,
|
||||||
|
MemcacheUnknownCommandError,
|
||||||
|
MemcacheUnknownError,
|
||||||
|
MemcacheIllegalInputError
|
||||||
|
)
|
||||||
|
|
||||||
from pymemcache import pool
|
from pymemcache import pool
|
||||||
from pymemcache.test.utils import MockMemcacheClient
|
from pymemcache.test.utils import MockMemcacheClient
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ import json
|
|||||||
import pytest
|
import pytest
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from pymemcache.client import (Client, MemcacheClientError)
|
from pymemcache.client.base import Client
|
||||||
from pymemcache.client import MemcacheIllegalInputError
|
from pymemcache.exceptions import (
|
||||||
|
MemcacheIllegalInputError,
|
||||||
|
MemcacheClientError
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration()
|
@pytest.mark.integration()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import time
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from pymemcache.client import MemcacheIllegalInputError
|
from pymemcache.exceptions import MemcacheIllegalInputError
|
||||||
|
|
||||||
|
|
||||||
class MockMemcacheClient(object):
|
class MockMemcacheClient(object):
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -10,7 +10,7 @@ setup(
|
|||||||
author='Charles Gordon',
|
author='Charles Gordon',
|
||||||
author_email='charles@pinterest.com',
|
author_email='charles@pinterest.com',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
install_requires=['six'],
|
install_requires=['six', 'clandestined'],
|
||||||
description='A comprehensive, fast, pure Python memcached client',
|
description='A comprehensive, fast, pure Python memcached client',
|
||||||
long_description=open('README.md').read(),
|
long_description=open('README.md').read(),
|
||||||
license='Apache License 2.0',
|
license='Apache License 2.0',
|
||||||
|
|||||||
Reference in New Issue
Block a user