Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sentinel Support Derived from pr-692 #1067

Merged
merged 24 commits into from
Mar 18, 2020
Merged

Conversation

shadim
Copy link
Contributor

@shadim shadim commented Feb 21, 2019

This PR is derived from PR-692 and have been merged with the latest master commit.
Things that have been done:

  1. review code for PR-692
  2. fixed potential infinite loop in the code
  3. Adapt code to success build with the latest master commit
  4. Manual testing on 3 Sentinel nodes and 3 Redis nodes (connection and failover)

Usage:

                ConfigurationOptions sentinelConfig = new ConfigurationOptions();
                sentinelConfig.ServiceName = "mymaster";
                sentinelConfig.EndPoints.Add("192.168.99.102", 26379);
                sentinelConfig.EndPoints.Add("192.168.99.102", 26380);
                sentinelConfig.EndPoints.Add("192.168.99.102", 26381);
                sentinelConfig.TieBreaker = "";
                sentinelConfig.DefaultVersion = new Version(4, 0, 11);                 
                // its important to set the Sentinel commands supported
                sentinelConfig.CommandMap = CommandMap.Sentinel;

                // Get sentinel connection
                ConnectionMultiplexer sentinelConnection = ConnectionMultiplexer.Connect(sentinelConfig, Console.Out);
                // Create master service configuration
                ConfigurationOptions masterConfig = new ConfigurationOptions { ServiceName = "mymaster" };
                // Get master Redis connection
                var redisMasterConnection = sentinelConnection.GetSentinelMasterConnection(masterConfig);

                ...
               IDatabase db = redisMasterConnection.GetDatabase();                
               db.StringSet(key, value);
               ...
               string value1 = db.StringGet(key);

Areson and others added 2 commits August 28, 2017 12:02
Initial pass at adding Sentinel support. Adds ConnectionMultiplexer that
is managed by a set of Sentinel servers. Usage:
```C#
ConfigurationOptions sentinelConfig = new ConfigurationOptions();
sentinelConfig.ServiceName = "ServiceNameValue";
sentinelConfig.EndPoints.Add("127.0.0.1", 26379);
sentinelConfig.EndPoints.Add("127.0.0.2", 26379);
sentinelConfig.EndPoints.Add("127.0.0.4", 26379);
sentinelConfig.TieBreaker = "";
sentinelConfig.DefaultVersion = new Version(3, 0);

// Add the configuration for the masters that we will connect to
ConfigurationOptions masterConfig = new ConfigurationOptions();
sentinelConfig.SentinelMasterConfignfigurationOptions = mo;

// Connect!
ConnectionMultiplexer sentinelConnection =
ConnectionMultiplexer.Connect(sentinelConfig);

// When we need to perform a command against the masters, use the master
connection
sentinelConnection.SentinelMasterConnection.GetDatabase().MyCommand();
```

Sentinel Support - Second Draft

Modified approach to allow for a sentinel-only setup with the option of
requesting a connection to a master managed by the sentinel connection.
Can also support multiple services on the same sentinel.

Handle closed managed connections

Add some basic handling for disposed connections that are managed by the
Sentinel connection.
@gkorland
Copy link
Contributor

@mgravell

@kooldude2010
Copy link

Need some help, noob here. Downloaded the code and built it and added reference to Redis.Stackexchange.dll to my own project. I am not able to see GetSentinelMasterConnection method. What am I doing wrong?

Any help would be greatly appreciated.

@shadim
Copy link
Contributor Author

shadim commented Feb 28, 2019 via email

@jrsearles
Copy link

I am finding with this PR that redis slaves are never used, even when requests are set to prefer them. Digging through the code it appears that slaves are never added to the serverSnapshot. Is this an oversight?

shadim and others added 2 commits May 12, 2019 14:50
- modify to use new class LogProxy with pr-1067 code
@gkorland
Copy link
Contributor

gkorland commented Jul 7, 2019

@mgravell any chance you got to review it?

…ConnectionMultiplexer area.

 adding new sentinel instance to be comply with the recommended sentinel configuration.
@shadim
Copy link
Contributor Author

shadim commented Sep 2, 2019

I am finding with this PR that Redis slaves are never used, even when requests are set to prefer them. Digging through the code it appears that slaves are never added to the serverSnapshot. Is this an oversight?

