Виноград
Синтаксис команд
Значение команды: переместите ключ текущей базы данных в заданную базу данных. Примечание команды: если текущая база данных (исходная база данных) и данная база данных (целевая база данных) имеют одинаковое имя данного ключа или ключ не существует в текущей базе данных, перемещение не имеет эффекта. Поэтому вы также можете использовать эту функцию для использования перемещения в качестве примитива блокировки. Формат команды:
MOVE key db
Командуйте реальным боем:
#Key exists in the current database
Redis > select 0 ා redis uses database 0 by default. For clarity, specify it again explicitly.
OK
redis> SET song "secret base - Zone"
OK
Redis > move Song 1 - move song to database 1
(integer) 1
Redis > exists song? Song has been removed
(integer) 0
Redis > select 1 - use database 1
OK
Redis: 1 > exists song ා verify that song has been moved to database 1 (note that the command prompt changes to "redis: 1", indicating that database 1 is being used)
(integer) 1
#When the key doesn't exist
redis:1> EXISTS fake_key
(integer) 0
Redis: 1 > move fake ᦇ attempt to move a nonexistent key from database 1 to database 0, failed
(integer) 0
Redis: 1 > select 0 - use database 0
OK
Redis > exists fake? Key? Confirm that fake? Key does not exist
(integer) 0
#When the source database and the target database have the same key
Redis > select 0 - use database 0
OK
redis> SET favorite_fruit "banana"
OK
Redis > select 1 - use database 1
OK
redis:1> SET favorite_fruit "apple"
OK
Redis: 1 > select 0 ා uses database 0 and attempts to move favorite ᦇ fruit to database 1
OK
Redis > move favorite ﹣ move failed because two databases have the same key
(integer) 0
Redis > get favorite fruit database 0's favorite fruit has not changed
"banana"
redis> SELECT 1
OK
Redis: 1 > get favorite fruit database 1's favorite fruit is also
"apple"Возвращаемое значение Если перемещение пройдет успешно, будет возвращено 1. Если перемещение не удастся, будет возвращено 0.
Анализ исходного кода
Функция команды перемещения, которая является функцией ввода команды перемещения:
void moveCommand(client *c) {
robj *o;
redisDb *src, *dst;
int srcid;
long long dbid, expire;
//Judge whether the cluster mode is on
if (server.cluster_enabled) {
addReplyError(c,"MOVE is not allowed in cluster mode");
return;
}
//Get current DB information from client information
src = c->db;
srcid = c->db->id;
//C - > argv is the parameter array, argv [1] stores the mobile key, and argv [2] stores the target database
//Getlonglongfromobject gets the target database ID, strong conversion to int type
//The judgment condition is therefore strong conversion string to int, judge whether it is within the scope of dbid, and switch the database to the target database
if (getLongLongFromObject(c->argv[2],&dbid) == C_ERR ||
dbid < INT_MIN || dbid > INT_MAX ||
selectDb(c,dbid) == C_ERR)
{
addReply(c,shared.outofrangeerr);
return;
}
//Get target database information
dst = c->db;
//Switch to the original database
selectDb(c,srcid); /* Back to the source DB */
//Judge whether the target database is consistent with the original database
if (src == dst) {
addReply(c,shared.sameobjecterr);
return;
}
/*Check whether the key exists in the original database and its information*/
o = lookupKeyWrite(c->db,c->argv[1]);
if (!o) {
addReply(c,shared.czero);
return;
}
//Get the expiration time of this key. If not, return - 1
expire = getExpire(c->db,c->argv[1]);
//Query whether the key exists in the target database. If it does not exist, an error message will be returned
if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
addReply(c,shared.czero);
return;
}
//Add the key and the object to the target database
dbAdd(dst,c->argv[1],o);
if (expire != -1) setExpire(c,dst,c->argv[1],expire);
incrRefCount(o);
/*Move completed, delete the original database*/
dbDelete(src,c->argv[1]);
server.dirty++;
addReply(c,shared.cone);
}Функция добавления базы данных: в команде “Переместить” мы хотим добавить ключ в целевую базу данных. Эта команда является ключевой.
void dbAdd(redisDb *db, robj *key, robj *val) {
//Copy key
sds copy = sdsdup(key->ptr);
//Insert the key into dict, copy is the key, Val is the corresponding value of the key
int retval = dictAdd(db->dict, copy, val);
serverAssertWithInfo(NULL,key,retval == DICT_OK);
if (val->type == OBJ_LIST ||
val->type == OBJ_ZSET)
signalKeyAsReady(db, key);
if (server.cluster_enabled) slotToKeyAdd(key);
}Функция добавления диктатора: вызовите эту функцию в бд Добавить и увеличить запись в дикт.
int dictAdd(dict *d, void *key, void *val)
{
//Insert a key into dict and return to entry
dictEntry *entry = dictAddRaw(d,key,NULL);
if (!entry) return DICT_ERR;
//Set the value of this entry
dictSetVal(d, entry, val);
return DICT_OK;
}Процесс GDB
Сначала установите значение ключа kkkkk равным 2, а затем выполните команду переместить
127.0.0.1:6380> set kkkk 2 OK 127.0.0.1:6380> select 0 OK 127.0.0.1:6380> move kkkk 1
1. First, we print the parameters passed in by the client. We can see that the three elements of argv are move, KKKK, 1: Сначала мы печатаем параметры, переданные клиентом. Мы видим, что тремя элементами argv являются move, KKKK, 1:
(gdb) p (char*)c->argv[0].ptr $10 = 0x7f175b820ae3 "move" (gdb) p (char*)c->argv[1].ptr $11 = 0x7f175b820afb "kkkk" (gdb) p (char*)c->argv[2].ptr $12 = 0x7f175b820acb "1"
2. Then we come to the getlonglongfromobject function. As we said above, the function is to convert data to int type. It has been described in the previous article and will not be described here. Then go to the second judgment condition to judge the scope of dbid, and finally switch to the target database, which conforms to the above reasoning: Затем мы переходим к функции getlonglongfromobject. Как мы уже говорили выше, функция заключается в преобразовании данных в тип int. Это было описано в предыдущей статье и не будет описано здесь. Затем перейдите ко второму условию суждения, чтобы оценить область действия dbid, и, наконец, переключитесь на целевую базу данных, которая соответствует приведенным выше рассуждениям:
(gdb) n 934 if (getLongLongFromObject(c->argv[2],&dbid) == C_ERR || (gdb) n 935 dbid < INT_MIN || dbid > INT_MAX || (gdb) 936 selectDb(c,dbid) == C_ERR) (gdb)
3. Print the information of the original database and the target database. We can see that the original database ID is 0 and the target database ID is 1 Распечатайте информацию исходной базы данных и целевой базы данных. Мы видим, что исходный идентификатор базы данных равен 0, а идентификатор целевой базы данных равен 3. Print the information of the original database and the target database. We can see that the original database ID is 0 and the target database ID is 1
(gdb) p *src
$14 = {dict = 0x7f175b80b360, expires = 0x7f175b80b3c0, blocking_keys = 0x7f175b80b420,
ready_keys = 0x7f175b80b480, watched_keys = 0x7f175b80b4e0, id = 0, avg_ttl = 0,
defrag_later = 0x7f175b80f330}
(gdb) p *dst
$15 = {dict = 0x7f175b80b540, expires = 0x7f175b80b5a0, blocking_keys = 0x7f175b80b600,
ready_keys = 0x7f175b80b660, watched_keys = 0x7f175b80b6c0, id = 1, avg_ttl = 0,
defrag_later = 0x7f175b80f360}4. After assigning the current database instance to DST, switch back to the original database, and judge whether the target database is consistent with the original database После назначения текущего экземпляра базы данных DST переключитесь обратно на исходную базу данных и оцените, соответствует ли целевая база данных исходной базе данных
942 selectDb(c,srcid); /* Back to the source DB */
(gdb)
946 if (src == dst) {5. Check whether the key exists. If it exists, return the object. Let’s look at the returned value. We find that the value type of the key is 0 and the value is 1. Then get its expire Проверьте, существует ли ключ. Если он существует, верните объект. Давайте посмотрим на возвращаемое значение. Мы обнаруживаем, что тип значения ключа равен 0, а значение равно 1. Затем получите срок его действия
(gdb) n
952 o = lookupKeyWrite(c->db,c->argv[1]);
(gdb)
953 if (!o) {
(gdb) p o
$2 = (robj *) 0x7f175b80ac80
(gdb) p *o
$3 = {type = 0, encoding = 1, lru = 9180225, refcount = 2147483647, ptr = 0x1}
(gdb) p $3.ptr
$4 = (void *) 0x1
(gdb) p (char*)$3.ptr
$5 = 0x1
(gdb) p (char)$3.ptr
$6 = 1 '(gdb) n
952 o = lookupKeyWrite(c->db,c->argv[1]);
(gdb)
953 if (!o) {
(gdb) p o
$2 = (robj *) 0x7f175b80ac80
(gdb) p *o
$3 = {type = 0, encoding = 1, lru = 9180225, refcount = 2147483647, ptr = 0x1}
(gdb) p $3.ptr
$4 = (void *) 0x1
(gdb) p (char*)$3.ptr
$5 = 0x1
(gdb) p (char)$3.ptr
$6 = 1 '\001'
(gdb) n
957 expire = getExpire(c->db,c->argv[1]);1'
(gdb) n
957 expire = getExpire(c->db,c->argv[1]);6. The next step is to determine whether the key exists in the target database. Because the target database does not exist, skip the if statement Следующий шаг-определить, существует ли ключ в целевой базе данных. Поскольку целевая база данных не существует, пропустите инструкцию if
(gdb)
960 if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
7. The next step is to add the key to the target database. The process here has been explained in the source code analysis, so only the execution process is posted.
173 void dbAdd(redisDb *db, robj *key, robj *val) {
(gdb) n
174 sds copy = sdsdup(key->ptr);
(gdb)
173 void dbAdd(redisDb *db, robj *key, robj *val) {
(gdb)
174 sds copy = sdsdup(key->ptr);
(gdb)
175 int retval = dictAdd(db->dict, copy, val);
(gdb)
177 serverAssertWithInfo(NULL,key,retval == DICT_OK);
(gdb)
178 if (val->type == OBJ_LIST ||
(gdb)
181 if (server.cluster_enabled) slotToKeyAdd(key);
(gdb)
182 }8 затем необходимо определить, есть ли срок действия. Если он существует, он настроен на увеличение количества ссылок. Ключ этой целевой базы данных был установлен. В то же время нам нужно удалить ключ исходной базы данных
965 if (expire != -1) setExpire(c,dst,c->argv[1],expire); (gdb) 966 incrRefCount(o); (gdb) n 969 dbDelete(src,c->argv[1]);
9. We print the dict of the target database and find that the KKKK that was just set up already exists. And the original key is no longer there. Мы печатаем дикт целевой базы данных и обнаруживаем, что только что настроенный KKKK уже существует. И оригинального ключа там больше нет.
(gdb) p *dst
$19 = {dict = 0x7f175b80b540, expires = 0x7f175b80b5a0, blocking_keys = 0x7f175b80b600, ready_keys = 0x7f175b80b660,
watched_keys = 0x7f175b80b6c0, id = 1, avg_ttl = 0, defrag_later = 0x7f175b80f360}
(gdb) p (char*)$19.dict.ht.table.key
$20 = 0x7f175b809931 "kkkk"
(gdb) p (char*)($21.dict.ht.table+0).key
$31 = 0x7f175b809921 "dddd"
(gdb) p (char*)($21.dict.ht.table+2).key
$32 = 0x7f175b8098f9 "key1"10. Finally, the response returns the client information. Наконец, в ответе возвращается информация о клиенте.
Расширять
База данных Redis multi: в соответствии с командой перемещения, которую мы объяснили, redis-это мультикоманда. Когда перемещение будет выполнено, мы выберем 0 используется для настройки базы данных. По умолчанию для redis используется база данных 0. Мы можем использовать команду deflation select для выбора базы данных. Экземпляр redis может предоставлять до 16 баз данных с индексами в диапазоне от 0 до 15,. Команда выглядит следующим образом:
Повторная транзакция. В redis вы можете использовать три команды multi exec discard для реализации транзакции. В транзакции все команды будут выполняться последовательно. Во время выполнения транзакции redis не будет предоставлять никаких услуг другим клиентам, чтобы гарантировать, что команды в транзакции выполняются атомарно
- Multi открывает транзакцию, и последующие команды будут сохранены в очереди команд
- Exec эквивалентно фиксации в транзакции реляционной базы данных
Отмена эквивалентна откату в транзакции реляционной базы данных. Например, операция отката:
Замок Редис
- Пессимистическая блокировка: консервативное отношение (пессимизм) к данным, изменяемым внешним миром. Таким образом, во всем процессе обработки данных обработка данных блокируется. Способ реализации: перед изменением любой записи попробуйте добавить к ней эксклюзивную блокировку. Если блокировка не срабатывает, это означает, что запись изменяется. Текущий запрос может подождать или вызвать исключение. Если блокировка успешно добавлена, вы можете изменить запись
- Оптимистическая блокировка: оптимистическая блокировка предполагает, что данные в целом не вызовут конфликта, поэтому только при отправке данных для обновления конфликт данных будет официально обнаружен. Если конфликт обнаружен, будет возвращено сообщение об ошибке
Здесь мы используем команду “Переместить” для анализа. Предположим, что в базе данных redis теперь есть ключ a со значением 10. В то же время есть два клиента redis (клиент 1, клиент 2), которые переехали A. Каков результат? Мы обнаруживаем, что последующее выполнение завершается неудачей. Но он не сообщил об ошибке. Почему? Когда два клиента работают с одним и тем же ключом, существует определенная последовательность. После перемещения первого ключа второй завершится ошибкой, если во время выполнения такого ключа не будет. Это означает, что мы можем воспользоваться этой функцией и использовать move в качестве примитива блокировки. В коде мы можем реализовать блокировку. Сама команда перемещения не имеет реализации блокировки, и мы не видели ее в исходном коде.
127.0.0.1:6380> keys * 1) "dddd" 2) "grape" 3) "key1" 4) "user" 127.0.0.1:6380> move grape 1 (integer) 1 (55.51s) 127.0.0.1:6380> 127.0.0.1:6380> keys * 1) "dddd" 2) "grape" 3) "key1" 4) "user" 127.0.0.1:6380> move grape 1 (integer) 0 (66.41s)
Для реализации блокировки redis рекомендуется прочитать: правильная поза для разблокировки замка redis