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

Add config set command to modify corresponding redis nodes dynamically #95

Merged
merged 4 commits into from
Jan 10, 2017

Conversation

jasonjoo2010
Copy link
Contributor

for redis-ctl monitor system
add setremotes support for op purpose

@wooparadog
Copy link
Contributor

Could you add some tests for this feature please?

@wooparadog wooparadog changed the title add setremotes support Add proxy setremotes command Oct 17, 2016
@doyoubi
Copy link
Contributor

doyoubi commented Oct 17, 2016

Thanks for the pull request.
Note that there are some other places using config.node and they also need lock.
Further, using lock here will block all worker threads when processing the setremotes command. Maybe you need to test whether the blocking time is acceptable. If not, consider using pointer and reference count to implement a lock free method.

@jasonjoo2010
Copy link
Contributor Author

config.node need by modified in config_add, update_slots and setremotes.

all reading stages accquire a read lock using a rwlock, is this acceptable?

@doyoubi
Copy link
Contributor

doyoubi commented Oct 17, 2016

When worker threads are processing commands, they will choose a redis connection and may access config.node where the whole worker thread is possibly be blocked by setremotes and wait until it finish updating config.node. I think you should test whether this will be a performance problem.

@jasonjoo2010
Copy link
Contributor Author

fine.

I will make a lock-free implement then.

@jasonjoo2010
Copy link
Contributor Author

now it's lock free impl.
and test's main has been updated.

is it necessary to write a separately test?

@@ -54,6 +55,8 @@ static const char *rep_auth_not_set = "-ERR Client sent AUTH, but no password is
struct cmd_item cmds[] = {CMD_DO(CMD_BUILD_MAP)};
const size_t CMD_NUM = sizeof(cmds) / sizeof(struct cmd_item);
static struct dict command_map;
//need not destroy in linux
static pthread_mutex_t lock_setremotes = PTHREAD_MUTEX_INITIALIZER;
Copy link
Contributor

Choose a reason for hiding this comment

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

Even if it's ok not to destroy mutex, I think we should do it explicitly. Not destroying it is confusing.

@@ -453,6 +456,60 @@ int cmd_proxy(struct command *cmd, struct redis_data *data)

