preface

As for Redis’s “starting and closing”, I have already made a detailed exposition of the basic Redis – “starting” in five chapters. I believe that you can learn a lot from Redis regardless of whether you have touched it before. The content of the basic chapter, as the name implies, is just a foundation, mainly said the development of Redis and the basic data types of Redis, the content and the usual use of the association will be relatively large, not too difficult, I hope you can digest. Here are the plane tickets for the basics:

[From] Overview of Redis – take you through the past life of Redis

[正] Redis foundation – Basic data structure String, Hash

【 英 】Redis foundation – basic data structure List, Set

[正] Redis – Basic data structure ZSet, Bitmap…

[From] Redis Foundation – summary of basic data structure

In the “Commitment” part, I will focus on the principles of Redis and talk about some relatively advanced features, such as pub/ SUB (publish/subscribe), persistence, high performance features, transactions, memory reclamation, etc. In the next part, I will lay the groundwork for you. Let’s string together the content of each chapter, so I don’t want to take up your preface.

Anyway, let’s take a look today at the Redis publish/subscribe model.

The body of the

Redis transactions

Redis. IO /topics/tran… Redisdoc.com/topic/trans…

Why use transactions

We know that Redis single command is atomic (such as get set mget mset). If multiple commands are involved and need to be treated as an indivisible processing sequence, transactions are needed.

For example, if we use setnx to implement distributed locks, we will set and then set expire for the key to prevent the lock from being released in case of del exception, and then del after the business is finished. We want these three actions to be executed as a set of commands.

Redis transactions have two characteristics:

  1. Execute in the order in which they are queued;

  2. Will not be affected by requests from other clients;

Redis transactions involve four commands: multi (start transaction), exec (execute transaction), discard (cancel transaction), and watch (monitor)

Usage of transactions

Case scenario: Tom and MIC have 1000 yuan each, and Tom needs to transfer 100 yuan to MIC.

Tom’s account balance decreases by 100 yuan, while Mic’s account balance increases by 100 yuan.

127.0.0.1:6379> set tom 1000
OK
127.0.0.1:6379> set mic 1000
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby tom 100
QUEUED
127.0.0.1:6379> incrby mic 100
QUEUED
127.0.0.1:6379> exec
1) (integer) 900
2) (integer) 1100
127.0.0.1:6379> get tom
"900"
127.0.0.1:6379> get mic
"1100"
Copy the code

Start the transaction through multi’s command. Transactions cannot be nested. Multiple multi commands have the same effect.

After the multi command is executed, the client can send any number of commands to the server. These commands are not executed immediately, but are placed in a queue. When the exec command is invoked, all commands in the queue will be executed.

Transactions are executed through exec’s commands. If exec is not executed, none of the commands are executed. What if you don’t want to execute a transaction halfway through?

Discard can be called to empty the transaction queue and abandon execution.

multi
set k1 1
set k2 2
set k3 3
discard
Copy the code

Watch command

A watch command is also provided in Redis.

It provides the CAS optimistic locking behavior (Check and Set/Compare and Swap) for Redis transactions, where multiple threads update a variable with its original value and update it to a new value only if it has not been modified by other threads.

We can use Watch to monitor one or more keys, and if at least one of the monitored keys is changed before exec executes after a transaction is started, the entire transaction will be cancelled (unless the key expires early). You can cancel with unwatch.

client 1 client 2
127.0.0.1:6379 > set the balance of 1000

OK

127.0.0.1:6379 > watch the balance

OK

127.0.0.1:6379 > multi

OK

127.0.0.1:6379 > incrby balance of 100

QUEUED
127.0.0.1:6379 > decrby balance of 100

(integer) 900
127.0.0.1:6379 > exec

(nil)

127.0.0.1:6379 > get the balance

“900”

Problems that the transaction may encounter

We divide the problems encountered by transaction execution into two types: errors occurring before and after exec execution.

An error occurred before exec execution

For example, the enqueue command has syntax errors, including parameter number, parameter name, etc. (compiler error).

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set ck 666
QUEUED
127.0.0.1:6379> hset ck 2673
(error) ERR wrong number of arguments for 'hset' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
Copy the code

In this case, the transaction is rejected, meaning that all commands in the queue are not executed.

An error occurred after exec execution

For example, a type error, such as a command that hashes a String, is a runtime error.

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> hset k1 a b
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get k1
"1"
Copy the code

Finally, we find that the command set k1 1 is successful, that is, in the case of runtime exceptions, only the wrong command is not executed, but other commands are not affected.

This obviously does not meet our definition of atomicity, that is, we cannot use the transaction mechanism of Redis to achieve atomicity and ensure data consistency.

Think about:

  • Why does Redis not roll back an error in a transaction?

We must start with the design of Redis. Those of you who have used relational databases may be wondering why Redis does not support transaction rollback. Let’s see if Redis needs to support rollback. We know that Redis commands fail only in syntactic cases (which Redis is able to detect during the process of putting the command on a transaction queue), and this problem does not typically occur in a production environment. And transaction rollback does not resolve any program errors. For example, if a query increments the value of a key by 2 instead of 1, or increments the wrong key, the transaction rollback mechanism does not solve these programmatic problems. Additionally, Redis has been simplified internally to ensure faster performance, since Redis does not require transaction rollback capabilities.

The Lua script

Lua/ˈluə/ is a lightweight scripting language written in C, similar to stored procedures for data.

Benefits of executing Redis commands using Lua scripts:

  1. Sending multiple commands at a time reduces network overhead.

  2. Redis will execute the entire script as a whole without being interrupted by other requests, keeping it atomic.

  3. For complex combined commands, we can put them in files and reuse command sets between programs.

Call the Lua script in Redis

Use the eval /ɪ’væl/ method with the syntax:

redis> eval lua-script key-num [key1 key2 key3 ....]  [value1 value2 value3 ....]Copy the code
  • Eval stands for executing Lua commands.

  • Lua-script stands for lua script content.

  • Key-num specifies the number of keys in a parameter. Note that in Redis, the key starts with 1. If there is no key parameter, write 0.

  • [key1 key2 key3…]. Key is passed to Lua as an argument, or it can be left blank, but it must correspond to the number of key-num.

  • [value1 value2 value3…] These parameters are passed to Lua and are optional.

Example, return a string with 0 arguments:

redis> eval "return 'Hello World'" 0
Copy the code

Call the Redis command in the Lua script

