I'm running a licensed version of ServiceStack and trying to get a sentinel cluster setup on Google Cloud Compute.
The cluster is basically GCE's click-to-deploy redis solution - 3 servers. Here is the code i'm using to initialize...
var hosts = Settings.Redis.Host.Split(';');
var sentinel = new ServiceStack.Redis.RedisSentinel(hosts, "master");
redis = sentinel.Setup();
container.Register<IRedisClientsManager>(redis);
container.Register<ICacheClient>(redis.GetCacheClient());
The client works fine - but once i shut down one of the redis instances everything craps the bed. The client complains about not being able to connect to the missing instance. Additionally, even when i bring the instance back up - it is in READ ONLY mode, so everything still fails. There doesn't seem to be a way to recover once you are in this state...
Am i doing something wrong? Is there some reason that RedisSentinal client doesn't figure out who the new master is? I feed it all 3 host IP addresses...
You should only be supplying the host of the Redis Sentinel Server to RedisSentinel as it gets the active list of other master/slave redis servers from the Sentinel host.
Some changes to RedisSentinel were recently added in the latest v4.0.37 that's now available on MyGet which includes extra logging and callbacks of Redis Sentinel events. The new v4.0.37 API looks like:
var sentinel = new RedisSentinel(sentinelHost, masterName);
Starting the RedisSentinel will connect to the Sentinel Host and return a pre-configured RedisClientManager (i.e. redis connection pool) with the active
var redisManager = sentinel.Start();
Which you can then register in the IOC with:
container.Register<IRedisClientsManager>(redisManager);
The RedisSentinel should then listen to master/slave changes from the Sentinel hosts and failover the redisManager accordingly. The existing connections in the pool are then disposed and replaced with a new pool for the newly configured hosts. Any active connections outside of the pool they'll throw connection exceptions if used again, the next time the RedisClient is retrieved from the pool it will be configured with the new hosts.
Callbacks and Logging
Here's an example of how you can use the new callbacks to introspect the RedisServer events:
var sentinel = new RedisSentinel(sentinelHost, masterName)
{
OnFailover = manager =>
{
"Redis Managers were Failed Over to new hosts".Print();
},
OnWorkerError = ex =>
{
"Worker error: {0}".Print(ex);
},
OnSentinelMessageReceived = (channel, msg) =>
{
"Received '{0}' on channel '{1}' from Sentinel".Print(channel, msg);
},
};
Logging of these events can also be enabled by configuring Logging in ServiceStack:
LogManager.LogFactory = new ConsoleLogFactory(debugEnabled:false);
There's also an additional explicit FailoverToSentinelHosts() that can be used to force RedisSentinel to re-lookup and failover to the latest master/slave hosts, e.g:
var sentinelInfo = sentinel.FailoverToSentinelHosts();
The new hosts are available in the returned sentinelInfo:
"Failed over to read/write: {0}, read-only: {1}".Print(
sentinelInfo.RedisMasters, sentinelInfo.RedisSlaves);
Related
Recently i work with redis and using jedis. In redis version 6, we can set required password mode for sentinels. I have 3 working sentinels, can connect and authen throught redis-cli. But using jedis, i can't connect to the sentinel with this warning:
Cannot get master address from sentinel running # 127.0.0.1:26379.
Reason: redis.clients.jedis.exceptions.JedisDataException: NOAUTH
Authentication required.. Trying next one.
Cannot get master address from sentinel running # 127.0.0.1:36379.
Reason: redis.clients.jedis.exceptions.JedisDataException: NOAUTH
Authentication required.. Trying next one.
Cannot get master address from sentinel running # 127.0.0.1:16379.
Reason: redis.clients.jedis.exceptions.JedisDataException: NOAUTH
Authentication required.. Trying next one.
And this error:
All sentinels down, cannot determine where is mymaster master is
running...
Here is my code:
GenericObjectPoolConfig pc = new GenericObjectPoolConfig();
pc.setMinIdle(2);
pc.setMaxIdle(5);
pc.setMaxTotal(5);
JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, pc, 1000*10, PASSWORD);
Jedis jedis = null;
try {
printer("Fetching connection from pool");
jedis = pool.getResource();
printer("Authenticating...");
jedis.auth(PASSWORD);
printer("auth complete...");
Socket socket = jedis.getClient().getSocket();
printer("Connected to " + socket.getRemoteSocketAddress());
printer("Writing...");
jedis.set("java-key-999", "java-value-999");
printer("Reading...");
printer(jedis.get("java-key-999"));
} catch (JedisException e) {
printer("Connection error of some sort!");
printer(e.getMessage());
Thread.sleep(2 * 1000);
} finally {
if (jedis != null) {
jedis.close();
}
}
Please help, thank you for your reading support <3
Your sentinel nodes are password protected. You would have to provide AUTH parameters to connect to sentinel nodes.
Update:
Password you're providing acts as the password for master node. To avoid NOAUTH error from sentinel nodes, you have to provide the password for sentinel nodes. Look for any constructor that takes the password for sentinel nodes. That parameter is generally named sentinelPassword. There are several such constructors, simplest of those is
JedisSentinelPool(String masterName, Set<String> sentinels, String password, String sentinelPassword)
You're welcome to look for other constructor that suits you most.
We have defined Lettuce client connection factory to be able to connect to Redis defining custom socket and command timeout:
#Bean
LettuceConnectionFactory lettuceConnectionFactory() {
final SocketOptions socketOptions = SocketOptions.builder().connectTimeout(socketTimeout).build();
final ClientOptions clientOptions =
ClientOptions.builder().socketOptions(socketOptions).build();
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(redisCommandTimeout)
.clientOptions(clientOptions).build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(redisHost,
redisPort);
final LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(serverConfig,
clientConfig);
lettuceConnectionFactory.setValidateConnection(true);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
Lettuce documentation define default values:
Default socket timeout is 10 seconds
Default command timeout is 60 seconds
If Redis service is down application must receive timeout in 300ms. Which value must be defined as the greatest value?
Github example project:
https://github.com/cristianprofile/spring-data-redis-lettuce
In socket options you specify connect timeout. This is a maximum time allowed for Redis client (Lettuce) to try to establish a TCP/IP connection to a Redis Server. This value should be relatively small (e.g. up to 1 minute).
If client could not establish connection to a server within 1 minute I guess it's safe to say server is not available (server is down, address/port is wrong, network security like firewalls prohibit connection etc).
The command timeout is completely different. Once connection is established, client can send commands to the server. It expects server to respond to those command. The timeout configures for how long client will be waiting for a response to a command from the server.
I think this timeout can be set to a bigger value (e.g a few minutes) in case client command sends a lot of data to the server and it takes time to transfer and store so much data.
Where can I find information about Hikari properties that can be modified at runtime?
I tried to modify connectionTimeout. I can do it and it will be modified in the HikariDataSource without an exception (checked by setting and then getting the property) but it takes no effect.
If I initially do:
HikariConfig config = new HikariConfig();
config.setConnectionTimeout(12000);
HikariDataSource pool = new HikariDataSource(config);
and later on I do
config.setConnectionTimeout(5000);
Hikari tries to get a new connection for 12 seconds instead of 5 seconds.
Or is there a way to change the value with effect?
Are there other properties with the same behaviour?
You can do this through the MX bean, but you don't need to use JMX
public void updateTimeout(final long connectionTimeoutMs, final HikariDataSource ds) {
var poolBean = ds.getHikariPoolMXBean();
var configBean = ds.getHikariConfigMXBean();
poolBean.suspendPool(); // Block new connections being leased
configBean.setConnectionTimeout(connectionTimeoutMs);
poolBean.softEvictConnections(); // Close unused cnxns & mark open ones for disposal
poolBean.resumePool(); // Re-enable connections
}
Bear in mind you will need to enable pool suspension in your initial config
var config = new HikariConfig();
...
config.setAllowPoolSuspension(true);
You can't dynamically update the property values by resetting them on the config object - the config object is ultimately read once when instantiating the Hikari Pool (have a look at the source code in PoolBase.java to see how this works.
You can however do what you want and update the connection timeout value at runtime via JMX. How to do this is explained in the hikari documentation here
If your JVM has JMX enabled (I recommend for every prod), you could:
SSH-tunnel JMX port to your local machine
Connect to the VM in a JMX client like JConsole
Operate pool MBean as needed
Note: JMX port must never be public to the internet, be sure that firewall protects you.
SSH Tunnel command example:
ssh -i ${key_path} -N -L 9000:localhost:9000 -L 9001:localhost:9001 ${user}#${address}
I'm using the configuration below, with a cluster running on my local machine with the same port range as below (37500..37509)
IgniteConfiguration cfg = new IgniteConfiguration();
cfg.setClientMode(true);
TcpDiscoverySpi spi = new TcpDiscoverySpi();
TcpDiscoveryMulticastIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder();
ipFinder.setMulticastGroup("127.0.0.1");
// Set initial IP addresses.
// Note that you can optionally specify a port or a port range.
ipFinder.setAddresses(Arrays.asList("127.0.0.1:37500..37509"));
spi.setLocalPort(37508);
spi.setLocalPortRange(0);
TcpCommunicationSpi commSpi=new TcpCommunicationSpi();
commSpi.setLocalPort(37509);
// Overriding discovery SPI.
cfg.setDiscoverySpi(spi);
// Overriding communication SPI.
cfg.setCommunicationSpi(commSpi);
try (Ignite ig = Ignition.start(cfg)) {
IgniteCache<Integer, String> cache = ig.getOrCreateCache("myCacheName");
cache.put(1, "vlad");
cache.get(1);
}
I'm getting the below error message:
[17:51:14] IP finder returned empty addresses list. Please check IP finder configuration and make sure multicast works on your network. Will retry every 2 secs.
Any thoughts?
The error itself is shown because you didn't set the IP finder to the discovery SPI (spi.setIpFinder(ipFinder)).
However, you should also note that DiscoverySpi and CommunicationSpi are two different components and they use different ports. What you did here is bound communication to one of the ports discovery will try to connect to. Port ranges for discovery and communication should not intersect.
I'm using AWS OpsWorks for a Rails application with Redis and Sidekiq and would like to do the following:
Override the maxmemory config for redis
Only run Redis & Sidekiq on a selected EC2 instance
My current JSON config only has the database.yml overrides:
{
"deploy": {
"appname": {
"database": {
"username": "user",
"password": "password",
"database": "db_production",
"host": "db.host.com",
"adapter": "mysql2"
}
}
}
}
Override the maxmemory config for redis
Take a look and see if your Redis cookbook of choice gives you an attribute to set that / provide custom config values. I know the main redisio one lets you set config value, as I do it on my stacks (I set the path to the on disk cache, I believe)
Only run Redis & Sidekiq on a selected EC2 instance
This part is easy: create a Layer for Redis (or Redis/Sidekiq) and add an instance to that layer.
Now, because Redis is on a different instance than your Rails server, you won't necessarily know what the IP address for your Redis server is. Especially since you'll probably want to use the internal EC2 IP address vs the public IP address for the box (using the internal address means you're already inside the default firewall).
Sooo... what you'll probably need to do is to write a custom cookbook for your app, if you haven't already. In your attributes/default.rb write some code like this:
redis_instance_details = nil
redis_stack_name = "REDIS"
redis_instance_name, redis_instance_details = node["opsworks"]["layers"][redis_stack_name]["instances"].first
redis_server_dns = "127.0.0.1"
if redis_instance_details
redis_server_dns = redis_instance_details["private_dns_name"]
end
Then later in the attributes file set your redis config to your redis_hostname (maybe using it to set:
default[:deploy][appname][:environment_variables][:REDIS_URL] = "redis://#{redis_server_dns}:#{redis_port_number}"
Hope this helps!