05 advanced features of redis
Pt1 publish and subscribe
When introducing LIST, we said that we can use LIST to implement producer consumer queue, but if we want to implement one to many publish and subscribe mode, we can't. For this reason, Redis provides channel based publish and subscribe functions.
The message publisher can publish messages to the specified channel, and subscribers can subscribe to one or more channels. As long as the message reaches the channel, all subscribers will receive the message.
127.0.0.1:6379> subscribe channel1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channel1" 3) (integer) 1 1) "message" 2) "channel1" 3) "hahah"
Redis also supports regular matching to subscribe to channel s. You can use? And * fuzzy matching.
-
? Represents a character
-
*Represents 0 or more characters
# Subscribers use psubscribe fuzzy matching 127.0.0.1:6379> psubscribe chn* Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "chn*" 3) (integer) 1 # The following is the processing after publishing the message 1) "pmessage" 2) "chn*" 3) "chnn" 4) "hahah" # Release news 127.0.0.1:6379> publish chnn hahah (integer) 1
Pt2 transaction
Redis request commands are single threaded and single commands are atomic. They either succeed or fail. There is no concurrent data consistency problem. However, when multiple commands or multiple clients operate commands concurrently, they need to be processed to solve the problems caused by concurrency. Redis provides the transaction function, which can execute a group of commands together. The transaction has three characteristics:
-
Execute commands in the order they enter the queue
-
Will not be affected by requests from other clients
-
Transactions cannot be nested. Multiple multi commands have the same effect
Redis transaction involves four commands:
-
Multi start transaction: start the transaction through the multi command. The client can continue to send any number of commands to the server. These commands will not be executed immediately, but will be placed in a queue;
-
Exec execution task: the commands in the queue are executed orderly. If exec is not executed, all commands will not be executed;
-
discard cancel transaction: cancels the execution of commands in the queue;
-
watch monitoring transaction execution: monitors the modification of key s, which will be described in detail later;
However, Redis transactions are not so perfect and are different from relational database transactions. We know that a transaction is to ensure the atomicity of a group of commands, either all successful or all failed. Partially successful commands must be rolled back to ensure atomicity, but Redis did not do so. It did not roll back.
-
When an exception occurs before the exec command is executed, the transaction execution will be cancelled and all commands in the queue will not be executed;
-
When an exception occurs after the exec command is executed, the executed command will not be rolled back, and the wrong command will not be executed;
# Scenario 1: an exception occurs before the exec command is executed # Open transaction 127.0.0.1:6379> multi OK # Execute command 127.0.0.1:6379> set name lucas QUEUED 127.0.0.1:6379> set age 30 QUEUED # hset is missing a value parameter, so the command is wrong 127.0.0.1:6379> hset job huifu (error) ERR wrong number of arguments for 'hset' command # Executing a transaction shows that the transaction has been cancelled because of a command error 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors.
# Scenario 2: an exception occurs after the exec command is executed 127.0.0.1:6379> multi OK 127.0.0.1:6379> set name lucas QUEUED # The command conflicts with the previous key value 127.0.0.1:6379> hset name chinese chen QUEUED 127.0.0.1:6379> set age 30 QUEUED # results of enforcement 127.0.0.1:6379> exec 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) OK
Therefore, redis transactions are not consistent with the common understanding. It is difficult for us to use redis transaction mechanism to achieve atomicity and ensure data consistency. Redis officials also explained this:
However there are good opinions for this behavior:
-
Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.
-
Redis is internally simplified and faster because it does not need the ability to roll back.
The main meaning is that in the above two scenarios, the problems caused by the code can be solved before the production goes online. Redis is not willing to sacrifice for this error and hopes to keep the internal command execution simple and fast.
Watch command
When multiple clients update variables, the data may be modified by other clients, resulting in unexpected results. Therefore, Redis provides the Watch command.
The watch command is consistent with the CAS optimistic lock behavior. When using watch, the value corresponding to the key will be recorded. When updating the key value, it will be compared with the data during watch. The update can be successful only when it is the same.
If at least one monitored key is modified before exec is executed after the transaction is started, the whole transaction will be cancelled.
Pt3 Lua script
Lua is a lightweight scripting language that is somewhat similar to stored procedures. Using Lua script to execute Redis has the following advantages:
-
Sending multiple commands at one time to reduce network overhead;
-
Redis executes the whole script as a whole without being interrupted by other requests, which is atomic;
-
Complex combined commands can be placed in files and reused later;
Pt3.1 invoke Lua script in Redis
Using eval syntax, you can execute Lua command on Redis client. The command format is as follows:
eval luaScript keyNum [key1 key2 key3 ...][value1 value2 value3]
-
eval stands for executing the command of Lua language;
-
luaScript represents the content of Lua language script;
-
Key num indicates the number of keys in the parameter. It should be noted that the key in Redis starts from 1. If there is no key parameter, write 0.
-
[key1, key2, Key3...] is the key passed to Lua language as a parameter. It can also be left blank, but it needs to correspond to the number of key num.
-
[value1 value2 value3] these parameters are passed to Lua language. They are optional.
127.0.0.1:6379> eval "return 'Hello world'" 0 "Hello world"
Pt3.2. Execute Redis command in Lua script
Use Redis Call to execute Redis command in Lua script. The syntax is as follows:
redis.call(command, key, [param1, param2...])
-
Command is a command, including set, get
-
Key is the operated key
-
Param1 and param2 represent the parameters given to the key
127.0.0.1:6379> eval "return redis.call('set', 'name','lucas')" 0 OK 127.0.0.1:6379> get name "lucas"
Of course, executing scripts directly seems to have no advantages and is very complex. We can execute multiple commands in files:
# View Lua file contents root@fa0050d94745:/data# ls appendonly.aof appendonly.aof.aaa backup.db dump.rdb redis.lua root zzh root@fa0050d94745:/data# cat redis.lua redis.call('set', 'age', '45') return redis.call('get', 'age') # Execute Redis Lua file root@fa0050d94745:/data# redis-cli --eval redis.lua 0 "45"
Pt3. 3. Redis + Lua case
Use Lua script to realize a simple current limiting operation, which limits each user to access Y times in X seconds
The script is as follows:
root@fa0050d94745:/data# cat redisIpLimit.lua local num = redis.call('incr', KEYS[1]) if tonumber(num) == 1 then redis.call('expire',KEYS[1],ARGV[1]) return 1 elseif tonumber(num) > tonumber(ARGV[2]) then return 0 else return 1 end
Test results (no more than 5 times in 6 seconds):
root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5 (integer) 1 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5 (integer) 1 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5 (integer) 1 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5 (integer) 1 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5 (integer) 1 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5 (integer) 0 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5 (integer) 0
Pt3.4 Lua script cache
If the Lua script is too large, the entire script needs to be passed to the Redis server every time the script is called, and the network overhead will be very large. To solve this problem, Redis can cache the Lua script and claim SHA1 summary code, and then directly execute the Lua script through the summary code.
-
Generate summary by script load command
-
evalsha executes the cached summary script
127.0.0.1:6379> script load "return 'Hello World'" "470877a599ac74fbfda41caa908de682c5fc7d4b" 127.0.0.1:6379> evalsha "470877a599ac74fbfda41caa908de682c5fc7d4b" 0 "Hello World"