by design GetSentinelMasterConnection should point to the master see the following architecture
http://taswar.zeytinsoft.com/wp-content/uploads/2018/03/redis-sentinel.png

there is no oversight to use the slaves you should do something like the following snippet:

            var Server = sentinelConnection.GetServer(<any sentinel ip>, <port>);
            var slaves = Server.SentinelSlaves(ServiceName);
            var config = new ConfigurationOptions
            {                 
                TieBreaker = "";
                ServiceName = TestConfig.Current.SentinelSeviceName,
                SyncTimeout = 5000
            };

            foreach(var kv in slaves)
            {
                config.EndPoints.Add(kv.ToDictionary()["name"]);
            }
            var readonlyConn = ConnectionMultiplexer.Connect(config);

I had added a new unit test for the above case see ReadOnlyConnectionSlavesTest.

@mrmartan
Copy link

mrmartan commented Sep 3, 2019

ConnectionMultiplexer.ConnectImplAsync does not call ConnectionMultiplexer.InitializeSentinel

And shouldn't there also be ConnectionMultiplexer.GetSentinelMasterConnectionAsync?

@mrmartan
Copy link

mrmartan commented Sep 3, 2019

Regarding slaves and

there is no oversight to use the slaves you should do something like the following snippet

I seem to fail to grasp the idea. Can you elaborate how this is envisioned to be used?
From #692:

The main test besides those would be master/slave working correctly - e.g. one ConnectionMultiplexer configured via Sentinel following and working after a Sentinel failover is issued.

Using GetSentinelMasterConnection how do I add slaves to the connection returned? I want this multiplexer to work the same way as non-sentinel enabled one, ie. handles reconfiguration through sentinel to connect to Redis instances and respects CommandFlags (Prefer/Demand/Master/Slave). There seems to be a lot of work expected to be done on the GetSentinelMasterConnection user side.

@shadim
Copy link
Contributor Author

shadim commented Sep 3, 2019

Regarding slaves and

there is no oversight to use the slaves you should do something like the following snippet

I seem to fail to grasp the idea. Can you elaborate on how this is envisioned to be used?
From #692:

The main test besides those would be master/slave working correctly - e.g. one ConnectionMultiplexer configured via Sentinel following and working after a Sentinel failover is issued.

Using GetSentinelMasterConnection how do I add slaves to the connection returned? I want this multiplexer to work the same way as non-sentinel enabled one, ie. handles reconfiguration through sentinel to connect to Redis instances and respects CommandFlags (Prefer/Demand/Master/Slave). There seems to be a lot of work expected to be done on the GetSentinelMasterConnection user side.

I agree it's easy to add the slaves also in the GetSentinelMasterConnection but, I am not sure if this match the Redis client design. because GetSentinelMasterConnection should point to the master only.
if there is a read operation that should be done against the slaves should have a new connection for this purpose of load balancing.

anyway, I will examine if its correct to do this change in GetSentinelMasterConnection or create a new method GetSentinelSlaveConnection.

@mrmartan
Copy link

mrmartan commented Sep 4, 2019

Should we discuss this here or under #692?

I come from a place where we used Redis Cluster and that is more or less transparent for this client. You throw at it all the cluster nodes and it "just works." Even with Sentinel when you point the client at Redis nodes (those Sentinel managed) it works until there is some Sentinel action. And the connection multiplexer very much understands which nodes are masters and which are slaves and sends commands according to CommandFlags. So the ultimate goal (for me at least) would be to give connection string pointing to Sentinel nodes to ConnectionMultiplexer.Connect() and be returned Sentinel-managed ConnectionMultiplexer instance that just works.

Also what #692 (and by extension you) is doing amounts to a breaking change. With this Sentinel support (through GetSentinelMasterConnection) one would not be able to use Sentinel with libraries depending on StackExchange.Redis unless those libraries change, e.g. Microsoft.Extensions.Caching.StackExchangeRedis

…ent that sentinel should response to info command)

 add Sentinel Masters command to setup connection instead using info command.
@Areson Areson mentioned this pull request Oct 21, 2019
@jastBytes
Copy link

I would love to see this working in the near future. Is there something I can do to make this happen soon?

@gkorland
Copy link
Contributor

gkorland commented Dec 9, 2019

@mgravell is anything blocking this PR?

@gkorland
Copy link
Contributor

gkorland commented Jan 1, 2020

@NickCraver

@NickCraver
Copy link
Collaborator

