共计 10031 个字符,预计需要花费 26 分钟才能阅读完成。
@TOC
Redis 概述 ✍
什么是 Redis
Redis 是 Remote Dictionary Server(近程字典服务器)的缩写,能够作为高效的缓存。
Redis 应用 C 语言开发,将数据保留在内存中,能够看成是一款 纯内存 的数据库,所以它的数据存取速度十分快。
Redis 通过键值对的模式来存储数据。Redis 的 Key 只能是 String 类型,Value 值则能够是 String 类型,Map 类型、List(列表) 类型、Set(汇合)类型、SortedSet(有序汇合)等类型。
为什么用 Redis 做缓存
相比于其余的键值对内存数据库,Redis 有如下特点:
- 速度快。不须要期待磁盘 IO,而是在内存之间进行数据存储和查问,速度十分快。缓存的数据总量不能太大,因为受到物理内存空间大小的限度
- 丰盛的数据结构
- 单线程 ,防止了线程切换和锁机制的性能耗费, 不过 6.0 后是多线程了
- 可长久化,反对 RDB 与AOF两种形式,将内存中的数据写入内部的物理存储设备
- 反对公布、订阅
- 反对 Lua 脚本
- 反对 分布式锁
- 反对 原子操作 和事务
- 反对 主从复制 和高可用集群
- 反对 管道,Redis 管道是指客户端能够将多个命令一次性发送到服务器,而后由服务器一次性返回所有后果。管道技术的长处是,在批量执行命令的利用场景中,能够大大减少网络传输开销,进步性能
Redis 装置启动
下载配置一下 Redis
Linux 服务器上:下载:wget http://download.redis.io/releases/redis-4.0.10.tar.gz
解压
tar -zxvf redis-4.0.10.tar.gz
cd redis-4.0.10
make MALLOC=libc(要求已装置 gcc)make install
批改 redis.conf 文件
> redis-server redis.conf 启动
> redis-cli -a password 连贯
也能够在命令行敞开:redis-cli -p 6379 -a your_password shutdown
压测工具:redis-benchmark -h localhost -p 6379 -c 50 -n 10000
具体可用参数翻文档
Redis 配置文件解读 ⚙
Redis 数据类型 ✍
String 字符串
Redis 本人构建了简略的 动静字符串 SDS(相似 ArrayList,预调配冗余空间),没有用 c 语言原生的。
String 类型既能够存储文字,又能够存储数数字,还能够存储二进制数据。
127.0.0.1:6379> set foo name
OK
127.0.0.1:6379> mset f1 str f2 strr f3 strrr
OK
127.0.0.1:6379> get foo
"name"
127.0.0.1:6379> get f1
"str"
127.0.0.1:6379> mget f1 f2 f3
1) "str"
2) "strr"
3) "strrr"
127.0.0.1:6379> strlen f1
(integer) 3
127.0.0.1:6379> set n1 1.0
OK
127.0.0.1:6379> get n1
"1.0"
127.0.0.1:6379> set n2 1
OK
127.0.0.1:6379> incr n2
(integer) 2
127.0.0.1:6379> set bina1 101110
OK
127.0.0.1:6379> get bina1
"101110"
利用场景
- 缓存,String 是很多语言都有的类型
- 计数器,应用 Redis 做零碎的实时计数器
- 共享用户 Session,用户刷新一次页面,可能须要拜访一下数据进行从新登录,或者拜访页面缓存 Cookie,能够利用 Redis 将用户的 Session 集中管理,只须要保障 Redis 的高可用,每次用户 Session 的更新和获取都能够疾速实现。
List 列表
Redis 的 List 类型是基于 双向链表 实现的,能够反对正向、反向查找和遍历。List 列表是简略的字符串列表,字符串依照增加的 程序 排列。能够增加一个元素到 List 列表的头部或尾部。
一个 List 列表最多能够蕴含 2^32^- 1 个元素。
List 列表的典型利用场景
网络社区中最新的发帖列表、简略的音讯队列、最新新闻的分页列表、博客的评论列表、排队零碎等。
比方在秒杀流动中,短时间有大量的用户申请发向服务器,而后盾的程序不可能立刻响应每一个用户的申请。须要一个排队零碎,依据用户的申请工夫,将用户的申请放入 List 队列中,后台程序顺次从队列中获取工作,解决并将后果返回到后果队列。
- 音讯队列:List 的链表构造能够实现阻塞队列,应用左进右出的命令来实现队列的设计,比方生产者通过 Lpush 从右边插入数据,多个消费者用 BRpop 命令阻塞地抢左边的数据。
- 文章列表、分页展现:能够应用 List 做分页展现,能够通过 lrange 命令获取某个闭区间的元素,实现分页查问,还能够做成下拉一直分页那种。
127.0.0.1:6379> set ts taylor
OK
127.0.0.1:6379> get ts
"taylor"
127.0.0.1:6379> rpush ts a b c
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> del ts
(integer) 1
127.0.0.1:6379> rpush ts a b c
(integer) 3
127.0.0.1:6379> lpush ts 1 3 4
(integer) 6
127.0.0.1:6379> llen ts
(integer) 6
127.0.0.1:6379> rpop ts
"c"
127.0.0.1:6379> lpop ts
"4"
127.0.0.1:6379> lrange ts
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange ts 0
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> llen ts
(integer) 4
127.0.0.1:6379> lrange ts 0 3
1) "3"
2) "1"
3) "a"
4) "b"
127.0.0.1:6379> lindex ts 0
"3"
127.0.0.1:6379> lset ts 0 5
OK
127.0.0.1:6379> lrange ts 0 3
1) "5"
2) "1"
3) "a"
4) "b"
Hash
Redis 中的 Hash(哈希表)是一个 String 类型的 Field(字段)和 Value(值)之间的映射表。基于 数组加链表 实现。(?像 Java 中的 HashMap)
在同一个哈希表中,每个字段的名字必须是惟一的。
127.0.0.1:6379> hset hm 001 alice
(integer) 1
127.0.0.1:6379> hget hm 001
"alice"
127.0.0.1:6379> hdel hm 001
(integer) 1
127.0.0.1:6379> hexists hm 001
(integer) 0
127.0.0.1:6379> hset hm 001 alice
(integer) 1
127.0.0.1:6379> hset hm 002 bob
(integer) 1
127.0.0.1:6379> hexists hm 002
(integer) 1
127.0.0.1:6379> hkeys hm
1) "001"
2) "002"
127.0.0.1:6379> hvals hm
1) "alice"
2) "bob"
Set
Set 汇合也是一个列表,能够 主动去掉反复元素,所以能够去实现全局去重。
底层应用了 intset 和hashtable两种数据结构存储的,intset 咱们能够了解为数组,hashtable 就是一般的哈希表(key 为 set 的值,value 为 null)
Set 领有一个命令,可用于判断某个元素是否存在,而 List 类型并没有这种性能的命令。
通过 Set 类型的命令能够疾速地向汇合增加元素,或者从汇合外面删除元素,也能够对多个 Set 进行汇合运算,例如并集、交加、差集,这个能够用来实现找独特好友那种性能。
127.0.0.1:6379> sadd st 001
(integer) 1
127.0.0.1:6379> sadd st 001
(integer) 0
127.0.0.1:6379> sadd st 002 003 004
(integer) 3
127.0.0.1:6379> srem st 002
(integer) 1
127.0.0.1:6379> sismember st 002
(integer) 0
127.0.0.1:6379> sismember st 003
(integer) 1
127.0.0.1:6379> scard st
(integer) 3
127.0.0.1:6379> smembers st
1) "004"
2) "003"
3) "001"
ZSet
ZSet(有序汇合)和 Set(汇合)的应用场景相似,区别是 Zset 会依据提供的 score
参数主动排序。
当须要一个 不反复且有序 的汇合列表时,能够抉择 ZSet。
Zset 的每个元素都关联着一个 分值(Score),这是一个浮点数格局的关联值。ZSet 会依据分值按从大到小的程序来排列各个元素。
zset 也有两种不同的实现,别离是 zipList 和skipList
zipList
:满足以下两个条件[score,value]键值对数量少于 128 个;
每个元素的长度小于 64 字节;
skipList
:不满足以上两个条件时应用跳表、组合了 hash 和 skipListhash 用来存储 value 到 score 的映射,这样就能够在 O(1)工夫内找到 value 对应的分数;
skipList 依照从小到大的顺序存储分数
skipList 每个元素的值都是[socre,value] 对
127.0.0.1:6379> zadd salary 1000 user001
(integer) 1
127.0.0.1:6379> zadd salary 2000 user002
(integer) 1
127.0.0.1:6379> zadd salary 3000 user003
(integer) 1
127.0.0.1:6379> zadd salary 4000 user004
(integer) 1
127.0.0.1:6379> type salary
zset
127.0.0.1:6379> zrank salary user004
(integer) 3
127.0.0.1:6379> zrem salary user004
(integer) 1
127.0.0.1:6379> zrank salary user004
(nil)
127.0.0.1:6379> zincrby salary 200 user002
"2200"
127.0.0.1:6379> zcard salary
(integer) 3
127.0.0.1:6379> zcount salary 1000 4000
(integer) 3
127.0.0.1:6379> zrangebyscore salary 1000 3000
1) "user001"
2) "user002"
3) "user003"
利用场景
- 排行榜
- 带权重的队列,让重要的工作先执行
Redis 的外部扩容机制
Redis 是一个键值对数据库,Redis 服务器中的所有数据保留在 db 数组中,数据库的构造是 redis.h/redisDb,其中,redisDb 构造的 dict 字典保留了数据库中的所有键值对,所以,说起 Redis 的扩容机制,指的是字典中的哈希表的 rehash(从新散列)操作。
哈希表保留的键值对会逐步地增多或者缩小,当字典内数据过大时,会导致更多的键抵触。
当数据缩小时,曾经调配的内存还在占用,会造成内存节约。
渐进式 rehash 的具体步骤:(准确性待钻研,临时这么表述)
- 为 ht[1] 调配空间,让字典同时持有 ht[0]和 ht[1]两个哈希表
- 在几点钟维持一个索引计数器变量 rehashidx,并将它的值设置为 0,示意 rehash 开始
- 在 rehash 进行期间,每次对字典执行 CRUD 操作时,程序除了执行指定的操作以外,还会将 ht[0]中的数据 rehash 到 ht[1]表中,并且将 rehashidx 加一
- 当 ht[0]中所有数据转移到 ht[1]中时,将 rehashidx 设置成 -1,示意 rehash 完结
采纳渐进式 rehash 的益处在于它采取分而治之的形式,防止了集中式 rehash 带来的宏大计算量。
Redis 怎么删除大 Key
1. 非 String 的 bigkey
Hash、ZSet、List、Set 与日俱增越来越大,比方到 GB 了,如果间接应用 del
删除会导致长时间阻塞。
del
命令在删除汇合类型数据时,工夫复杂度为 O(M),M 是汇合中元素的个数。
Redis 是单线程的,单个命令执行工夫过长就会阻塞其余命令,容易引起缓存雪崩。
解决方案:
-
渐进式删除
分批删除,通过
scan
命令遍历大 Key,每次获得少部分元素,对其删除,而后再获取和删除下一批元素。hscan、sscan、zscan。 -
unlink:
unlink
命令由 Redis 4.0 推出,在所有命名空间中把 Key 删除,立刻返回,不阻塞;后盾线程执行真正的开释空间的操作。
三种非凡数据类型
geospatial 地理位置
定位、间隔计算。
geoadd
geopos
geodist
georadius
……
Hyperloglog
HyperLogLog 是一个专门为了计算汇合的基数而创立的概率算法。(不反复的元素的个数)。
对于一个给定的汇合,HyperLogLog 能够计算出整个汇合的近似基数:近似基数并非汇合的理论基数,它可能会比理论的基数小一点或者大一点,然而估算基数和理论基数之间的误差会处于一个正当的范畴之内。因而那些不须要晓得理论基数或者因为条件限度而无奈计算出理论基数的程序就能够把这个近似基数当作汇合的基数来应用。
pfadd 对汇合元素进行计数
pfcount 返回汇合的近似基数
pfmerge 计算多个 HyperLogLog 的并集
Bitmap
Redis 的位图 (bitmap) 是由多个二进制位组成的数组,数组中的每个二进制位都有与之对应的偏移量(也称索引),用户通过对这些偏移量能够对位图中指定的一个或多个二进制位进行操作。
setmit 设置二进制位的值
>> setbit bitmap001 0 1
>> setbit bitmap002 10 1
getbit 获取二进制位的值
>> getbit bitmap001 0
bitcount 统计被设置的二进制位数量
bitpos 查找第一个指定的二进制位值
Redis 的事务 ✍
应用事务
Redis 单条命令是放弃原子性的,但事务是不放弃原子性的。
一个事务中的所有命令都会被序列化,在事务执行的过程中,会依照程序执行。
具备一次性、程序性、排他性。
Redis 事务没有隔离级别的概念。
所有的命令在事务中,并没有间接被执行,只有发动执行命令的时候才会执行 (Exec)
Redis 的事务
- 开启事务(multi)
- 命令入队
- 执行事务(exec)
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 hello
QUEUED
127.0.0.1:6379> set key2 world
QUEUED
127.0.0.1:6379> get key2
QUEUED
127.0.0.1:6379> set key3 hi
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "world"
4) OK
放弃事务:
> discard
如果有编译时异样,就是有命令写错了,那全副命令都勾销了。
如果有运行时异样(比方给字符串 incr),除了错的,其余的仍旧能够执行。
监控 乐观锁
乐观锁
- 认为什么时候都会出错,无论什么时候都会加锁
乐观锁
- 认为什么时候都不会出问题,所以不会上锁。更新的时候去判断是否有人批改过
- 获取 version
- 更新的时候比拟 version
Redis 监督测试:
上图是失常状况,exec 提交执行而后呈现后果。
这里两个过程,在第一个过程还没提交事务之前,过程 2 批改了数据,所以过程 1 的数据提交不了,返回了 nil。
应用 watch
能够当做 redis 的乐观锁操作。
解锁应用unwatch
。
应用 watch 实现乐观锁:应用 watch 监督数据,在事务执行失败后,解锁,获取最新的值再次监督,再次开启事务。
Redis 长久化 ✍
Redis 作为一个内存数据库,长久化是必不不可少的。
Redis 长久化有两种形式:
- RDB:RDB 长久化机制,是对 Redis 中的数据执行 周期性 的长久化。
- AOF:AOF 机制对每条写入命令作为日志,以
append-only
的模式写入一个日志文件中,在 Redis 重启的时候,能够通过 回放AOF 日志中的写入指令来从新构建整个数据集。
通过 RDB 或 AOF,都能够将 Redis 内存中的数据给长久化到磁盘下面来,还能够将数据备份到别的中央。
如果 Redis 挂了,能够将备份放到指定目录中重启 Redis,Redis 就会主动依据长久化文件复原内存中的数据。
如果同时应用 RDB 和 AOF 两种长久化机制,那么在 Redis 重启的时候,会应用 AOF 来从新构建数据,因为AOF 中的数据更加残缺。
RDB
RDB 长久化性能所生成的 RDB 文件是一个通过压缩的二进制文件,通过该文件能够还原生成 RDB 文件时的数据库状态。
RDB 文件的创立与载入
有两个 Redis 命令能够用于生成 RDB 文件,一个是SAVE
,另一个是BGSAVE
。
SAVE 命令会阻塞 Redis 服务器过程,直到 RDB 文件创建结束为止,在服务器阻塞期间,服务器不能解决任何命令申请:
redis> SAVE // 期待直到 RDB 文件创建结束
OK
和 SAVE 命令间接阻塞服务器过程的做法不同,BGSAVE 命令会派生 (fork) 出一个子过程,而后由子过程负责创立 RDB 文件,服务器过程 (父过程) 持续解决命令申请:
redis> BGSAVE // 派生子过程,并由子过程创立 RDB 文件
BGSAVE 命令执行期间,SAVE 命令会被回绝、BGSAVE 也会被回绝。
主动间隔性保留
因为 BGSAVE 命令能够在不阻塞服务器过程的状况下执行,所以 Redis 容许用户通过设置服务器配置的 save 选项让服务器每隔一段时间主动执行一次 BGSAVE 命令。
只有满足以下三个条件中的任意一个,BGSAVE 命令就会被执行:
- 服务器在 900 秒内对数据库进行了至多 1 次批改
- 服务器在 300 秒内对数据库进行了至多 10 次批改
- 服务器在 60 秒内对数据库过程了至多 10000 次批改
AOF
AOF 长久化是通过保留 Redis 服务器所执行的写命令来记录数据库状态。
AOF 长久化的实现
AOF 长久化性能的实现能够分为 命令追加 (append)、 文件写入 、 文件同步 (sync) 三个步骤。
当 AOF 长久化性能处于关上状态时,服务器在执行完一个写命令之后,会以协定格局将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的开端。
RDB、AOF 对过期键的解决
对于 RDB,在执行 SAVE 或 BGSAVE 命令时,程序会对数据库中的键进行查看,已过期的键不会被保留到新创建的 RDB 文件中。
对于 AOF,某个键过期但还没被 惰性删除
或定期删除
,AOF 文件不会因为这个过期键而产生任何影响。
当过期键被惰性删除或定期删除后,程序会向 AOF 文件追加一条 DEL 命令。
RDB 优缺点 ⚙
- RDB 会产生多个数据文件,每个数据文件代表某一个时刻中 Redis 的数据,这种模式非常适合做冷备(非实时,定期备份)。
- RDB 对 Redis 对外提供的读写服务,影响十分小,能够让 Redis 放弃搞性能,因为 fork 了一个子过程。
- 基于 RDB 复原比 AOF 更快。
- 心愿尽可能少丢数据,RDB 没有 AOF 好。
- RDB 在 fork 子过程来执行 RDB 快照数据文件生成的时候,如数据文件特地大,可能导致对客户端提供的服务暂停数毫秒甚至秒。
AOF 优缺点 ⚙
- AOF 能够更好地保证数据不失落,个别 AOF 会每距离 1 秒,通过一个后盾线程执行一次 fsync 操作,最多失落 1 秒钟的数据。
- AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能高,就算文件尾部破损也比拟容易修复。
- AOF 日志文件过大的时候,呈现后盾重写操作,也不会影响客户端的读写,在 rewrite log 的时候,会对其中指令进行压缩。在创立新日志文件时,老日志文件还是照常写入,当新的 merge 后的日志文件 ready 的时候,再替换新老日志文件即可。
- AOF 日志文件的命令通过可读较强的形式进行记录,这个个性非常适合做灾难性的误删除的紧急复原。比方不小心 flushall 了,能够立刻拷贝 AOF 文件,将最初一条 flushall 命令删除,将 AOF 文件放回去,就能够通过复原机制主动复原。
- 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
- AOF 开启后,反对的写 QPS(每秒申请数)会比 RDB 反对的写 QPS 低,因为 AOF 会配置成每秒 fsync 一次日志文件。
-
以前 AOP 产生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有复原截然不同的数据进去。所以说相似 AOF 这种较为简单的基于命令日志 /merge/ 回放的形式,比 RDB 残缺数据快照的形式要软弱一些,容易有 bug。AOF 为了防止 rewrite 导致的 bug,所以都是基于过后内存中的数据进行指令的从新构建。
RDB 和 AOF 如何抉择
- 不要仅仅应用 RDB,会导致失落很多数据
- 也不要仅仅应用 AOF,AOF 做冷备没有 RDB 做冷备的复原速度快;RDB 每次简略粗犷生成数据快照,更加强壮。
- Redis 反对同时开启两种长久化形式,能够综合应用 AOF 和 RDB 两种长久化机制,用 AOF 来保证数据不失落,作为数据恢复的第一抉择;用 RDB 来做不同水平的冷备,在 AOF 文件都失落或损坏不可用的时候,还能够应用 RDB 来进行疾速的数据恢复。
内存淘汰策略
每进行一次 redis 操作的时候,redis 都会检测可用内存,判断是否要进行内存淘汰,当超过可用内存的时候,咱们就会应用对应策略,默认是 no-eviction
noeviction
:该策略对于写申请不再提供服务,会间接返回谬误,排除 del 等非凡操作allkeys-random
:从 redis 中随机选取 key 进行淘汰allkeys-lru
:从 redis 中选取应用起码的 key 进行淘汰volatile-random
:从 redis 中设置过过期工夫的 key,进行随机淘汰volatile-ttl
:从 redis 中选取行将过期的 key,进行淘汰volatile-lru
:从 redis 中设置过过期工夫的 key 中,选取起码应用的进行淘汰
config get maxmemory-policy // 获取以后内存淘汰策略
config set maxmemory-policy valatile-lru // 通过命令批改淘汰策略
想永恒配置还是要在配置文件里改~
公布和订阅 ✍
Redis 的公布与订阅性能能够让客户端通过播送形式,将音讯 (message)
同时发送给可能存在的多个客户端,并且发送音讯的客户端不须要直到接管音讯的客户端的具体信息。
PUBLISH、SUBSCRIBE
publish 命令将一条音讯发送给指定频道,publish 命令会返回接管到音讯的客户端数量作为返回值。
publish 命令从 Redis 2.0.0 版本开始可用。
subscribe 命令让客户端订阅一个或多个频道,subscribe 命令在每次胜利执行订阅一个频道之后,都会向执行命令的客户端返回一条订阅音讯,音讯蕴含了被订阅胜利的频道以及客户端目前已订阅的频道数量。
当客户端成为频道的订阅者之后,就会接管到来自被订阅频道的音讯,这些音讯成为频道音讯。
和订阅音讯一样,由 3 个元素组成:
- message , 示意这是一条频道音讯并非订阅音讯
- 音讯的起源频道
-
注释,音讯的真正内容
UNSUBSCRIBE
尽管 Redis 提供了退订的命令,然而 Redis 自带的命令行客户端 redis-cli 在执行 subscribe 命令之后就会进入阻塞状态,无奈再执行其余任何命令,实际上基本用不上 subscribe 命令。
一些编程语言为公布和订阅提供了更好的反对,也能够应用 unsubscribe。
PSUBSCRIBE PUNSUBSCRIBE
想要订阅所有带有 ”news.” 前缀的频道的音讯:
redis> psubscribe "news.*"
想要退订 “news.” 模式的频道:
redis> punsubscribe "news.*" redis> punsubscribe // 退订所有的模式
PUBSUB
redis> pubsub channels [pattern] // 查看被订阅的频道 [pattern]="news.*" redis> pubsub nunsub [channel channel ...] // 查看频道的订阅者数量 redis> pubsub numpat // 查看被订阅模式的总数量