Call (command, key [param1, param2… ). Syntax format:

redis> eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
Copy the code
  • Command is a command, including set, get, and del.

  • Key is the key being operated on.

  • Param1, param2… Represents the parameter given to key.

Note that unlike Java, definitions have only tangible arguments and calls have only arguments.

Lua is called with key for parameters and argv for parameter values (arguments).

Set key-value pairs

Call the Lua script in Redis to execute the Redis command

redis> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 ck 2673 
redis> get ck
Copy the code

The above command is equivalent to set ck 2673.

Writing Lua scripts directly in Redis-CLI is not convenient, nor can you edit and reuse them. We usually put the scripts in a file and execute the file.

Call the command in the Lua script file in Redis to operate Redis

Create Lua script file:

cd /usr/local/soft/redis5. 0. 5/src
vim ck.lua
Copy the code

Lua script content, set first, then value:

redis.call('set'.'ck'.'lua666')
return redis.call('get'.'ck')
Copy the code

Call the Lua script in the Redis client

CD/usr/local/soft/redis5.0.5 / SRC redis - cli - eval ck. Lua 0Copy the code

Get the return value:

[root@localhost src]# redis-cli --eval ck.lua 0
"lua666"
Copy the code

Example: Perform traffic limiting for IP addresses

Requirement: Access Y times in X seconds.

Design idea: Use the key to record the IP address, and use the value to record the number of access times.

Once you get the IP, add 1 to the IP. If it is the first access, set the expiration time (parameter 1) for the key. Otherwise, the number of times exceeds the limit (parameter 2) and 0 is returned. Returns 1 if there are no more times. The key expires and can be accessed again.

KEY[1] is IP, ARGV[1] is expiration time X, and ARGV[2] is restricted access number Y.

–ip_limit.lua

–IP traffic limiting: Limits the IP traffic frequency and provides 10 access times in six seconds

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
Copy the code

Limit access to 10 times in 6 seconds, invoke test (10 times in a row) :

. / redis - cli - eval "ip_limit. Lua" app: IP: limit: 192.168.8.111, 6 to 10Copy the code
  • App: IP: limit: 192.168.8.111 is the key value, followed by parameter values, middle to add a space and a comma, coupled with a space.

/redis-cli -eval [lua script] [key…] Space, space [args…]

  • Multiple parameters are separated by a space.

Code: LuaTest. Java

Cache Lua scripts

  • Why cache

In the case of long scripts, if the entire script needs to be passed to the Redis server each time the script is invoked, there will be a large network overhead. To address this issue, Redis provides the EVALSHA command, which allows developers to execute scripts via SHA1 summaries of script content.

  • How to cache

When the script load command is executed, Redis will calculate the SHA1 summary of the script and record it in the script cache. When the EVALSHA command is executed, Redis will search for the corresponding script content from the script cache based on the summary provided. If the script is found, the script will be executed. “NOSCRIPT No matching script. Please use EVAL.”

127.0.0.1:6379 > script load "return" Hello World ", "" 470877 a599ac74fbfda41caa908de682c5fc7d4b" 127.0.0.1:6379 > evalsha "470877a599ac74fbfda41caa908de682c5fc7d4b" 0 "Hello World"Copy the code

Square case

Redis has autoincrement commands like incrby, but it doesn’t multiply itself, like by 3, by 5.

We can write a multiplication operation and multiply it by the following argument:

local curVal = redis.call("get", KEYS[1])
if curVal == false then
	curVal = 0
else
	curVal = tonumber(curVal)
end
	curVal = curVal * tonumber(ARGV[1])
	redis.call("set", KEYS[1], curVal)
	return curVal
Copy the code

Make the script a single line separated by a semicolon

local curVal = redis.call("get", KEYS[1]); if curVal == false 	then curVal = 0 else 	curVal = tonumber(curVal) end; curVal=curVal *tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVal
Copy the code

Script load ‘command’

127.0.0.1:6379> script load 'local curVal = redis. Call ("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal * tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVal' "be4f93d8a5379e5e5b768a74e77c8a4eb0434441"Copy the code

Call:

127.0.0.1:6379 > set 2 OK 127.0.0.1 num: 6379 > evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 num 1 June 12 (integer)Copy the code

Script timeout

Redis command execution itself is a single thread, this thread also needs to execute the client Lua script, if the Lua script execution times out or falls into an infinite loop, is there no way to provide services for the client?

eval 'while(true) do end' 0
Copy the code

To prevent Redis from running a script for too long, Redis provides the lua-time-limit parameter to limit the maximum running time of the script, which is 5 seconds by default.

Lua-time-limit 5000 (in the redis.conf configuration file)Copy the code

When the script runs longer than this limit, Redis will start accepting additional commands but will not execute them (to ensure atomicity of the script, since the script has not been terminated), but will return a “BUSY” error.

Redis provides a script kill command to abort script execution. Open a new client:

script kill
Copy the code

If the currently executing Lua script modifies Redis data (SET, DEL, etc.), the script kill command cannot be used to terminate the script execution.

127.0.0.1:6379> eval "redis. Call ('set','ck','666') while true do end" 0Copy the code

To ensure atomicity of script execution, if part of script execution terminates, it violates the requirement of atomicity of script execution. Ultimately, ensure that the script either executes at all or not at all.

127.0.0.1:6379> script kill (error) UNKILLABLE Sorry the script already executed write commands against the dataset. You  can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.Copy the code

In this case, redis can only be forcibly terminated with the shutdown nosave command.

The difference between the shutdown nosave and shutdown nosave is that the shutdown nosave does not persist, meaning that database changes that occurred since the last snapshot are lost.

Summary: If we have some special requirements, we can use Lua to implement them, but be aware of those time-consuming operations.

By the way

There is a problem? Can you leave me a message or chat privately? Just give it a thumbs up

Of course, you can also go to my official account “6 Xi Xuan”,

Reply to “Learn” and receive a copy of the Video tutorial for Advanced Architects for Java Engineers

Answer “interview”, can obtain:

MySQL brain Map MySQL brain map

There are [Ali cloud] [Tencent cloud] purchase discount oh ~ specific please contact me

The sunrise hin I am trained programmers, PHP, Android and hardware are done, but in the end or choose to focus on Java, so have what questions to ask the public for discussion (emotional pouring technology can ha ha ha), see words will reply as soon as possible, hope can with everyone common learning progress, about the server architecture, Java core knowledge analysis, career, interview summary and other articles will be pushed irregularly output, welcome to pay attention to ~~~