@gkorland Yep - there's still zero testing on any of the Sentinel bits thus far - it's not something we use or prioritize, so it especially needs testing. It's something we've brought up in all the other Sentinel PRs thus far.

@shadim
Copy link
Contributor Author

shadim commented Jan 1, 2020

but, we have completed the missing testing.
what kind of testing did you missing in this pr?

@NickCraver
Copy link
Collaborator

@shadim Ah indeed! I completely missed this from being collapsed - let me try and pull this local and remedy a few things :) It'll need Marc's eyes as well but let me get tests in shape on Linux, master merged, etc. - let's see if I can push to that branch...

@shadim
Copy link
Contributor Author

shadim commented Jan 9, 2020

@shadim this is what I have locally at the moment running all servers:
image

Can you confirm what you're seeing local?

I imagine this is a state issue based on bad assumptions in the tests of "clean slate" which is true on the CI server on a fresh checkout, but not locally where you get state, e.g. the contents of the .conf for a sentinel monitor may look like this on checkout:

port 26381
sentinel monitor mymaster 127.0.0.1 7010 1
sentinel down-after-milliseconds mymaster 1000
sentinel failover-timeout mymaster 2000
sentinel config-epoch mymaster 0
dir "../Temp" 

But locally after a run, it'll look like (and future test runs will start with):

port 26381
sentinel myid cbfdf24284a91f65b29bdcc22210489b9311c3a5
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 127.0.0.1 7010 1
sentinel down-after-milliseconds mymaster 1000
dir "/mnt/c/git/StackExchange/StackExchange.Redis/tests/RedisConfigs/Temp"
# Generated by CONFIG REWRITE
maxclients 4064
protected-mode no
sentinel failover-timeout mymaster 2000
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 1
sentinel known-replica mymaster 127.0.0.1 7011
sentinel known-sentinel mymaster 127.0.0.1 26380 a2602c71b26af58d8496fc497461af281b4fd40c
sentinel known-sentinel mymaster 127.0.0.1 26379 bdc76ccaaf8833537b87afc7dcb1b7719b37a64f
sentinel current-epoch 1

I'm out for some errands and docs today, but poking at git resetting those locally to see if that is indeed the issue and then we can go about resetting state at the start of the test properly (as failover tests do - see Failover.cs constructor).

If you could sanity check me on having inconsistent or bad test results locally, that'd be a huge help! We want to get that green before moving forward here and getting eyes/thoughts from @mgravell on approach.

@NickCraver no in my local environment I get all the test pass, I think you are right this because of bad assumptions in the tests of "clean slate"

image

@jastBytes
Copy link

I would like to contribute, but did not manage to make the solution work on Linux and from what I've read in the documentation testing is only supported on Windows in general.

@jded76
Copy link

jded76 commented Feb 22, 2020

I'd like to contribute on this too, if you need any help.
I tried the sentinel tests and they are running fine, no matter how many times i stopped the servers and running start-sentinel.cmd again, or if run the tests without stopping the servers.

tests

I looked at the tests, and there is no assumption of who is the master or the slave server.
The pattern they use in general is get the current master, make a fail-over, check that the new master is different from the previous. So I don't think that anything is needed in the Sentinel.cs constructor.

@NickCraver Maybe the output of the servers will help you determine what is the exact problem of the failures.
One strange thing that I noticed in your config is that before the "Generated by CONFIG REWRITE" line, you have the ID of the server (sentinel myid cbfdf24284a91f65b29bdcc22210489b9311c3a5). In my configs this setting does not exist. And I noticed that the IDs of the servers is changing after every run of the batch file (start-sentinel.cmd).
Can you check that the IDs of your servers are different on every config, because if not this will cause conflict (see here)?

@jded76
Copy link

jded76 commented Mar 7, 2020

@NickCraver is this still alive? Can I help with something to get things done?

@NickCraver
Copy link
Collaborator

I've been trying to get tests on CI stable (including getting an upgrade to our CI plan) - now trying consistent builds here. I'm also testing some community contributions on Docker to get a clean slate test environment more readily available for all.

