question: sentinel integration best practice
Is there a detailed document or sample about the Redis Sentinel usage?
- How to connect to the Redis Sentinel (How to specify the endpoint?)
- How to handle the Redis node failure, would it be handled automatically?
- How to handle Primary/Replica(Master/Slave) change?
- Would there be any differences between write commands client and read-only client?
- (more I may miss)...
Based on My Own Experience
In our team, we wanted to use Redis Stream as a broker, so it was important for us to know in case of failover, does the application handle it and uses new master node or not. Our Redis was configured in Sentinel mode, so we tested it to confirm whether our setup supports Sentinel and whether it switches to the new master node after failover.
Here’s what we found:
IConnectionMultiplexer handles failover and can connect to the new master. In my scenario, each time I queried Redis, I got a new database from the connection multiplexer. So it does handle failover. You can try this yourself easily with a Sentinel Docker Compose setup on Linux (I can share the Docker Compose file and configuration if you need them).
How to connect to Sentinel?
Once you set ServiceName in your configuration options or in your connection string, the config switches to Sentinel mode. This is based on the documentation (and also my experience): https://stackexchange.github.io/StackExchange.Redis/Configuration
How to handle Redis node failure — is it automatic? Yes, it is handled automatically. During the failure you will notice issues, but after failover everything works fine.
How to handle Primary/Replica (Master/Slave) change? Sentinel and IConnectionMultiplexer handle this failover. You just need to get a new database instance from the connection multiplexer.
Would there be any differences between write commands client and read-only client? I don’t know.
Other friends can correct me if they have had different experiences.
This is how I configured Redis Sentinel in my application:
My Options class::
public class RedisConnectionOptions
{
public List<string> Endpoints { get; set; } = null!;
/// <summary>
/// Redis service name. Required for Redis Sentinel configuration but not used in standalone mode. Set to null for standalone Redis.
/// </summary>
public string? ServiceName { get; set; }
public string? User { get; set; }
public string? Password { get; set; }
public int DefaultDatabase { get; set; }
public bool AbortOnConnectFail { get; set; } = false;
public int ConnectTimeout { get; set; } = 5_000;
public int SyncTimeout { get; set; } = 5_000;
public int AsyncTimeout { get; set; } = 5_000;
}
How I configured it in DI:
private static void AddRedisConnection(IServiceCollection services, Action<RedisConnectionOptions> connectionOptionsAction)
{
services.TryAddSingleton<IConnectionMultiplexer>(serviceProvider =>
{
var connectionOptions = new RedisConnectionOptions();
connectionOptionsAction.Invoke(connectionOptions);
var logger = serviceProvider.GetRequiredService<ILogger<IConnectionMultiplexer>>();
var configurationOptions = new ConfigurationOptions
{
User = connectionOptions.User,
Password = connectionOptions.Password,
ServiceName = connectionOptions.ServiceName,
DefaultDatabase = connectionOptions.DefaultDatabase,
AbortOnConnectFail = connectionOptions.AbortOnConnectFail,
ConnectTimeout = connectionOptions.ConnectTimeout,
SyncTimeout = connectionOptions.SyncTimeout,
AsyncTimeout = connectionOptions.AsyncTimeout
};
foreach (string endpoint in connectionOptions.Endpoints)
{
configurationOptions.EndPoints.Add(endpoint);
}
var connection = ConnectionMultiplexer.Connect(configurationOptions);
ConfigureConnectionLogging(connection, logger);
return connection;
});
}
Configure Logging:
private static void ConfigureConnectionLogging(IConnectionMultiplexer connectionMultiplexer, ILogger logger)
{
connectionMultiplexer.ConnectionFailed += (sender, args) =>
{
logger.LogError(
"Redis connection failed. Endpoint: {Endpoint}, Failure: {FailureType}, Exception: {Exception}",
args.EndPoint, args.FailureType, args.Exception?.Message);
};
connectionMultiplexer.ConnectionRestored += (sender, args) =>
{
logger.LogInformation("Redis connection restored. Endpoint: {Endpoint}, Failure: {FailureType}",
args.EndPoint, args.FailureType);
};
connectionMultiplexer.ConfigurationChanged += (sender, args) =>
{
logger.LogInformation("Redis configuration changed. Endpoint: {Endpoint}",
args.EndPoint);
};
connectionMultiplexer.ConfigurationChangedBroadcast += (sender, args) =>
{
logger.LogInformation("Redis configuration change broadcast received. Endpoint: {Endpoint}",
args.EndPoint);
};
connectionMultiplexer.ErrorMessage += (sender, args) =>
{
logger.LogWarning("Redis error message received. Endpoint: {Endpoint}, Message: {Message}",
args.EndPoint, args.Message);
};
connectionMultiplexer.InternalError += (sender, args) =>
{
logger.LogError(
"Redis internal error occurred. Endpoint: {Endpoint}, Origin: {Origin}, Exception: {Exception}",
args.EndPoint, args.Origin, args.Exception?.Message);
};
}
@ejlalzadeh thanks for sharing, very helpful
Will the SE handle the load balence about read command?