文章目录
redis持久化、事务、发布订阅
一、redis的持久化
1. rdb(Redis DataBase)
1.1 是什么
-
- 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
-
- Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
1.2 fork
fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
1.3 rdb保存的是什么
rdb保存的是dump文件
1.4 配置位置
1.5 如何触发RDB快照
-
- 配置文件中默认的快照配置
save 3600 1
save 120 10
save 60 10000
120s内键值的变动超过10次触发:
触发之前不存在dump6380.dump文件
触发后自动生成
-
- 命令save或者是bgsave
-
- Save:save时只管保存,其它不管,全部阻塞
-
- BGSAVE:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间
-
- 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
1.6 如何恢复
-
- 将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可
-
- CONFIG GET dir获取目录
1.7 优势
-
- 适合大规模的数据恢复
-
- 对数据完整性和一致性要求不高(未触发快照可能会丢失数据)
1.8 劣势
-
- 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改
-
- fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
1.9 如何停止
动态所有停止RDB保存规则的方法:redis-cli config set save “”
1.10 注意事项
正常情况下,备份文件和redis主机不存在同一机器上,需要定时转存备份未见
1.11 部分相关配置
1.11.1 stop-writes-on-bgsave-error yes
如果配置成no,表示不在乎数据不一致或者有其他手段控制和发现
1.11.2 rdbcompression yes
对于存储到磁盘中的快照,可以设置是否进行压缩存储,如果是的话,redis会采用LZF算法进行压缩。如果不想消耗CPU进行压缩的话,可以设置为关闭此功能。一般情况不会关闭
1.11.3 rdbchecksum yes
在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能小号,如果希望获取到最大的性能提升,可以关闭此功能。
2. aof(appendonly.aof)
2.1 是什么
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),
只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis
重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
2.2 Aof保存的是什么
Aof保存的是appendonly.aof文件
2.3 配置位置
redis.conf中进行配置
2.4 正常恢复
-
- 修改默认的appendonly no,改为yes
-
- 将有数据的aof文件复制一份保存到对应目录(config get dir)
-
- 恢复:重启redis然后重新加载
2.5 异常恢复
-
- 备份被写坏的AOF文件
-
- redis-check-aof --fix进行修复
-
- 恢复:重启redis然后重新加载
2.6 rewrite
2.6.1 是什么
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
2.6.2 重写原理
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
2.6.3 触发机制
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发,实际生产环境日志很大,相应的size也会变更
2.7 优势
-
- 每修改同步:appendfsync always 同步持久化每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
-
- 每秒同步:appendfsync everysec 异步操作,每秒记录如果一秒内宕机,有数据丢失
-
- 不同步:appendfsync no 从不同步
2.8 劣势
-
- 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
-
- aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
3. 同时存在
开启appendonly
删除相关的持久化文件
重启后生成持久化文件appendonly6380.aof
设置键值,然后FLUSHALL,SHUTDOWN(会生成rdb文件)
FLUSHALL,SHUTDOWN后生成dump6380.rdb文件
删除rdb文件,查看恢复情况,发现并未恢复!
查看aof文件,发现aof文件记录了FLUSHALL操作,删除后可恢复
编辑aof文件,使aof文件出现错误,客户端执行shutdown,生成rdb文件
重新启动redis,发现启动失败,说明aof和rdb可以共存,但是先加载的是aof
修复aof文件
二、redis的事务
1. 是什么
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞
2. 能干嘛
一个队列中,一次性、顺序性、排他性的执行一系列命令
3. 怎么玩
Usage
A Redis Transaction is entered using the MULTI command. The command always replies with OK. At this point the user can issue multiple commands. Instead of executing these commands, Redis will queue them. All the commands are executed once EXEC is called.
Calling DISCARD instead will flush the transaction queue and will exit the transaction.
The following example increments keys foo and bar atomically.
命令 | 作用 |
---|---|
DISCARD | 取消事务,放弃执行事务块内的所有命令 |
EXEC | 执行所有事务块内的命令 |
MULTI | 标记一个事务块的开启 |
WATCH key [key …] | 监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将会被打断 |
3.1 正常执行
127.0.0.1:6380> MULTI
OK
127.0.0.1:6380(TX)> set k1 v1
QUEUED
127.0.0.1:6380(TX)> set k2 v2
QUEUED
127.0.0.1:6380(TX)> get k2
QUEUED
127.0.0.1:6380(TX)> set k3 v3
QUEUED
127.0.0.1:6380(TX)> EXEC
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6380>
3.2 放弃事务
127.0.0.1:6380> MULTI
OK
127.0.0.1:6380(TX)> set k1 v1
QUEUED
127.0.0.1:6380(TX)> set k2 v22
QUEUED
127.0.0.1:6380(TX)> set k3 v33
QUEUED
127.0.0.1:6380(TX)> DISCARD
OK
127.0.0.1:6380> get k2
"v2"
127.0.0.1:6380>
3.3 全体连坐
127.0.0.1:6380> MULTI
OK
127.0.0.1:6380(TX)> set k1 v1
QUEUED
127.0.0.1:6380(TX)> set k2 v2
QUEUED
127.0.0.1:6380(TX)> set k3 v3
QUEUED
127.0.0.1:6380(TX)> getset k3 // 语法错误,入队报错,全部执行失败
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6380(TX)> set k4 v4
QUEUED
127.0.0.1:6380(TX)> set k5 v5
QUEUED
127.0.0.1:6380(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6380> get k5
(nil)
127.0.0.1:6380>
3.4 冤头债主
127.0.0.1:6380> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6380> get k2
"v2"
127.0.0.1:6380> MULTI
OK
127.0.0.1:6380(TX)> incr k1 //incr k1运行的时候出错
QUEUED
127.0.0.1:6380(TX)> set k2 v22
QUEUED
127.0.0.1:6380(TX)> set k3 v33
QUEUED
127.0.0.1:6380(TX)> set k4 v4
QUEUED
127.0.0.1:6380(TX)> get k4
QUEUED
127.0.0.1:6380(TX)> EXEC
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) OK
5) "v4"
127.0.0.1:6380> get k2
"v22"
127.0.0.1:6380>
结论:redis是部分支持事务的
3.5 watch监控
3.5.1 悲观锁/乐观锁/CAS(Check And Set)
3.5.1.1 悲观锁
悲观态度,认为一定会发生,锁定整张表,一致性高,效率差
数据库中的行锁,表锁,读锁,写锁,以及 syncronized 实现的锁均为悲观锁。
3.5.1.2 乐观锁
乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突。
乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。
3.5.1.3 CAS
3.5.2 初始化信用卡可用余额和欠额
127.0.0.1:6380> WATCH balance
OK
127.0.0.1:6380> MULTI
OK
127.0.0.1:6380(TX)> DECRBY balance 20
QUEUED
127.0.0.1:6380(TX)> INCRBY debt 20
QUEUED
127.0.0.1:6380(TX)> EXEC
1) (integer) 80
2) (integer) 20
127.0.0.1:6380>
3.5.3 无加塞篡改,先监控再开启multi,保证两笔金额变动在同一个事务内
127.0.0.1:6380> WATCH balance
OK
127.0.0.1:6380> MULTI
OK
127.0.0.1:6380(TX)> DECRBY balance 20
QUEUED
127.0.0.1:6380(TX)> INCRBY debt 20
QUEUED
127.0.0.1:6380(TX)> EXEC
1) (integer) 80
2) (integer) 20
127.0.0.1:6380>
3.5.4 有加塞篡改
127.0.0.1:6380> get balance
"80"
127.0.0.1:6380> get debt
"20"
127.0.0.1:6380> watch balance watch后另一个终端对balance进行修改
OK
127.0.0.1:6380> MULTI
OK
127.0.0.1:6380(TX)> DECRBY balance 20
QUEUED
127.0.0.1:6380(TX)> INCRBY debt 20
QUEUED
127.0.0.1:6380(TX)> EXEC
(nil)
127.0.0.1:6380>
watch后另一个终端对balance进行修改
127.0.0.1:6380> get balance
"80"
127.0.0.1:6380> set balance 800
OK
127.0.0.1:6380>
3.5.5 unwatch
127.0.0.1:6380> get balance
"500"
127.0.0.1:6380> watch balance
OK
127.0.0.1:6380> set balance 100
OK
127.0.0.1:6380> UNWATCH
OK
127.0.0.1:6380> WATCH balance
OK
127.0.0.1:6380> MULTI
OK
127.0.0.1:6380(TX)> set balance 80
QUEUED
127.0.0.1:6380(TX)> set debt 20
QUEUED
127.0.0.1:6380(TX)> exec
1) OK
2) OK
127.0.0.1:6380>
3.5.6 一旦执行了exec之前加的监控锁都会被取消掉了
3.5.7 小结
-
- watch指令类似于乐观锁,事务提交时,如果key的值已经被其他客户端改变,整个事务队列都不会被执行
-
- 通过watch命令在事务执行之前监控了多个keys,倘若在watch之后有任何key的值发生了变化,EXEC命令执行的事务都将被放弃,同事返回Nullmulti-bulk应答以通知调用者事务执行失败
4. 3阶段
4.1 开启
以multi开启一个事务
4.2 入队
将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
4.3 执行
由EXEC命令触发事务的执行
5. 3特性
5.1 单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
5.2 没有隔离级别的概念
队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
5.3 不保证原子性
redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
三、Redis的发布订阅
1. 是什么
进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
2. 常见命令
命令 | 作用及描述 |
---|---|
PSUBSCRIBE pattern [pattern …] | 订阅一个或多个符合给定模式的频道 |
PUBSUB subcommand [argument [argument …]] | 查看订阅与发布系统状态 |
PUBLISH channel message | 将信息发送到指定的频道 |
PUNSUBSCRIBE [pattern [pattern …]] | 退订所有给定模式的频道 |
SUBSCRIBE channel [channel …] | 订阅给定的一个或多个频道的信息 |
UNSUBSCRIBE [channel [channel …]] | 退订给定的频道 |
3. 案列
订阅c1 c2 c3
127.0.0.1:6380> SUBSCRIBE c1 c2 c3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "c1"
3) (integer) 1
1) "subscribe"
2) "c2"
3) (integer) 2
1) "subscribe"
2) "c3"
3) (integer) 3
发布c2
127.0.0.1:6380> PUBLISH c2 hello-redis
(integer) 1
127.0.0.1:6380>
收到消息:
127.0.0.1:6380> SUBSCRIBE c1 c2 c3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "c1"
3) (integer) 1
1) "subscribe"
2) "c2"
3) (integer) 2
1) "subscribe"
2) "c3"
3) (integer) 3
1) "message"
2) "c2"
3) "hello-redis"
支持匹配符:
127.0.0.1:6380> PSUBSCRIBE new*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "new*"
3) (integer) 1
1) "pmessage"
2) "new*"
3) "new1"
4) "redis2015"
1) "pmessage"
2) "new*"
3) "new13"
4) "hello-li"
127.0.0.1:6380> PUBLISH new1 redis2015
(integer) 1
127.0.0.1:6380> PUBLISH new13 hello li
(error) ERR wrong number of arguments for 'publish' command
127.0.0.1:6380> PUBLISH new13 hello-li
(integer) 1
127.0.0.1:6380>