I've got CI mostly green now which was the hard part - kicking builds here and trying to pull in the Docker approach as another testing method on any platform for all. That'll make this much easier to solve...the changing state on local machines makes this cluster state-dependent type of PR especially hard to determine flakiness of (it's likely my cluster).

@ejsmith
Copy link
Contributor

ejsmith commented Mar 16, 2020

@NickCraver I'm having a really hard time trying to create a clean set of changes ahead of this PR for the docker support. I have a fork based on https://github.com/shadim/StackExchange.Redis that I have made changes to here: https://github.com/ejsmith/StackExchange.Redis My git skills are failing me. It seems like this entire PR isn't a ton of changes and it would be nice to have a clean PR with minimal changes rebased onto your current master, but I haven't been able to figure out how to do that without just doing a diff and creating a brand new PR.

@ejsmith
Copy link
Contributor

ejsmith commented Mar 16, 2020

I created this PR to the PR. :-) shadim#3

NickCraver pushed a commit that referenced this pull request Mar 17, 2020
Found while fixing up runs for #1067. Docker layer cache sucks :)
NickCraver added a commit that referenced this pull request Mar 17, 2020
Found while fixing up runs for #1067. Docker layer cache sucks :)
Copy link
Collaborator

@NickCraver NickCraver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting this in for the 2.1.x release, thanks for the work here everyone! We've got more testing in, Docker ability to test this locally better, and are generally in a good state. We can make more refinements here as needed.

@NickCraver NickCraver merged commit b2db13f into StackExchange:master Mar 18, 2020
@gliljas
Copy link
Contributor

gliljas commented Mar 29, 2020

Were the "transparency issues" raised by @mrmartan addressed?

@mgravell
Copy link
Collaborator

mgravell commented Mar 29, 2020 via email

@emi662002
Copy link

emi662002 commented Jun 18, 2020

This PR is derived from PR-692 and have been merged with the latest master commit.
Things that have been done:

  1. review code for PR-692
  2. fixed potential infinite loop in the code
  3. Adapt code to success build with the latest master commit
  4. Manual testing on 3 Sentinel nodes and 3 Redis nodes (connection and failover)

Usage:

                ConfigurationOptions sentinelConfig = new ConfigurationOptions();
                sentinelConfig.ServiceName = "mymaster";
                sentinelConfig.EndPoints.Add("192.168.99.102", 26379);
                sentinelConfig.EndPoints.Add("192.168.99.102", 26380);
                sentinelConfig.EndPoints.Add("192.168.99.102", 26381);
                sentinelConfig.TieBreaker = "";
                sentinelConfig.DefaultVersion = new Version(4, 0, 11);                 
                // its important to set the Sentinel commands supported
                sentinelConfig.CommandMap = CommandMap.Sentinel;

                // Get sentinel connection
                ConnectionMultiplexer sentinelConnection = ConnectionMultiplexer.Connect(sentinelConfig, Console.Out);
                // Create master service configuration
                ConfigurationOptions masterConfig = new ConfigurationOptions { ServiceName = "mymaster" };
                // Get master Redis connection
                var redisMasterConnection = sentinelConnection.GetSentinelMasterConnection(masterConfig);

                ...
               IDatabase db = redisMasterConnection.GetDatabase();                
               db.StringSet(key, value);
               ...
               string value1 = db.StringGet(key);

Is this the right way to configure sentinels? I'm quite confused as i see this in the documentation:

If you specify a serviceName in the connection string, it will trigger sentinel mode. This example will connect to a sentinel server on the local machine using the default sentinel port (26379), discover the current master server for the mymaster service and return a managed connection pointing to that master server that will automatically be updated if the master changes:

var conn = ConnectionMultiplexer.Connect("localhost,serviceName=mymaster");

My specific problem:
When I use the code from this post with GetSentinelMasterConnection in combination with RedisXmlRepository I get an error:
'This operation has been disabled in the command-map and cannot be used: LRANGE'

@ejsmith
Copy link
Contributor

ejsmith commented Jun 19, 2020

When you call var conn = ConnectionMultiplexer.Connect("localhost,serviceName=mymaster"); that is returning a managed connection to the Redis servers (not the sentinel). So you don’t need to call GetSentinelMasterConnection afterward.

@emi662002
Copy link

When you call var conn = ConnectionMultiplexer.Connect("localhost,serviceName=mymaster"); that is returning a managed connection to the Redis servers (not the sentinel). So you don’t need to call GetSentinelMasterConnection afterward.

Indeed! thanks!

odinserj added a commit to HangfireIO/StackExchange.Redis that referenced this pull request Sep 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.