Skip to content

Commit 977c4a5

Browse files
Add support of redis clusters (#236)
* Add support of redis clusters (#1) Add Redis cluster support as session management backend
1 parent c089668 commit 977c4a5

File tree

4 files changed

+116
-1
lines changed

4 files changed

+116
-1
lines changed

beaker/cache.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import beaker.ext.google as google
2323
import beaker.ext.mongodb as mongodb
2424
import beaker.ext.redisnm as redisnm
25+
import beaker.ext.redisclusternm as redisclusternm
2526
from functools import wraps
2627

2728
# Initialize the cache region dict
@@ -126,7 +127,8 @@ def _init(self):
126127
'ext:sqla': sqla.SqlaNamespaceManager,
127128
'ext:google': google.GoogleNamespaceManager,
128129
'ext:mongodb': mongodb.MongoNamespaceManager,
129-
'ext:redis': redisnm.RedisNamespaceManager
130+
'ext:redis': redisnm.RedisNamespaceManager,
131+
'ext:rediscluster': redisclusternm.RedisClusterNamespaceManager,
130132
})
131133

132134

beaker/docs/modules/rediscluster.rst

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
:mod:`beaker.ext.redisclusternm` -- Redis cluster NameSpace Manager and Synchronizer
2+
==============================================================================
3+
4+
.. automodule:: beaker.ext.redisclusternm
5+
6+
Module Contents
7+
---------------
8+
9+
.. autoclass:: RedisClusterNamespaceManager
10+
.. autoclass:: RedisClusterSynchronizer

beaker/ext/redisclusternm.py

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import os
2+
import threading
3+
import time
4+
import pickle
5+
6+
from beaker.container import NamespaceManager
7+
8+
try:
9+
import redis
10+
except ImportError:
11+
redis = None
12+
13+
from beaker.ext.redisnm import RedisNamespaceManager, RedisSynchronizer
14+
from beaker._compat import string_type
15+
16+
17+
class RedisClusterNamespaceManager(RedisNamespaceManager):
18+
"""Provides the :class:`.NamespaceManager` API over Redis cluster.
19+
20+
Provided ``urls`` can be both multiple redis connection strings separated by a comma or
21+
an already existing RedisCluster instance.
22+
23+
Unlike a StrictRedis connection string, a RedisCluster one does not support
24+
database indicators, it is zero by default.
25+
26+
Example: `redis://node-1:7001,redis://node-2:7002`
27+
28+
Additional options can be passed in kwargs (e.g. `username="redis", password="secure_password"`).
29+
30+
The data will be stored into redis keys, with their name
31+
starting with ``beaker_cache:``.
32+
"""
33+
34+
def __init__(self, namespace, urls, timeout=None, **kwargs):
35+
super(RedisNamespaceManager, self).__init__(namespace)
36+
self.lock_dir = None # Redis uses redis itself for locking.
37+
self.timeout = timeout
38+
self.nodes = []
39+
self.options = kwargs
40+
41+
if redis is None:
42+
raise RuntimeError('redis is not available')
43+
44+
if isinstance(urls, string_type):
45+
for url in urls.split(','):
46+
url_options = redis.connection.parse_url(url)
47+
if 'db' in url_options:
48+
raise redis.exceptions.RedisClusterException(
49+
"A ``db`` querystring option can only be 0 in cluster mode"
50+
)
51+
self.nodes.append(redis.cluster.ClusterNode(
52+
host=url_options.get('host'),
53+
port=url_options.get('port')
54+
))
55+
self.client = RedisClusterNamespaceManager.clients.get(
56+
urls, redis.cluster.RedisCluster, startup_nodes=self.nodes, **kwargs
57+
)
58+
else:
59+
self.client = urls
60+
61+
def get_creation_lock(self, key):
62+
return RedisClusterSynchronizer(self._format_key(key), self.client, self.nodes, **self.options)
63+
64+
65+
class RedisClusterSynchronizer(RedisSynchronizer):
66+
"""Synchronizer based on redis cluster.
67+
68+
Provided ``urls`` can be both multiple redis connection strings separated by a comma or
69+
an already existing RedisCluster instance.
70+
71+
Unlike a StrictRedis connection string, a RedisCluster one does not support
72+
database indicators, it is zero by default.
73+
74+
Example: ``redis://node-1:7001,redis://node-2:7002,
75+
76+
This Synchronizer only supports 1 reader or 1 writer at time, not concurrent readers.
77+
"""
78+
79+
def __init__(self, identifier, urls, nodes=None, **kwargs):
80+
super(RedisSynchronizer, self).__init__()
81+
self.identifier = 'beaker_lock:%s' % identifier
82+
if isinstance(urls, string_type):
83+
self.client = RedisClusterNamespaceManager.clients.get(
84+
urls, redis.cluster.RedisCluster, startup_nodes=nodes, **kwargs
85+
)
86+
else:
87+
self.client = urls
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from beaker.cache import Cache
2+
from . import base
3+
4+
5+
class TestRedis(base.CacheManagerBaseTests):
6+
CACHE_ARGS = {
7+
'type': 'ext:rediscluster',
8+
'url': 'redis://localhost:6379'
9+
}
10+
11+
def test_client_reuse(self):
12+
cache1 = Cache('test1', **self.CACHE_ARGS)
13+
cli1 = cache1.namespace.client
14+
cache2 = Cache('test2', **self.CACHE_ARGS)
15+
cli2 = cache2.namespace.client
16+
self.assertTrue(cli1 is cli2)

0 commit comments

Comments
 (0)