Redis is an open-source, in-memory key-value store known for its high performance and versatility. It operates primarily as a cache or a quick-response database, storing data in memory to enable low-latency read and write operations. Redis supports various data structures, including strings, lists, sets, and hashes, making it suitable for a wide range of applications from caching to real-time analytics. Its design allows for rapid data access and manipulation, which is crucial for applications requiring high throughput and low latency.
High Availability Architecture (Sentinel)#
To enhance reliability and availability, Redis can be paired with Redis Sentinel, a system designed to monitor Redis instances and manage failover processes. Sentinel provides automated monitoring of master and slave instances, ensuring that if the master fails, a slave can be promoted to master without manual intervention. This architecture allows applications to maintain continuous operation even in the face of server failures.Key features of Redis Sentinel include:
Monitoring: Sentinel continuously checks the health of master and slave instances.
Automatic Failover: In case of a master failure, Sentinel automatically promotes a slave to become the new master.
Configuration Management: It updates clients with the new master information, allowing them to reconnect seamlessly.
Together, Redis and Sentinel create a robust architecture that ensures high availability and resilience, making it an ideal choice for applications that require constant uptime and reliable data access.
How to Setup#
To set up Redis with Sentinel on every machine for high availability, follow these detailed steps. This guide assumes you have three Linux servers ready, each running Redis and Sentinel.
Step 1: Install Redis#
On all three servers, install Redis using the following commands:
1
2
| sudo apt-get update
sudo apt-get install redis-server
|
Server Configuration Overview#
- Server 1 (Master, 10.211.11.7) - Runs Redis as the master.
- Server 2 (Replica, 10.211.11.8) - Runs Redis as a replica of Server 1.
- Server 3 (Replica, 10.211.11.9) - Runs Redis as a replica of Server 1.
On Server 1 (Master)#
Edit the Redis configuration file located at /etc/redis/redis.conf
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # Allow external connections
bind 0.0.0.0
# Set a password for replication
requirepass your_master_password
# Enable protected mode
protected-mode no
sed -i 's/^bind 127.0.0.1/bind 0.0.0.0/' /etc/redis/redis.conf
sed -i 's/^protected-mode yes/protected-mode no/' /etc/redis/redis.conf
sed -i "s/# requirepass foobared/requirepass <your_password>/" /etc/redis/redis.conf
sed -i -E 's/^#?\s*masterauth.*/masterauth your_password_here/' /etc/redis/redis.conf
# only in replica node
sed -i -E 's/^#?\s*replicaof\s+<masterip>\s+<masterport>/replicaof new_server_ip 6379/' redis.conf
|
On Server 2 (Replica)#
Edit the Redis configuration file on Server 2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Allow external connections
bind 0.0.0.0
# Set a password for replication
requirepass your_master_password
# Enable protected mode
protected-mode no
# Configure as a replica of Server 1
replicaof <Server_1_IP> 6379
# Set the master user for replication
masterauth your_master_password
sed -i -E 's/^#?\s*masterauth.*/masterauth your_password_here/' /etc/redis/redis.conf
sed -i -E 's/^#?\s*replicaof\s+<masterip>\s+<masterport>/replicaof new_server_ip new_port/' redis.conf
|
On Server 3 (Replica)#
Repeat the same configuration as on Server 2, but point to Server 1’s IP address.
Create a Sentinel configuration file on each server. Here’s how to do it:
On All Servers (Sentinel Configuration)#
Create a file named sentinel.conf
at /etc/redis/sentinel.conf
:
1
2
3
4
5
6
7
| port 26379
bind 0.0.0.0
sentinel monitor mymaster <Server_1_IP> 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel auth-pass mymaster your_master_password
|
Make sure to replace <Server_1_IP>
with the actual IP address of your master server.
Change permission of file to redis
user
1
| chown redis:redis /etc/redis/sentinel.conf
|
Step 4: Start Redis and Sentinel Services#
Start Sentinel Instances#
You can start the Sentinel service using the following command on each server:
1
| redis-server /etc/redis/sentinel.conf --sentinel
|
To run this command in the background, you may want to create a systemd service for Sentinel:
Create a new service file at /etc/systemd/system/redis-sentinel.service
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| [Unit]
Description=Redis Sentinel
After=network.target
[Service]
User=redis
Group=redis
Type=simple
ExecStart=/usr/bin/redis-server /etc/redis/sentinel.conf --sentinel
ExecStop=/usr/bin/redis-cli -p 26379 shutdown
Restart=always
[Install]
WantedBy=multi-user.target
|
Then enable and start the Sentinel service:
1
2
3
| sudo systemctl daemon-reload
sudo systemctl enable redis-sentinel.service
sudo systemctl start redis-sentinel.service
|
Start Redis Instances#
On each server, start the Redis service:
1
2
| sudo systemctl restart redis-server
sudo systemctl enable redis-server
|
Step 5: Verify Setup#
To check if everything is working correctly, connect to one of the Sentinels:
1
| redis-cli -p 26379 SENTINEL masters
|
This command should return information about the monitored master instance.
Redis client#
Sender#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| import redis
from redis.sentinel import Sentinel
import time
# Define the Sentinel connection details
sentinels = [('10.211.11.7', 26379), ('10.211.11.8', 26379), ('10.211.11.9', 26379)]
password = 'S7WubxPy8ZoPhuTS5bgttw'
def get_master():
"""Get the current master Redis instance from Sentinel."""
while True:
try:
sentinel = Sentinel(sentinels, socket_timeout=0.1)
master = sentinel.master_for('mymaster', password=password)
return master
except (redis.ConnectionError, redis.TimeoutError) as e:
print(f"Error connecting to Sentinel: {e}. Retrying in 5 seconds...")
time.sleep(5) # Wait before retrying
def main():
"""Main function to increment a value in Redis continuously."""
while True: # Run indefinitely until stopped
master = get_master() # Get the master instance
# Log the master node's address
master_address = master.connection_pool.get_connection('SET').host
master_port = master.connection_pool.get_connection('SET').port
print(f"Connected to Redis node at {master_address}:{master_port}")
value = 0 # Initialize the value for INCR
while True: # Continuously attempt to set the value
try:
value += 1 # Increment the value
master.set('INCR', value) # Store the incremented value in Redis under 'INCR'
print(f'Set INCR = {value} in Redis node at {master_address}:{master_port}') # Print confirmation with node info
time.sleep(1) # Sleep for 1 second before next increment
except (redis.ConnectionError, redis.TimeoutError) as e:
print(f"Connection error occurred: {e}. Re-fetching master...")
break # Break to re-fetch master on connection error
# Handle re-fetching of the master after an error
while True:
try:
master = get_master() # Attempt to get a new master
master_address = master.connection_pool.get_connection('SET').host
master_port = master.connection_pool.get_connection('SET').port
print(f"Connected to new Redis node at {master_address}:{master_port}")
break # Exit this loop if successful
except Exception as e:
print(f"Failed to connect to new master: {e}. Retrying in 5 seconds...")
time.sleep(5) # Wait before retrying
if __name__ == "__main__":
main()
|
Receive#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
import redis
from redis.sentinel import Sentinel
import time
# Define the Sentinel connection details
sentinels = [('10.211.11.7', 26379), ('10.211.11.8', 26379), ('10.211.11.9', 26379)]
password = 'S7WubxPy8ZoPhuTS5bgttw'
def get_master():
"""Get the current master Redis instance from Sentinel."""
while True:
for sentinel_address in sentinels:
try:
sentinel = Sentinel([sentinel_address], socket_timeout=0.1)
master = sentinel.master_for('mymaster', password=password)
return master
except (redis.ConnectionError, redis.TimeoutError) as e:
print(f"Error connecting to Sentinel at {sentinel_address}: {e}. Trying next sentinel...")
time.sleep(1) # Short wait before trying next sentinel
print("All sentinels are unreachable. Retrying in 5 seconds...")
time.sleep(5) # Wait before retrying all sentinels
def main():
while True: # Run indefinitely until stopped
master = get_master() # Get the master instance
# Get the master node's address for logging
master_address = master.connection_pool.get_connection('GET').host
master_port = master.connection_pool.get_connection('GET').port
print(f"Connected to Redis node at {master_address}:{master_port}")
while True: # Run indefinitely until stopped
try:
# Fetch the value of the 'INCR' key from Redis
value = master.get('INCR')
# Decode the value if it is not None
if value is not None:
value = int(value)
print(f'Retrieved INCR = {value} from Redis node at {master_address}:{master_port}')
time.sleep(1) # Sleep for 1 second before the next iteration
except (redis.ConnectionError, redis.TimeoutError) as e:
print(f"Connection error occurred: {e}. Re-fetching master...")
break # Break to re-fetch master on connection error
# Handle re-fetching of the master after an error
while True:
try:
master = get_master() # Attempt to get a new master
master_address = master.connection_pool.get_connection('GET').host
master_port = master.connection_pool.get_connection('GET').port
print(f"Connected to new Redis node at {master_address}:{master_port}")
break # Exit this loop if successful
except Exception as e:
print(f"Failed to connect to new master: {e}. Retrying all sentinels in 5 seconds...")
time.sleep(5) # Wait before retrying
if __name__ == "__main__":
main()
|
Conclusion#
With this setup, each server runs both a Redis instance and a Sentinel instance, providing high availability through monitoring and automatic failover capabilities. This configuration allows for redundancy and ensures that if one server goes down, others can take over seamlessly. Always ensure that your passwords are strong and consider additional security measures based on your environment’s needs.
Citations: