If you want to use Redis, Lua scripts cannot be avoided
Preface
As a server, the memory is not unlimited, there's always the case of memory exhaustion, then when Redis
the server out of memory, if it continues to perform the requested command, Redis
will be how to handle it?
Memory reclamation
When using the Redis
service, in many cases, some key-value pairs will only be valid for a certain period of time. In order to prevent this type of data from occupying memory all the time, we can set the validity period for the key-value pairs. Redis
It may be provided through 4
a separate command to set the expiration time to a key:
expire key ttl
: Thekey
expiration time value is setttl
in seconds .pexpire key ttl
: Thekey
expiration time value is setttl
in milliseconds .expireat key timestamp
: Thekey
expiration time value to the specifiedtimestamp
number of seconds .pexpireat key timestamp
: Thekey
expiration time value to the specifiedtimestamp
number of milliseconds .
PS: No matter which command to use, and ultimately Redis
the bottom are using the pexpireat
command to achieve. Further, set
commands may be provided key
at the same time plus the expiration time, it can guarantee atomicity set value, and a time of expiration.
After setting the expiration date, can ttl
and pttl
(if you do not set the expiration time of the command returns the following two commands to query the two remaining expiration time -1
, if you set an invalid expiration time, then return -2
):
ttl key
Returns thekey
remaining number of seconds expired.pttl key
Returns thekey
number of milliseconds remaining expired.
Expiration strategy
If you delete an expired key, we generally have three strategies:
- Timed deletion: Set a timer for each key, once the expiration time is up, delete the key. This strategy is very friendly to memory, but
CPU
unfriendly, because each timer will take up someCPU
resources. - Lazy deletion: No matter whether the key has expired or not, it will not be deleted actively, and it will be judged whether it is expired every time the key is obtained. If it expires, the key will be deleted, otherwise the value corresponding to the key will be returned. This strategy is not memory friendly and may waste a lot of memory.
- Regular scan: The system scans regularly at regular intervals, and deletes expired keys. This strategy is relatively a compromise between the above two strategies. It should be noted that this regular frequency should be controlled in accordance with the actual situation. The use of this strategy has a drawback that keys that have expired may also be returned.
In Redis
them, their choice is a policy 2
and strategy 3
integrated use. But Redis
periodically scanning only Scan button set expiration time, because the key to set the expiration time Redis
will be stored separately, the situation scans all keys so it will not appear:
typedef struct redisDb {
dict *dict; //所有的键值对
dict *expires; //设置了过期时间的键值对
dict *blocking_keys; //被阻塞的key,如客户端执行BLPOP等阻塞指令时
dict *watched_keys; //WATCHED keys
int id; //Database ID
//... 省略了其他属性
} redisDb;
8 elimination strategies
If Redis
among all the keys have not expired, and this time the memory is full, then the client continues to execute set
when such orders Redis
will be how to deal with it? Redis
It provides different elimination strategies to deal with this scenario.
First Redis
provides a parameter maxmemory
to configure the Redis
maximum memory usage:
maxmemory <bytes>
Command or may config set maxmemory 1GB
be dynamically modified.
If this parameter is not set, then the 32
operating system bit in Redis
use up 3GB
memory in 64
the operating system is not limited in the position.
Redis
Provides 8
seed out of policy, parameters can be maxmemory-policy
configured:
Elimination strategy | Description |
---|---|
volatile-lru | The keys with expiration time are deleted according to the LRU algorithm until free space is available. If there is no key object that can be deleted, and the memory is still not enough, an error will be reported |
allkeys-lru | Delete all keys according to the LRU algorithm until free space is available. If there is no key object that can be deleted, and the memory is still not enough, an error will be reported |
volatile-lfu | According to the LFU algorithm, delete the key with expiration time until free space is available. If there is no key object that can be deleted, and the memory is still not enough, an error will be reported |
allkeys-lfu | Delete all keys according to the LFU algorithm until free space is available. If there is no key object that can be deleted, and the memory is still not enough, an error will be reported |
volatile-random | Randomly delete keys with an expiration time until free space is available. If there is no key object that can be deleted, and the memory is still not enough, an error will be reported |
allkeys-random | Randomly delete all keys until free space is available. If there is no key object that can be deleted, and the memory is still not enough, an error will be reported |
volatile-ttl | According to the ttl attribute of the key-value object, delete the data that will expire recently. If not, report an error directly |
noeviction | The default strategy, do not do any processing, directly report an error |
PS: elimination strategy also can be used as a command config set maxmemory-policy <策略>
to be dynamically configured.
LRU algorithm
LRU
Full Least Recently Used
name: . That is: the longest time it has not been used recently. This is mainly for use time.
Redis's improved LRU algorithm
In Redis
them, we do not use the traditional LRU
method, because of the traditional LRU
existence algorithm 2
problem:
- Need extra space for storage.
- There may be some
key
value to use very often, but recently not used, therebyLRU
algorithms deleted.
In order to avoid the above 2
problem, Redis
among the traditional LRU
algorithm was modified to delete by sampling method .
Provides a configuration file attribute maxmemory_samples 5
, the default value is 5
expressed randomly selected 5
a key
value, then these 5
two key
values in accordance with the LRU
deletion algorithm, so obviously, key
the larger the value, the higher the deletion of accuracy.
Sampling of the LRU
algorithm and the conventional LRU
algorithm, Redis
the official website of which there is a comparison chart:
-
The light gray band is the deleted object.
-
The gray bands are objects that have not been deleted.
-
Green is the added object.
The first figure represents the upper-left corner of the traditional LRU
algorithm, we can see that when the number of samples reaches 10
a (upper right corner), and has been the traditional LRU
algorithm is very close.
How Redis manages heat data
When we describe a String object in front, it referred to the redisObject
object placed in a lru
property:
typedef struct redisObject {
unsigned type:4;//对象类型(4位=0.5字节)
unsigned encoding:4;//编码(4位=0.5字节)
unsigned lru:LRU_BITS;//记录对象最后一次被应用程序访问的时间(24位=3字节)
int refcount;//引用计数。等于0时表示可以被垃圾回收(32位=4字节)
void *ptr;//指向底层实际的数据存储结构,如:SDS等(8字节)
} robj;
lru
The properties are written when the object is created, and updated when the object is accessed. Normal people's thinking is that the final decision whether to delete a key must be subtracted with the current timestamp lru
, and the one with the largest difference will be deleted first. But Redis
there is not to do so, Redis
in maintaining a global property lru_clock
, this property is through a global function serverCron
every 100
milliseconds time to update the record of the current unix
time stamp.
Finally decided to delete the data by lru_clock
subtracting the object lru
attributes derived. So why Redis
bother? Wouldn't it be more accurate to take the global time directly?
This is because doing so avoids updated each object lru
attributes when you can take directly to global attributes, without the need to call system function to obtain the system time, so as to enhance the efficiency ( Redis
of which there are many details to consider to improve this performance, it can be said It is to optimize the performance as much as possible to the extreme).
But there's a problem, we see that the redisObject
object lru
property only 24
bits, 24
bits can only store 194
a time stamp size-day, more than once 194
will be restarted from the day after the 0
start of computation, so this time it may appear redisObject
object lru
attributes greater than the global lru_clock
attributes of the case.
Because of this, the calculation of the time also need to be divided into 2
cases:
- When global
lruclock
>lru
, uselruclock
-tolru
get free time. - When the global
lruclock
<lru
is usedlruclock_max
(i.e.,194
days) -lru
+lruclock
obtain idle time.
It should be noted that this calculation method does not guarantee that the longest idle time can be deleted from the sampled data. This is because the first over 194
the case few days is not being used, again only the lruclock
first 2
round continues over lru
when the property is calculated will go wrong.
Such as object A
record lru
is 1
days, while lruclock
the second round are to 10
-day, and this time it will lead to results only 10-1=9
days, in fact, it should be the 194+10-1=203
day. But this kind of situation can be said to happen rarely, so this processing method may be deleted inaccurately, but this algorithm itself is an approximate algorithm, so it will not have much impact.
LFU algorithm
LFU
Full Least Frequently Used
name: . That is: the least frequently used recently, this is mainly for the frequency of use. This property is also recorded in redisObject
the lru
internal attributes.
When we use LFU
the recovery strategy, lru
attributes the high 16
bits used to record the access time (last decrement time: ldt, in minutes), low 8
bit is used to record the frequency of access (logistic counter: logc), referred to counter
.
Increasing frequency of visits
LFU
Each key only counter 8
position, it represents the maximum value 255
, it Redis
uses a probability based on the number of devices to achieve counter
annually. r
Given an old access frequency, when a key is accessed, it counter
is incremented as follows:
- Extraction
0
and1
random number betweenR
. counter
-Initial value (default is5
), get a basic difference value, if this difference value is less than0
, then take it directly0
, for the convenience of calculation, record this difference value asbaseval
.- Probability
P
is calculated as1/(baseval * lfu_log_factor + 1)
follows: . - If
R < P
, the frequency incrementcounter++
( ).
The formula lfu_log_factor
is called a logarithmic factor, by default 10
, you may be controlled by the parameters:
lfu_log_factor 10
The figure below is a logarithmic factor lfu_log_factor
and frequency counter
graph growth:
You can see that when the logarithmic factor lfu_log_factor
is 100
time, probably the 10M(1000万)
visit will visit will counter
grow to 255
, and the default 10
can support up to 1M(100万)
visits counter
in order to reach 255
the upper limit, which in most of the scenes are enough to meet demand.
Decreasing frequency of visits
If the access frequency counter
only been incremental, then sooner or later to all 255
, that counter
has been incremented by a not fully reflect the key
heat, so when one key
after a period of time is not being accessed, counter
also needs a corresponding decrease.
counter
The rate of reduction by the parameter lfu-decay-time
control, default 1
, in minutes. The default value is 1
expressed: N
there is no access within minutes and counter
will reduce N
.
lfu-decay-time 1
The specific algorithm is as follows:
- Get current timestamp, converted to minutes after taking the low
16
position (for convenience of subsequent calculations, the value is referred tonow
). - Remove the object
lru
high attribute16
bit (for convenience of subsequent calculations, the value is referred toldt
). - When
lru
>now
, the default is one cycle (16
bit, maximum65535
), then take the difference65535-ldt+now
: whenlru
<=now
, take the differencenow-ldt
(for the convenience of subsequent calculations, this difference is recorded asidle_time
). - Remove the profile
lfu_decay_time
value is then calculated:idle_time / lfu_decay_time
(For convenience of subsequent calculations, the value is referred tonum_periods
). - Finally, the
counter
reduction:counter - num_periods
.
Look so complex, in fact, one sentence formula: Remove the current timestamp and the object lru
attribute comparison, calculate how long the current is not accessed, such that the calculated results 100
minutes is not accessed, removed and then the configuration parameters lfu_decay_time
If this configuration defaults 1
that is, 100/1=100
on behalf of 100
minutes did not visit, so counter
reduced 100
.
to sum up
This paper describes the Redis
process of key policy expired, and when the server memory is not enough Redis
of 8
species elimination strategy, finally introduced Redis
two main elimination algorithm LRU
and LFU
.