[TOC]
1. Redis 简介
redis 全称 Remote Dictionary Service(近程字典服务)。Redis 存储的都是 K - V 对,Redis 里寄存的任何一条数据都有惟一的 key 作为名称。Redis 中,所有的 key 都存储在一个很大的字典中。一个 Redis 实例有多个库(database),可用过配置文件里的“databases”配置,库索引从 0 到 databases-1,能够应用指令 SELECT <index>
切换到不通的库。
留神:在 Redis 命令行,Redis 的指令不辨别大小写,然而 key 辨别大小写。
2. Redis 根底数据结构
Redis 有五种根底的数据结构:string、list、hash、set、zset。其中 list、hash、set、zset 称为容器型数据结构,容器型数据结构有两条通用规定:
- 如果容器不存在,就创立一个,再进行操作。
- 如果容器里没有数据了,就立刻删除,回收内存。
2.1 string(字符串)
string 是 Redis 最简略的数据结构,一个 key 对应一个 value。Redis 的 string 实际上是应用数组实现的。
string 根本指令有:
- SET:设置一个 kv 对,示例:
SET name lily
- GET:获取一个 key 对应的的 value,示例:
GET name
- MSET:设置一个或多个 kv 对,示例:
MSET name lily age 18
- MGET:获取一个或多个 key 对应的 value,示例:
MGET name age
- SETNX:设置一个 kv 对,如果该 key 不存在,设置胜利;如果改成存在,设置失败,示例:
SETNX name lilei
- INCR:如果 value 是整数,还能够进行原子加一操作,示例:
INCR age
- DECR:如果 value 是整数,还能够进行原子减一操作,示例:
DECR age
2.2 list(列表)
Redis 的 list 是链表实现的,所以其插入删除操作十分快,工夫复杂度都是 O(1),然而索引定位很慢,工夫复杂度是 O(n)。当 list 中没有元素了,该数据结构会主动删除,内存被回收。
list 的根本指令有:
- LPUSH:从右边插入元素,能够插入多个。示例:
LPUSH students lily lilei zhangsan
- LPOP:从右边删除元素,默认删除一个元素。能够指定须要删除的个数,示例:
LPOP students 5
- RPUSH:从左边插入元素,用法同
LPUSH
- RPOP:从左边删除元素,用法同
LPOP
- LRANGE:列出指定范畴里的元素(不会删除元素),示例:
LRANGE students 0 -1
- LINDEX:(工夫复杂度 O(n))获取指定地位的元素,示例:
LINDEX students 1
- LTRIM:(工夫复杂度 O(n))删除指定范畴的元素,示例:
LTRIM students 1 2
list 罕用场景:
-
队列 / 音讯队列
从 list 的一边 push 数据,从另一边 pop 数据,就能够作为队列或者音讯队列应用。如果 list 外面没有数据,
LPOP
和RPOP
不会阻塞,都会立刻返回空。如果用作音讯队列,消费者须要解决LPOP
或RPOP
立刻返回空的状况,个别的做法是如果返回为空,期待一段时间,再次获取
,然而等待时间不好确定,等待时间太短会拉高 CPU,造成节约;等待时间太长会升高 QPS,影响性能。Redis 为 list 设计了阻塞读指令:BLPOP
、BRPOP
,指令用法是BLPOP key1 [key2 ...] timeout
,这里BLPOP/BRPOP
里的B
是blocking
的意思。阻塞读在 list 没有数据的时候,会立刻休眠,一旦有数据到了,则立即醒过来。 -
栈
在 list 的同一边 push 和 pop 数据,就能够作为栈应用
-
音讯队列
LPOP
或者RPOP
操作不会阻塞,如果 list 外面没有数据,LPOP
和RPOP
都会立刻返回空,如果
2.3 hash(字典)
Redis 的 hash 的值只能是字符串。
hash 的根本指令:
- HMSET:写入数据,反对批量。示例:
HMSET 001 name lily age 18
,其中 001 是该条 hash 的 key,name、age 是 hash 外面的 field。 - HMGET:获取指定 hash 的某个 field 的 value。示例:
HMGET 001 name
- HGETALL:获取指定 hash 的所有数据。示例:
HGETALL 001
- HINCRBY:如果 hash 外部的 value 是整型,能够应用
HINCRBY
进行原子加减操作(只需讲操作数设置为正数就是减法操作),示例:HINCRBY 001 age 1
,HINCRBY 001 age -1
2.4 set(汇合)
Redis 的 set 是无序的。
set 的根本指令:
- SADD:退出汇合。示例:
SADD students lily lilei
- SMEMBERS:列出汇合内成员。示例:
SMEMBERS students
- SISMEMBER:查问某个成员是否存在。示例:
SISMEMBER students lily
- SREM:删除某个成员。示例:
SREM students lily
- SCARD:获取汇合长度。示例:
SCARD students
- SPOP:弹出成员,能够指定须要弹出的个数(不晓得默认是 1)。示例:
SPOP students 5
2.5 zset(有序汇合)
Redis 的 zset 是一个很有特色的数据结构,一方面它是一个 set,保障了外部 value 的唯一性,另一方面它能够给每个 value 赋予一个 score,代表这个 value 的排序权重。它的外部实现采纳的是”跳跃列表(skiplist)“。
zset 根本指令:
- ZADD:减少成员。示例:
ZADD students 95 lily 85 lilei 70 zhangsan 55 lisi
- ZRANGE:正序获取指定个数的成员。示例:
ZRANGE students 0 -1
- ZREVRANGE:倒序获取指定个数的成员。示例:
ZREVRANGE statuents 0 -1
- ZRANGEBYSCORE:依据 score 范畴获取成员,正序排列。示例:
ZRANGEBYSCORE students 0 60
- ZREVRANGEBYSCORE:依据 score 范畴获取成员,顺叙排列。示例:
ZREVRANGEBYSCORE students 100 0
- ZREM:删除指定成员。示例:
ZREM students zhangsan
- ZREMRANGEBYSCORE :按 score 范畴删除成员。示例:
ZREMRANGEBYSCORE students 0 60
- ZSCORE:获取成员 score。示例:
ZSCORE students lilei
- ZCARD:获取 zset 长度。示例:
ZCARD students
3. Redis 通用指令
3.1 EXPIRE
EXPIRE指令用于设置超时,某个 key 超时后会主动删除。示例:EXPIRE students 10
,10 秒后 students 将被主动删除。
超时相干指令有:
- TTL:查看残余过期工夫,单位是秒。示例:
TTL students
- PTTL:查看残余过期工夫,单位是豪秒。示例:
PTTL students
3.2 KEYS
KEYS获取以后库所有的或者特定的 key 列表,反对通配符”*“。示例:KEYS *
查问全副 key。
3.3 SCAN
KEYS
能够获取 key 列表,然而有两个显著的毛病:
- 没有 offset、limit 参数,一次性返回所有满足条件的 key。
-
KEYS
算法是遍历算法,复杂度是 O(n),如果以后库中 key 很多,应用KEYS
会导致 Redis 卡顿。 Redis 提供了
SCAN
指令,SCAN
相比KEYS
有一下特点:- 复杂度尽管也是 O(n),然而它是通过游标分步进行的,不会阻塞线程。
- 提供 limit 参数,能够管制每次返回后果的最大条数。
- 提供模式匹配性能。
- 服务器不须要为游标保留状态,游标的惟一状态就是
SCAN
返回给客户端的游标整数。 - 返回的后果可能会有反复,须要客户端去重。
- 遍历的过程中如果有数据批改,改变后的数据能不能遍历到是不确定的。
- 单词返回的后果是空的并不意味着遍历完结,而要看返回的游标值是否为零。
SCAN
用法:SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
,示例:scan 0 match s* count 5 type string
。SCAN
有四个参数,第一个是 cursor 整数值(相当于 offset),第二个是 key 的正则模式,第三个是 count(相当于 limit),第四个是 key 的类型。第一次遍历时 cursor 值为 0,而后将返回后果中的第一个整数值作为下一次遍历的 cursor,始终遍历到返回的 cursor 值为 0 时完结。须要留神的是,每次 SCAN
返回的个数不肯定是 count,因为这个 count 不是限定返回后果的数量,而是限定服务器单次遍历的字典槽位数量,只有返回的 cursor 不是 0,就示意遍历还没完结。
3.4 INFO
通过 INFO
指令,能够清晰地看到 Redis 外部一系列运行参数,如下:
127.0.0.1:6379> info
# Server
redis_version:6.2.1
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:fa5dfffb0053744e
...
# Clients
connected_clients:2
cluster_connections:0
maxclients:10000
...
# Memory
used_memory:895736
used_memory_human:874.74K
used_memory_rss:8294400
used_memory_rss_human:7.91M
...
# Persistence
loading:0
current_cow_size:0
current_fork_perc:0.00%
...
# Stats
total_connections_received:16
total_commands_processed:140
instantaneous_ops_per_sec:0
...
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:b81735795edbd0987a13af0b42d5896e0c87e10c
...
# CPU
used_cpu_sys:178.131199
used_cpu_user:250.434150
used_cpu_sys_children:0.009278
...
# Modules
# Errorstats
errorstat_ERR:count=15
# Cluster
cluster_enabled:0
# Keyspace
db0:keys=1,expires=0,avg_ttl=0
db1:keys=2,expires=0,avg_ttl=0
INFO
指令能够查看某一方面地数据,如下:
127.0.0.1:6379> info clients
# Clients
connected_clients:2
cluster_connections:0
maxclients:10000
...
127.0.0.1:6379> info cpu
# CPU
used_cpu_sys:178.614214
used_cpu_user:251.278258
used_cpu_sys_children:0.009278
...
3.5 其余指令
- TYPE,查看数据类型。实例:
TYPE students
- SAVE:长久化。
- BGSAVE:后盾长久化。
4. Redis 高级性能
4.1 分布式锁
锁的通用应用办法:“获取锁 – 业务逻辑 – 开释锁”。应用 Redis 实现分布式锁的实质就是”占位“,具体实现是应用 SETNX
操作同一个 key。哪个过程可能 SETNX
胜利,就示意失去了锁(因为一旦 key 存在了,其余过程 SETNX
就会失败)。为了避免过程获取锁后在开释锁之前解体,会应用 EXPIRE
给锁设置个超时。为了避免过程在 SETNX
之后,EXPIRE
之前解体,须要保障 SETNX
和EXPIRE
的执行是原子化的。SET
指令能够实现,示例:SET lock true ex 10 nx
。
应用 Redis 做分布式锁须要留神的问题:
- 如果过程在加锁和锁超时之间逻辑执行的工夫太长,过程自身可能会呈现问题,这个须要依据业务解决。而且锁超时之后,其余过程就能够获取到锁,然而如果这时前一个过程开释锁,就可能把第二个过程获取到的锁开释掉。解决这个问题,就须要在
SET
设置锁的时候把 value 设置成一个随机数,开释锁的时候先匹配随机数是否统一,而后再删除 key。
4.2 延时队列
能够应用 Redis 的 zset 实现简略的延时队列,思维很简略,就是把 score 设置成以后工夫戳加上延时时长,而后有一个轮循,应用 ZRANGEBYSCORE
获取小于以后工夫戳的元素。如果没有到期,是获取不到放入的元素的。
用法示例:
client = redis.Redis(host="10.53.3.62")
now = int(time.time())
print("start", now)
client.zadd("tasks", {"task1": now + 5})
while 1:
a = client.zrangebyscore("tasks", 0, int(time.time()))
print(int(time.time()), a)
time.sleep(1)
# 输入:(能够看到,5s 之后能力获取到数据,达到了延时成果)start 1618295567
1618295567 [b'+']
1618295568 [b'+']
1618295569 [b'+']
1618295570 [b'+']
1618295571 [b'+']
1618295572 [b'+', b'task1']
1618295573 [b'+', b'task1']
1618295574 [b'+', b'task1']
4.3 位图
Redis 提供了位图数据结构,实际上就是 string。Redis 提供了以下指令操作位图:
- SETBIT:设置某位的值。示例:
SETBIT w 1 1
,示意给w
的第 1 位设置为 1。 - GETBIT:获取某位的值。示例:
GETBIT w 1
,示意获取w
的第 1 地位。 - BITCOUNT:统计制订范畴内 1 的个数。示例:
BITCOUNT w 0 1
- BITPOS:查找指定范畴内呈现的第一个 0 或 1。用法:
BITPOS key bit [start] [end]
,示例:BITPOS w 1 0 1
- BITFIELD:反对一次操作多个位。
BITFIELD
有三个子指令,别离是GET/SET/INCRBY
,它们都能够对指定位片段进行读写,然而最多只能解决 64 个间断的位,如果超过 64 位,就得应用多个子指令,BITFIELD
能够一次执行多个子指令。
4.4 HyperLogLog
HyperLogLog 数据结构是 Redis 的高级数据结构。HyperLogLog 提供不准确的去重计数计划,用于不须要准确统计的统计场景。HyperLogLog 不保留源数据,只去重并统计个数。HyperLogLog 没有提供相似查找的办法,不能应用 HyperLogLog 判断某个元素是否存在。
HyperLogLog 用法示例:
- PFADD:退出元素(会去重)。示例:
PFADD students lily
- PFCOUNT:统计个数。示例:
PFCOUNT students
4.5 布隆过滤器
布隆过滤器用于判断某个元素是否存在。当布隆过滤器说这个元素存在时,这个元素可能存在;当它说这个元素不存在时,那就必定不存在。Redis 提供了反对布隆过滤器的插件,须要装置该插件,能力应用。
用法示例:
- BF.ADD:退出元素。示例:
BF.ADD students lily
- BF.EXISTS:判断元素是否存在。示例:
BF.EXISTS students lilei
- BF.MADD:批量退出元素。示例:
BF.MADD students zhangsan lisi wangwu
- BF.MEXISTS:判断多个元素是否存在。示例:
BF.MEXISTS students zhangsan lisi wangwu
布隆过滤器的原理
每个布隆过滤器对应到 Redis 的数据结构外面就是一个大型的位数组和几个不一样的无偏 hash 函数。向布隆过滤器种增加 key 时,会应用多个 hash 函数对 key 进行 hash 算得一个整数索引值而后对数组长度取模失去一个地位,每个 hash 函数都会算得一个不同得地位,再把位数组得这几个地位都置为 1 就实现了 add 操作。所以查问 key 是否存在时,即便 hash 后的所有位都为 1,也不肯定存在,可能有的地位的 1 是其余 key 的 hash 后果,然而如果 hash 后的地位中有某个地位的后果不是 0,那就示意该 key 肯定不存在。
4.6 zset 简略限流
能够应用 Redis 的 zset 限度某个用户在规定的工夫内容许拜访的次数。根本思维也不简单:就是能获取到一个时间段范畴的次数统计。应用 zset 的 ZRANGEBYSCORE
指令就能够做到。用户没拜访一次,就往 zset 外面退出一个元素,score 应用工夫戳(要准确的话能够应用毫秒级的),value 只有惟一就行,简略起见,能够和 score 一起应用毫秒级工夫戳。通常只须要保留规定工夫范畴内的拜访次数,如果规定工夫以前的次数不须要存储,能够应用 ZREMRANGEBYSCORE
删掉规定工夫之前的数据。
用法示例:
client = redis.Redis(host="10.53.3.62")
def is_action_allowed(user_id, action_key, period, max_count):
"""指定用户 user_id 的某个行为 action_key 在特定的工夫内 period 只容许产生肯定的次数 max_count"""
key = 'hist:%s:%s' % (user_id, action_key)
now_ts = int(time.time() * 1000) # 毫秒工夫戳
with client.pipeline() as pipe:
pipe.zadd(key, {now_ts: now_ts}) # 记录拜访次数
pipe.zremrangebyscore(key, 0, now_ts - period * 1000) # 移除老旧数据
pipe.zcard(key) # 获取规定工夫内的次数
_, _, current_count = pipe.execute()
return current_count <= max_count
for i in range(20):
can_reply = is_action_allowed("lily", "request", 60, 10)
print(i, can_reply)
# ======= 输入:0 True
1 True
2 True
3 True
4 True
5 True
6 True
7 True
8 True
9 True
10 True
11 True
12 True
13 True
14 False
15 False
16 False
17 False
18 False
19 False
4.7 GeoHash
Redis 提供了地理位置模块 GeoHash,理论是应用 zset 存储的。
GeoHash 用法:
- GEOADD:减少地位。
- GEODIST:计算间隔。
- GEOPOS:查问经纬度,可批量。
- GEOHASH:获取元素 geohash 值,能够应用该 geohash 值到网站 http://geohash.org/${hash}直 …
- GEORADIUSBYMEMBER:获取元素左近的其余元素。
- GEORADIUS:依据坐标值查问左近的元素。
示例:
127.0.0.1:6379> GEOADD sz 114.035529 22.615108 beizhan
(integer) 1
127.0.0.1:6379> GEOADD sz 113.821705 22.638172 jichang
(integer) 1
127.0.0.1:6379> GEODIST sz beizhan jichang km
"22.1019"
127.0.0.1:6379> GEOPOS sz beizhan
1) 1) "114.03552979230880737"
2) "22.61510789529109644"
127.0.0.1:6379> GEOHASH sz beizhan
1) "ws10duyns30"
127.0.0.1:6379> GEORADIUSBYMEMBER sz beizhan 30 km count 3 asc
1) "beizhan"
2) "jichang"
127.0.0.1:6379> GEORADIUSBYMEMBER sz beizhan 20 km count 3 asc
1) "beizhan"
127.0.0.1:6379> GEORADIUS sz 113.821705 22.638172 23 km withdist count 3 asc
1) 1) "jichang"
2) "0.0001"
2) 1) "beizhan"
2) "22.1018"
4.8 PubSub
Redis 的 list 能够做为简略的音讯队列应用,然而它不反对多播。Redis 提供了 PubSub,一个简略的音讯队列,反对多播。它的用法比较简单,就是订阅和公布。PubSub 指令:
- SUBSCRIBE:订阅,反对模式匹配,反对批量订阅。
- PUBLISH:公布。
PubSub 毛病:
PubSub 的音讯不会长久化,如果没有消费者,生产者公布的音讯会丢掉。
5. Redis 进阶篇
5.1 长久化
Redis 提供了两种长久化机制:一种是 RBD(Redis Database),即快照;另一种是 AOF(Append Only File)日志追加。RBD 是一次全量备份,AOF 日志是间断的增量备份。RBD 是内存数据的二进制序列化模式,在存储上十分紧凑,而 AOF 日志记录的是内存数据批改的指令记录文本。AOF 日志会缓缓变大,所以须要定期进行 AOF 重写,给 AOF 日志进行瘦身。
5.1.1 RBD
Redis 应用操作系统的多过程 COW(Copy On Write,写时复制)机制来实现快照长久化。Redis 在长久化时 fork 一个子过程,快照长久化齐全交给子过程来解决,父过程持续解决客户端申请。子过程刚刚产生时,和父过程共享内存外面的代码段和数据段,当父过程接管到客户端申请须要批改数据时,会利用 COW 机制将须要批改的页复制一份进行批改,不会扭转原来的页,所以子过程看到的还是它本人产生的那一瞬间的数据(这就是叫这种长久化机制为”快照“的起因),能够安心的遍历数据进行长久化。
5.1.2 AOF
AOF 日志存储的是 Redis 服务器的程序指令序列,AOF 日志只记录对内存进行批改的指令记录。Redis 复原的时候,只须要依据 AOF 日志”重放“就行了。
Redis 在长期运行过程中,AOF 日志会越变越大,所以须要对 AOF 日志进行瘦身。Redis 提供了 bgrewirteaof
指令用于对 AOF 日志进行瘦身。其原理就是开拓一个子过程对内存进行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化结束后再将操作期间产生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加结束后就立刻代替旧的 AOF 日志文件,瘦身工作就实现了。
当 Redis 对 AOF 日志文件进行写操作时,实际上是将内容写到了内核为文件描述符调配的一个内存缓存中,而后内核会异步将脏数据刷回到磁盘。Redis 提供了不同的刷盘策略:
- 定时刷盘,工夫距离可配。(如果须要开启长久化,个别采纳这种形式。)
- 来一个指令就刷盘一次。(会影响性能)
- 永不被动刷盘,让操作系统决定何时刷盘。(很不平安)
5.1.3 混合长久化
将 RDB 文件的内容和增量的 AOF 日志文件存在一起,这里的 AOF 日志不是全量的日志,而是自快照长久化开始到长久化完结的这段时间产生的增量 AOF 日志。Redis 重启的时候,先加载 RBD 的内容,再重放增量 AOF 日志。
5.2 管道
Redis 的管道并不是 Redis 服务器提供的一个技术,而是客户端提供的。原理就是客户端将多个申请并行发送,节俭期待每个申请返回响应的工夫。服务端还是按失常程序解决每个申请。
5.3 事务
Redis 也反对事务,相干指令有:
- MULTI:批示事务的开始,相当于关系型数据库里的
begin
。 - EXEC:执行事务,相当于关系型数据库里的
commit
。 - DISCARD:抛弃事务,相当于关系型数据库里的
rollback
。
简略事务应用示例:
# === EXEC 应用示例
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR students
QUEUED
127.0.0.1:6379(TX)> INCR students
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 1
2) (integer) 2
127.0.0.1:6379> get students
"2"
127.0.0.1:6379>
# ==== DISCARD 应用示例
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR students1
QUEUED
127.0.0.1:6379(TX)> INCR students1
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> get students1
(nil)
Redis 在开始一个事务时,所有的指令在 EXEC
之前不执行,而是缓存在服务器的一个事务队列中。如果服务器收到 EXEC
指令,就开始执行整个事务队列,并在执行结束后一次性返回所有指令的运行后果;如果服务器收到 DISCARD
指令,则抛弃事务缓存队列中的所有指令。
Redis 的事务是不反对原子性的,即便事务执行两头遇到了失败,后续指令也还是会继续执行。如下:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET name lily
QUEUED
127.0.0.1:6379(TX)> INCR name
QUEUED
127.0.0.1:6379(TX)> set after hello
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> GET after # 即便第二个指令出错了,第三个指令依然失常执行
"hello"
Redis 能够应用 WATCH
指令配合事务来解决并发批改的问题(WATCH
机制是一种乐观锁)。WATCH
会在事务开始之前监督一个或多个变量,当事务开始执行时(发送了 EXEC
),Redis 会查看被WATCH
监督的变量是否被以后事务之外的申请批改过,如果有批改过,EXEC
指令会返回 nil,Redis 事务执行返回失败。
127.0.0.1:6379> WATCH monitor
OK
127.0.0.1:6379> INCR monitor # 在事务之前批改了 monitor
(integer) 1
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR monitor
QUEUED
127.0.0.1:6379(TX)> EXEC # 事务执行失败
(nil)
# =====
127.0.0.1:6379> WATCH monitor1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> incr monitor1
QUEUED
127.0.0.1:6379(TX)> incr monitor1
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 1
2) (integer) 2
留神:Redis 禁止在 MULTI
和EXEC
之间执行 WATCH
指令,而必须在 MULTI
之前检视变量。
5.4 淘汰策略
当 Redis 内存应用超出配置参数“maxmemory”限度的内存后,Redis 提供了几种可选策略(配置里的“maxmemory-policy”)来让用户决定如果解决。Redis 配置文件外面能够看到这些策略:
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
- volatile-lru:从设置了过期工夫的 key 的汇合中应用近似 LRU 算法进行淘汰。没有设置过期工夫的 key 不会被淘汰。
- allkeys-lru:从所有的 key 的汇合中应用近似 LRU 算法进行淘。
- volatile-lfu:从设置了过期工夫的 key 的汇合中应用近似 LFU 算法进行淘汰。
- allkeys-lfu:从所有的 key 的汇合中应用近似 LFU 算法进行淘。
- volatile-random:从设置了过期工夫的 key 的汇合中随机淘汰 key。
- allkeys-random:从所有的 key 的汇合中随机淘汰 key。
- volatile-ttl:从设置了过期工夫的 key 的汇合中依据剩余时间 ttl 的值进行淘汰,ttl 越小的越先被淘汰。
- noeviction:不做任何操作,只对写申请返回谬误,读申请能够持续进行。
5.4.1 LRU
LRU,Least Recentlly Used,最近起码应用。LRU 算法应用一个链表,保护被拜访的 key 的程序,每个 key 被拜访的时候,就把 key 移到表头,所以链表的程序就是 key 最近被拜访的工夫程序。如果空间满了,就从表尾删除数据。
应用 python 的 OrderedDict(字典 + 双向链表)实现简略的 LRU 算法:
from collections import OrderedDict
class LRUDict(OrderedDict):
def __init__(self, capacity):
super().__init__()
self.capacity = capacity
self.items = OrderedDict()
def __setitem__(self, key, value):
old_value = self.items.get(key)
if old_value is not None: # 如果 key 存在,挪动到表头
self.items.pop(key)
self.items[key] = value
elif len(self.items) < self.capacity: # 如果 key 不存在,且空间够用,直接插入到表头
self.items[key] = value
else:
self.items.popitem(last=False) # 如果空间满了,删除最初一个元素,而后将 key 插入到表头
self.items[key] = value
def __getitem__(self, key):
value = self.items.get(key)
if value is not None: # 每次拜访 key,都把 key 挪动到表头
self.items.pop(key)
self.items[key] = value
return value
def __repr__(self):
return repr(self.items)
d = LRUDict(10)
for i in range(15):
d[i] = i
print(d)
5.4.2 LFU
LFU,Least Frequently User,最近不常常应用。LFU 算法是在给每个 key 统计拜访次数。在每个 key 被拜访的时候,计数加一,如果多个计数雷同,则按拜访工夫排序。空间满的时候,从计数最小的开始删除。
6. Redis 集群篇
6.1 主从同步
6.1.1 增量同步
Redis 增量同步同步的是指令流,主节点将会批改数据的指令记录在内存 buffer 中,而后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流,一边向主节点反馈同步偏移地位。
Redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始笼罩后面的内容。如果网络不好,从节点同步太滞后,可能会造成从节点数据失落。
6.1.2 全量同步(快照同步)
快照同步首先在主库上进行一次 bgsave
将数据长久化到磁盘,而后再将快照文件的内容全副传送到从节点。从节点将快照文件全副接管结束后,清空内存占用的数据,对快照文件执行一次全量加载,加载实现后再告诉主节点持续进行增量同步。
6.1.3 无盘复制
无盘复制是指服务器间接通过套接字将快照内容发送到从节点,主节点一边遍历内存,一边将内容发送到从节点。从节点先将收到的内容存入磁盘,而后进行一次性加载。
6.2 Sentinel(哨兵)模式
Redis Sentinel 是 Redis 官网提供的一种高可用计划。Redis Sentinel 自身能够单点部署,也能够集群部署。
Redis Sentinel 负责继续监控主从节点的衰弱,当主节点挂掉时,主动抉择一个最优的从节点切换为主节点。客户端连贯 Redis 集群时,会首先连贯 Sentinel,通过 Sentinel 查问主节点的地址,而后客户端间接去连贯主节点进行数据交互。主节点故障时,客户端再向 Sentinel 获取新的主节点地址。
6.3 Cluster
Redis Cluster 是 Redis 作者本人提供的去中心化的 Redis 集群化计划。Redis Cluster 中每个节点负责存储一部分数据,节点之间通过非凡的二进制协定交互集群信息。Redis Cluster 起码须要三个主节点,个别会为每个主节点配置一个(或多个)从节点,某个主节点故障时,集群会主动将该主节点的某个从节点晋升为主节点。如果产生故障的主节点没有从节点,那么实践上整个集群将不可用,不过 Redis 提供了参数“cluster-require-full-coverage”能够容许局部节点故障,其它节点还能够持续提供对外拜访。
Redis Cluster 将所有的数据划分为 16384 个槽(solt),每个节点负责其中一部分槽位,槽位的信息存储在每个节点中。当 Redis Cluster 的客户端连贯集群时,它会失去一份集群的槽位配置信息,这样当客户端须要查找某个 key 时,能够间接定位到指标节点。
6.3.1 槽定位算法
Cluster 默认应用 crc16 算法进行 hash 再对 16384 取模来失去具体槽位。Cluster 提供有 hash tag 容许用户应用指定的 tag 来定位槽。如果 key 蕴含 {...}
模式,则只应用“{”和“}”之间的字符串进行哈希以定位槽,例如 key 是 ”xxxxxsdfdf{hello}”,或“fff222222{hello}”的,都只会应用“hello”来定位槽。
6.3.2 跳转
当客户端向一个谬误的节点发送指令,该节点发现 key 所在的槽位不归本人治理,这时它会向客户端发送一个非凡的跳转指令 MOVED
携带指标操作的节点地址,通知客户端去连贯这个节点。
6.3.3 迁徙
Redis Cluster 迁徙的单位是槽,Redis 一个槽一个槽进行迁徙,当一个槽正在迁徙时,这个槽就处于两头过渡状态。这个槽的源节点的状态为migrating,在指标节点的状态为importing,示意数据正在从源流向指标。
迁徙工具 redis-trib 首先会在源和指标节点设置好两头过渡状态,而后一次性获取源节点槽位的所有 key,再挨个 key 进行迁徙。每个 key 迁徙过程是:从源节点获取内容,发送到指标节点,指标节点存入内存,源节点删除内容。留神,这里的迁徙过程是同步的,在指标节点存入内存到源节点删除 key 之间,节点的主线程会处于阻塞状态,晓得 key 被胜利删除。所以 集群环境下要防止大 key 的产生。
6.3.4 节点下线
因为 Redis Cluster 是去中心化的,一个节点认为某个节点失联了并不代表所有节点都认为它失联了。只有当大多数节点都认定某个节点失联了,集群才认为该节点须要进行主从切换来容错。
7. 参考文献
- 《Redis 深度历险:外围原理和利用实际》