if (strcasecmp(type, "INFO") == 0) {
return cmd_proxy_info(cmd);
} else if (strcasecmp(type, "SETREMOTES") == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider using the CONFIG SET command. It's more intuitive to use redis command to telll the user what exactly this command do. In fact I didn't realize what SETREMOTES is until I read the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure to change this?
because the PR in redisctl almost be merged so we must decide it quickly.

CONFIG SET NODE HOST1 PORT1 HOST2 PORT2 ... ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this cmd is implemented to support operations. It's mainly for REDIS-CTL.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, because later we may let more config options changable in run time. I think we should keep all this to only one CONFIG SET command.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, no problem
the cmd's syntax will be "CONFIG SET NODE ip:port,ip1:port1", is that ok?

and i want to figure out if there is a method to check "cluster_ok" in proxy, need by redisctl.

Copy link
Contributor

Choose a reason for hiding this comment

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

CONFIG SET NODE ip port ip port... is easier to parse. And I suggest NODE should not be case sensitive.
What does cluster_ok mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"CONFIG SET NODE ip:port,ip1:port1" is that i want to reuse config_add and easy to understand comparing to config syntax, is this ok? Maybe we will reuse more in config_add when we add more functions.

there's a polling monitor in REDIS-CTL and it tries to fetch the status of cluster in proxy. The value will turn to false when cluster is down or update-slots-thread fails.

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. OK.
  2. Corvus doesn't check the status of cluster.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Ok. Maybe i will always set this to "ok" for corvus proxy.

//lock global
pthread_mutex_lock(&lock_setremotes);
if (data->elements % 2 != 0 || data->elements < 4) {
cmd_mark_fail(cmd, rep_addr_err);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please correct your indention.

@@ -453,6 +456,60 @@ int cmd_proxy(struct command *cmd, struct redis_data *data)

if (strcasecmp(type, "INFO") == 0) {
return cmd_proxy_info(cmd);
} else if (strcasecmp(type, "SETREMOTES") == 0) {
//lock global
pthread_mutex_lock(&lock_setremotes);
Copy link
Contributor

Choose a reason for hiding this comment

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

The critical section here is too large. A smaller one provides better performance and makes it more easier to understand what it protects.

}
if (pos_to_str(&data->element[i + 1].pos, port_s) == CORVUS_ERR) {
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

When the input is illegal, reject it with error message instead of just ignoring it.

}
{
struct node_conf *newnode = cv_malloc(sizeof(struct node_conf));
memset(newnode, 0, sizeof(struct node_conf));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think cv_calloc is better.

for (i = 0; i < config.node.len; i++) {
server = conn_get_server_from_pool(ctx, &config.node.addr[i], false);
struct node_conf *node = config.node;
conf_node_inc_ref(node);
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that there is race condition here. After you get node, other threads may call conf_node_dec_ref and destroy it which will cause corruption in conf_node_inc_ref(node). We do need lock here and this is not likely to bring notable performance problem since the critical section here is so small.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

to avoid locking, is it possible to increase refcount first then get pointer?

{ 
    ATOMIC_INC(ref)
    return ref
}

Copy link
Contributor

Choose a reason for hiding this comment

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

No... Before you increase reference count, you have to get its address first. But between them, other threads may free the content of that address. ATOMIC_INC does not guarantee  the address is safe to use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done with a mutex lock and change inc() to return a pointer

@@ -83,7 +84,9 @@ int main(int argc, const char *argv[])
build_contexts();
struct context *contexts = get_contexts();

memcpy(&config.node, &conf, sizeof(config.node));
config.node = cv_malloc(sizeof(struct node_conf));
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you used valgrind to check whether there is memory leak in test program?
Please add more complete tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, checked for newest version and change all malloc to calloc

@jasonjoo2010 jasonjoo2010 force-pushed the master branch 4 times, most recently from 15393e3 to 8d187eb Compare October 18, 2016 03:32
@jasonjoo2010
Copy link
Contributor Author

hi,
how is everything going guys?

there is a new feature is needed to support running in dockers:
the configuration items can be overrided by ones specified in parameters.
eg: corvus -c corvus.conf -n 10.10.55.99:6379,10.10.55.98:6382

is that in the corvus road map?
or could i make a PR for it?

and surely some basic configurations' modifying in runtime can be made together.

@wooparadog
Copy link
Contributor

@jasonjoo2010

We originally don't have plans for overriding configurations on the fly. But it's indeed a must have feature for running in docker etc. It's quite honoured to have your pull request. In regard to hot configuration modifying, we currently lack enough man power to fully test this feature, so it'll be sometime before we're fully confident to merge this request.

But

corvus -c corvus.conf -n 10.10.55.99:6379,10.10.55.98:6382

seems a simple feature, your pull request is quite welcome.

@jasonjoo2010
Copy link
Contributor Author

@wooparadog
fine. I would try to commit this PR soon.
good job for days and thanks for all your efforts in opening sources such a wonderful proxy.

@tonicmuroq
Copy link

when would this PR be merged ... Now corvus automatically discovers redis nodes, and when I use GET xxx after I deleted a node which holds value for key xxx, it returns error, and I need to retry to trigger auto discovery nodes of corvus, which I think is not better than SETREMOTES because clients may not do retry ..... = =

@jasonjoo2010
Copy link
Contributor Author

@tonicbupt what kind of cluster do you deploy? generally multi (M-S) or more slaves in a "slot group" may not cause such problem. The slave of the broken master should become master automatically. And once a request failed corvus would to update the mapping once.

@wooparadog am i right? I just say it according to the debug log and i remember having done such experiment before.

@doyoubi
Copy link
Contributor

doyoubi commented Dec 5, 2016

Corvus will redirect and retry when receive MOVED or ASK. So no need for clients to do that.
Corvus keep a list of nodes(16 at most) in memory and will update them if it find the cluster has changed.
But there are still two reasons for adding SETREMOTES:
(1) When all of the 16 nodes kept in memory are moved out from the cluster at the same time, corvus can't find correct nodes automatically any more.
(2) After several times of deleting cluster nodes, the node in config file may become outdated. Once restarted corvus can't find the correct nodes.

@tonicmuroq
Copy link

@jasonjoo2010 here's how I reproduce this issue:
I create 3 redis instance, say, A, B, C, and use ruskit to make them a cluster, all of them are master. then I create a corvus instance to proxy redis requests to this cluster, SET a 1, SET b 1, SET c 1, all success. then I use ruskit to remove one node, say, C, then this cluster only has two masters, A, B. now SET a 1, SET b 1 will succeed but SET c 1 will return cluster is down. Of course cluster now is down, because corvus may use C as the node to proxy request to, then if I retry SET c 1, will succeed, which means corvus updated nodes information after cluster is down. here you will see, clients may need to do second retry, which is not that easy for them... so I think SETREMOTES is really useful...

@doyoubi
Copy link
Contributor

doyoubi commented Dec 6, 2016

@tonicbupt In your case corvus will not redirect SET c 1 to node A or B. If you use ruskit delete to remove node C, all slots of C will be moved to A and B and there shouldn't be cluster is down. What's the response of CLUSTER NODES after you delete node C?
Corvus is always stateless and will never build the hash table itself. It will only get the slots table from redis node. If the cluster is really down, corvus can't redirect slots of C to A or B.

@tonicmuroq
Copy link

@doyoubi INFO shows A, B, C nodes in corvus. corvus didn't update its mapping when I use ruskit to remove one node, so it might pass request to the deleted node and get answer Cluster is down, then corvus tries to update its mapping using A, B, and got right result, then SET c 1 will succeed. This is the probable process I guess... I kind of hope corvus will retry automatically when it got answer Cluster is down, and this is not that possible, so SETREMOTES will be really useful, I can use SETREMOTE to update corvus' mapping manually.

@jasonjoo2010
Copy link
Contributor Author

@doyoubi maybe i know what tonic is saying about.

when he remote a node from cluster using ruskit, the node removed is still accessable (no conn err and no redirect error), so corvus will not retry during this request.

All other request will succeed.

maybe corvus only update its map with a update thread timeout(does it exists?)

@doyoubi
Copy link
Contributor

doyoubi commented Dec 6, 2016

@tonicbupt I reproduce your case and get cluster is down successfully. But CLUSTER NODES showed there were only A and B.
Actually corvus did update the mapping when it find cluster is down in a separate thread but may get its job done after the request exceeded max retry. So your first request failed while the second didn't.
But if corvus doesn't received SETREMOTES in time or it does but soon update its mapping from a redis node with outdated slots mapping, your clients still get the down error.
I think there is no such way for a stateless corvus to overcome this problem unless it keep retrying...

@tonicmuroq
Copy link

tonicmuroq commented Dec 6, 2016

@doyoubi actually I mean after removing nodes, INFO shows A, B, C, and after got Cluster is down, INFO shows A, B ... anyway I got what you mean... there's no difference whether using SETREMOTES or not when remove a node. But if I add one node to cluster, will corvus automatically find the new node? I add node D to cluster and migrate slots to D, error will occur before the migration is done I guess...

@doyoubi
Copy link
Contributor

doyoubi commented Dec 6, 2016

@tonicbupt Of course, corvus will find the new node by redirecting according to MOVED and ASK.

@tevino
Copy link
Contributor

tevino commented Jan 3, 2017

@jasonjoo2010 Hey, Just in case that you forgot this.

This feature is officially in our schedule, there are still changes requested, if you are not active on this PR anymore, we'll continue your work by ourselves.

@jasonjoo2010
Copy link
Contributor Author

@tevino
All changes requested had been fixed already ASAP before and rebase to one commit as requested.
So you may not seen the just in position difference.

Or is there any more reviews?

Copy link
Contributor

@doyoubi doyoubi left a comment

Choose a reason for hiding this comment

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

Thanks to your contribution. Sorry for leaving this PR for such a long time.

@@ -54,7 +59,7 @@ static const char *rep_auth_not_set = "-ERR Client sent AUTH, but no password is
struct cmd_item cmds[] = {CMD_DO(CMD_BUILD_MAP)};
const size_t CMD_NUM = sizeof(cmds) / sizeof(struct cmd_item);
static struct dict command_map;

static pthread_mutex_t lock_config = PTHREAD_MUTEX_INITIALIZER;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this lock used to guarantee that there is always only one thread modifying the config.node? I think we can do this in another place. See the reason below.

pthread_mutex_lock(&lock_config);
if (strcasecmp(option, "NODE") == 0) {
// config set node host:port,host1:port1
char value[data->element[3].pos.str_len + 1];
Copy link
Contributor

Choose a reason for hiding this comment

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

Here the length of element may be less than 4. I think we should add check before this.

if (data->elements != 4) {
cmd_mark_fail(cmd, rep_config_parse_err);
} else if (data->element[3].pos.str_len
< 9|| pos_to_str(&data->element[3].pos, value) != CORVUS_OK) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you add some comment about that 9?


void config_init()
{
memset(config.cluster, 0, CLUSTER_NAME_SIZE + 1);
strncpy(config.cluster, "default", CLUSTER_NAME_SIZE);

config.bind = 12345;
memset(&config.node, 0, sizeof(struct node_conf));
config.node = cv_calloc(1, sizeof(struct node_conf));
memset(config.node, 0, sizeof(struct node_conf));
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to memset again.

if (socket_parse_ip(p, &config.node.addr[config.node.len]) == -1) {
cv_free(config.node.addr);
return -1;
addr = cv_realloc(addr, sizeof(struct address) * (addr_cnt + 1));
Copy link
Contributor

Choose a reason for hiding this comment

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

Please correct your indention.

newnode->len = addr_cnt;
newnode->refcount = 1;
struct node_conf *oldnode = config.node;
config.node = newnode;
Copy link
Contributor

@doyoubi doyoubi Jan 3, 2017

Choose a reason for hiding this comment

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

Actually we should add lock to protect the swapping pointers here. This is corresponding to the locking in conf_node_inc_ref;
Think about this case:
(1) oldnode get its value.
(2) conf_node_dec_ref is called somewhere else and destroy this oldnode.
Well, even though once initialization, config.node will only be modified by cmd_config protected by lock_config, conf_node_dec_ref also exists in conn_get_raw_server which may be confusing - Is this place will free the current config.node?
Further the the critical section protected by lock_config is a little bit long.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

node_dec_ref has to be reserved to make things simple if you just take it as "i dont want to use it anymore".

lock_config has been removed and it considered to be a config cmd lock. but no logic cannot be multi-thead currently i just remove it now.

i add same lock in pointer swapping.

void conf_node_dec_ref(struct node_conf *node)
{
pthread_mutex_lock(&lock_conf_node);
int refcount = ATOMIC_DEC(node->refcount, 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can safely remove the lock here. Thanks to the atomic operation multiple calling of conf_node_dec_ref will end up with only one zero refcount;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, so just remove it

return CORVUS_ERR;
}
if (strcasecmp(type, "SET") == 0) {
// `config set` generelly need global lock
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment is outdated now.

p = strtok(NULL, ",");
}
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Remember to correct your indention.

Copy link
Contributor

Choose a reason for hiding this comment

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

And remember to fix conflicts.

Copy link
Contributor

Choose a reason for hiding this comment

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

What is that new PR mainly about? Is that a new feature?
I recommend fixing the conflicts here and creating a separate PR for new features.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, conflicts have been resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@doyoubi OK, now

did these slowly due to poor internet trafic

pthread_mutex_lock(&lock_conf_node);
struct node_conf *oldnode = config.node;
config.node = newnode;
conf_node_dec_ref(oldnode);
Copy link
Contributor

Choose a reason for hiding this comment

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

conf_node_dec_ref(oldnode); can be safely moved outside of the critical section.

node_list.len++;
}
conf_node_dec_ref(node);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move line 40 and 46 outside of the critical section.

}
}
} else {
cmd_mark_fail(cmd, rep_addr_err);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think what you want here is rep_config_err.

@jasonjoo2010 jasonjoo2010 force-pushed the master branch 2 times, most recently from 8269e60 to a1642dd Compare January 4, 2017 08:05
Copy link
Contributor

@doyoubi doyoubi left a comment

Choose a reason for hiding this comment

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

I think returning rep_addr_err is a little bit confusing in line 551 of command.c.
Others LGTM.

cmd_mark_fail(cmd, rep_addr_err);
}
} else if (strcasecmp(type, "GET") == 0) {
if (strcasecmp(option, "NODE") == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We'd better check data->elements here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

aha, sorry about changes after merging.
I change the assert for elements judging ensure that.

@doyoubi doyoubi changed the title Add proxy setremotes command Add config set command to modify corresponding redis nodes dynamically Jan 4, 2017
@doyoubi
Copy link
Contributor

doyoubi commented Jan 4, 2017

Thanks for your contribution @jasonjoo2010 . We will finally merge this PR after we finish testing.

@tevino
Copy link
Contributor

tevino commented Jan 4, 2017

@jasonjoo2010 Thanks for your work! 😋

@jasonjoo2010
Copy link
Contributor Author

@doyoubi @tevino We just take it as an important online component, too. So we are also working on it.

Thank you all for the great, cute work.

@doyoubi doyoubi merged commit 0318b8b into eleme:master Jan 10, 2017
@tonicmuroq
Copy link

🍻

@doyoubi doyoubi mentioned this pull request Jan 11, 2017
@doyoubi doyoubi mentioned this pull request May 9, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants