Skip to content

Commit

Permalink
comments and changelog
Browse files Browse the repository at this point in the history
  • Loading branch information
andymccurdy committed Sep 13, 2013
1 parent d570d4c commit b2fc50b
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Thanks Guillaume Viot.
* Executing an empty pipeline transaction no longer sends MULTI/EXEC to
the server. Thanks EliFinkelshteyn.
* Full Sentinel support thanks to Vitja Makarov. Thanks!
* 2.8.0
* redis-py should play better with gevent when a gevent Timeout is raised.
Thanks leifkb.
Expand Down
31 changes: 19 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,11 @@ Sentinel support
^^^^^^^^^^^^^^^^

redis-py can be used together with `Redis Sentinel <http://redis.io/topics/sentinel>`_
to discover redis nodes on the fly using service name instead of address.
First of all you need to define sentinel cluster:
to discover Redis nodes. You need to have at least one Sentinel daemon running
in order to use redis-py's Sentinel support.

Connecting redis-py to the Sentinel instance(s) is easy. You can use a
Sentinel connection to discover the master and slaves network addresses:

.. code-block:: pycon
Expand All @@ -410,8 +413,9 @@ First of all you need to define sentinel cluster:
>>> sentinel.discover_slaves('mymaster')
[('127.0.0.1', 6380)]
Then you can use sentinel instance to acquire master or slave client for the
corresponding service name:
You can also create Redis client connections from a Sentinel instnace. You can
connect to either the master (for write operations) or a slave (for read-only
operations).

.. code-block:: pycon
Expand All @@ -421,17 +425,20 @@ corresponding service name:
>>> slave.get('foo')
'bar'
master and slave objects are normal StrictRedis instances with their connection
pool bound to the sentinel instance. To establish connection with a redis server
sentinel servers will be asked for the address of 'mymaster' service. If no
server is found MasterNotFoundError or SlaveNotFoundError is raised both
exceptions are subclasses of ConnectionError.
The master and slave objects are normal StrictRedis instances with their
connection pool bound to the Sentinel instance. When a Sentinel backed client
attempts to establish a connection, it first queries the Sentinel servers to
determine an appropriate host to connect to. If no server is found,
a MasterNotFoundError or SlaveNotFoundError is raised. Both exceptions are
subclasses of ConnectionError.

For slave client it will iterate over slaves unitl usable one is found. On failure
it will use master's address.
When trying to connect to a slave client, the Sentinel connection pool will
iterate over the list of slaves until it finds one that can be connected to.
If no slaves can be connected to, a connection will be established with the
master.

See `Guidelines for Redis clients with support for Redis Sentinel
<http://redis.io/topics/sentinel-clients>`_ to learn more abour Redis Sentinel.
<http://redis.io/topics/sentinel-clients>`_ to learn more about Redis Sentinel.

Author
^^^^^^
Expand Down
4 changes: 2 additions & 2 deletions redis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ def sentinel(self, *args):
return self.execute_command('SENTINEL', *args, **{'parse': parse})

def sentinel_masters(self):
"Returns a dictionary containing known masters state."
"Returns a dictionary containing the master's state."
return self.execute_command('SENTINEL', 'masters',
parse='SENTINEL_INFO_MASTERS')

Expand All @@ -571,7 +571,7 @@ def sentinel_sentinels(self, service_name):
parse='SENTINEL_INFO')

def sentinel_get_master_addr_by_name(self, service_name):
"Returns pair (host, port) for the given ``service_name``"
"Returns a (host, port) pair for the given ``service_name``"
return self.execute_command('SENTINEL', 'get-master-addr-by-name',
service_name, parse='SENTINEL_ADDR_PORT')

Expand Down
64 changes: 36 additions & 28 deletions redis/sentinel.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class SentinelConnectionPool(ConnectionPool):
"""
Sentinel backed connection pool.
If ``check_connection`` flag is set to True SentinelManagedConnection sends
PING command right after establishing TCP connection.
If ``check_connection`` flag is set to True, SentinelManagedConnection
sends a PING command right after establishing the connection.
"""

def __init__(self, service_name, sentinel_manager, **kwargs):
Expand Down Expand Up @@ -93,7 +93,7 @@ def rotate_slaves(self):

class Sentinel(object):
"""
Redis Sentinel cluster client::
Redis Sentinel cluster client
>>> from redis.sentinel import Sentinel
>>> sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1)
Expand All @@ -103,11 +103,11 @@ class Sentinel(object):
>>> slave.get('foo')
'bar'
First required argument ``sentinels`` is a list of sentinel nodes each node
is represented by a pair (hostname, port).
``sentinels`` is a list of sentinel nodes. Each node is represented by
a pair (hostname, port).
Use ``socket_timeout`` to specify timeout for underlying sentinel clients,
it's recommended to use short timeouts.
Use ``socket_timeout`` to specify a timeout for sentinel clients.
It's recommended to use short timeouts.
Use ``min_other_sentinels`` to filter out sentinels with not enough peers.
"""
Expand All @@ -129,11 +129,11 @@ def check_master_state(self, state, service_name):

def discover_master(self, service_name):
"""
Asks sentinels for master's address corresponding to the service
labeled ``service_name``.
Asks sentinel servers for the Redis master's address corresponding
to the service labeled ``service_name``.
Returns a pair (address, port) or raises MasterNotFoundError if no
alive master is found.
master is found.
"""
for sentinel_no, sentinel in enumerate(self.sentinels):
try:
Expand All @@ -149,7 +149,7 @@ def discover_master(self, service_name):
raise MasterNotFoundError("No master found for %r" % (service_name,))

def filter_slaves(self, slaves):
"Remove slaves that are in ODOWN or SDOWN state"
"Remove slaves that are in an ODOWN or SDOWN state"
slaves_alive = []
for slave in slaves:
if slave['is_odown'] or slave['is_sdown']:
Expand All @@ -158,7 +158,7 @@ def filter_slaves(self, slaves):
return slaves_alive

def discover_slaves(self, service_name):
"Returns list of alive slaves for service ``service_name``"
"Returns a list of alive slaves for service ``service_name``"
for sentinel in self.sentinels:
try:
slaves = sentinel.sentinel_slaves(service_name)
Expand All @@ -172,18 +172,22 @@ def discover_slaves(self, service_name):
def master_for(self, service_name, redis_class=StrictRedis,
connection_pool_class=SentinelConnectionPool, **kwargs):
"""
Returns redis client instance for master of ``service_name``.
Returns a redis client instance for the ``service_name`` master.
Undercover it uses SentinelConnectionPool class to retrive master's
address each time before establishing new connection.
A SentinelConnectionPool class is used to retrive the master's
address before establishing a new connection.
NOTE: If master address change is detected all other connections from
the pool are closed.
NOTE: If the master's address has changed, any cached connections to
the old master are closed.
By default redis.StrictRedis class is used you can override this with
``redis_class`` argument. Use ``connection_pool_class`` to specify
your own connection pool class instead of SentinelConnectionPool. All
other arguments are passed directly to the SentinelConnectionPool.
By default clients will be a redis.StrictRedis instance. Specify a
different class to the ``redis_class`` argument if you desire
something different.
The ``connection_pool_class`` specifies the connection pool to use.
The SentinelConnectionPool will be used by default.
All other arguments are passed directly to the SentinelConnectionPool.
"""
kwargs['is_master'] = True
return redis_class(connection_pool=connection_pool_class(
Expand All @@ -192,15 +196,19 @@ def master_for(self, service_name, redis_class=StrictRedis,
def slave_for(self, service_name, redis_class=StrictRedis,
connection_pool_class=SentinelConnectionPool, **kwargs):
"""
Returns redis client instance for slave of ``service_name``.
Returns redis client instance for the ``service_name`` slave(s).
A SentinelConnectionPool class is used to retrive the slave's
address before establishing a new connection.
By default clients will be a redis.StrictRedis instance. Specify a
different class to the ``redis_class`` argument if you desire
something different.
Undercover it uses SentinelConnectionPool class to choose slave's
address each time before establishing new connection.
The ``connection_pool_class`` specifies the connection pool to use.
The SentinelConnectionPool will be used by default.
By default redis.StrictRedis class is used you can override this with
``redis_class`` argument. Use ``connection_pool_class`` to specify
your own connection pool class instead of SentinelConnectionPool. All
other arguments are passed directly to the SentinelConnectionPool.
All other arguments are passed directly to the SentinelConnectionPool.
"""
kwargs['is_master'] = False
return redis_class(connection_pool=connection_pool_class(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_sentinel.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, service_name='mymaster', ip='127.0.0.1', port=6379):
'is_sdown': False,
'is_odown': False,
'num-other-sentinels': 0,
}
}
self.service_name = service_name
self.slaves = []
self.nodes_down = set()
Expand Down

0 comments on commit b2fc50b

Please sign in to comment.