This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
The problem
Redis large key is a headache. If a large key appears on the Redis line, del cannot be executed immediately, because deleting the large key will cause blocking. During the blocking period, all requests may cause timeout. When the timeout is increasing, new requests will continue to come in, which will cause the Redis connection pool to be exhausted, causing various online services that depend on Redis to be abnormal.
Here’s a simple test
The script first writes a large amount of data to Redis:
127.0.0.1:6379> hlen hset_test
(integer) 3784945
Copy the code
Here we see more than 3 million data, let’s perform a del to see:
127.0.0.1:6379> del hset_test
(integer) 1
(3.90s)
Copy the code
You can see that it takes about 4s. We know that the Redis core is single-threaded, so redis is unable to process other requests during this block.
Low peak deletion
The simplest way is to delete the data during the low business peak. For example, most scenarios are deleted at around 4 a.m. Of course, this approach cannot avoid requests during blocking, and is generally applicable to businesses with very small QPS during execution.
Scan for partial
Since the big key cannot be deleted all at once, we will delete it in batches.
hset
For hSET, we hSAN delete in batches.
HSCAN key 0 COUNT 100 HDEL key fieldsCopy the code
Take 100 at a time and delete them
set
For a set, we can take a random batch of data at a time and delete it
SRANDMEMBER key 10 SREM key fieldsCopy the code
zset
With zSet, you can simply delete a batch of data at a time
ZREMRANGEBYRANK key 0 10Copy the code
list
For list, just pop
I :=0 for {lpop key I ++ if I %100 == 0 {sleep(1ms)}}Copy the code
Asynchronous delete
Expired key deletion policy
Some people say that since deleting a large key online will cause blocking, then set a TTL for this key and give redis to delete it by itself. Let me first look at redis’ expired key removal policy:
Regular deletion:
We know redis has expired keys and permanent keys. Redis keeps a separate list of keys in the dictionary. Redis knows that keys in the dictionary can expire at any time, so I will come to deal with them regularly. By default, the command is executed every 100ms. Each time the serveCron is executed, it will randomly select some keys from the TTL keys and check them. If the keys are expired, it will perform synchronous deletion. Reasons for random check:
- Impossible to check all, block thread
- Random words are fair to some extent
Lazy to delete
By periodically deleting keys, we can delete a batch of expired keys each time. However, if a key has expired and is not cleared after periodic deletion, the user who reads the key cannot return the key directly. In this case, the user will also check whether the key has expired.
Elimination strategy
- Noeviction: Will return an error when memory usage exceeds the configuration and will not expel any keys
- Allkeys-lru: Expels the longest unused keys via the LRU algorithm
- Volatile -lru: Expunts the longest unused key from the set of keys with an expiration time set by the LRU algorithm
- Allkeys-random: deletes allkeys randomly
- Volatile-random: Banishes randomly from a set of expired keys
- Volatile-ttl: Expels keys that are about to expire from keys with expiration time
- Volatile -lfu: Expels the least frequently used key out of all keys with expiration time
- Allkeys-lfu: expel the least frequently used key from allkeys
The obsolescence policy is a flexible option. Generally, the appropriate obsolescence policy is selected according to the business. When is the customized obsolescence policy triggered? When we add a key or update a bigger key. So its deletion is also synchronous, and if it happens to eliminate a large key, unfortunately it will also currently block.
Bottom line: No matter which of the above three triggers a delete, it is synchronized. So even if you add TTL, redis will be deleted synchronously, and the big key will still block.
Asynchronous delete
In redis4.0, the author also considers the blocking problem caused by large key deletion, so there is asynchronous deletion, which can be divided into user active and program passive.
Take the initiative to delete
unlink
For active deletion, Redis provides unlink as an alternative to del. When we are in unlink, Redis will first check the number of elements to be deleted (such as collection). If the number of elements in the collection is less than or equal to 64, it will directly perform synchronous deletion, because this is not a big key and will not waste a lot of overhead. However, when the number of keys exceeds 64, Redis will consider that there is a high probability that it is a large key, and redis will delete the key from the dictionary first, and the actual value will be handed over to the asynchronous thread, which will not affect the main thread at all.
Flushall, flushdb
Flushall [ASYNC] : flushdb; flushall [ASYNC] : flushdb; flushall [ASYNC] : flushdb; flushall [ASYNC] : flushDB; flushall [ASYNC] : flushDB; The old dictionary is left to asynchronous threads to slowly delete.
Passive delete
Redis Configures policies
- Lazyfree-lazy-eviction: Asynchronous deletion will be initiated when Redis has a memory deletion policy set to maxMemory. The disadvantage of asynchronous deletion in this scenario is that the memory will not be released if the memory is not deleted in time.
- Lazyfree-lazy-expire: Specifies an asynchronous thread to delete a key with a TTL when redis clears it.
- Up – lazy – flush: When the slave node joins the replication pool, it flushes its data. If it takes a long time to flush, more data will accumulate in the replication buffer. It takes a long time for the slave to synchronize data. Slave flush can be handled by asynchronous off-the-shelf to improve synchronization speed.
- lazyfree-lazy-server-del: This option is used for directives such as rename a field
RENAME key newkey
If newKey exists, rename will delete the value of newKey. If newKey is a large key, this will block. When this option is enabled, it will also be handed over to an asynchronous thread, which will not block the main thread.
As an aside: Rename
Let’s start with a test:
127.0.0.1:6379> set A 1 OK 127.0.0.1:6379> eval "for I =1, 10000000000,1 do redis.call('hset','B', I,1) end" 0 (15.89s)Copy the code
- Set A to 1
- Add 1000W of data to B
If you want to rename A to B, run the rename A B command
127.0.0.1:6379> rename A B
OK
(11.07s)
Copy the code
It is found that the block is caused by redis deleting B. If there is a scene with rename, it must pay attention to whether newKey already exists and whether newkey is a big key.