4、持久化、事务

持久化

优势Redis是基于缓存的存储,所以如果服务器宕机,会导致数据丢失,所以如果需要保存在磁盘上,需要使用持久化方案,Redis提供两种的持久化方案,RDB和AOF,默认情况下,开启了RDB方案,关闭AOF方案

RDB(Redis Database)

也叫做快照模式,只会保存在数据库的最后的结果,并不会保存过程

手动触发

save命令
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set a a
OK
127.0.0.1:6379> set b b
OK
127.0.0.1:6379> set c c
OK
127.0.0.1:6379> save
OK
127.0.0.1:6379>

此命令是阻塞式命令,是同步的。执行数据库的备份,把数据库的数据全部存储到一个dump.rdb文件中。并且服务器在每次启动的时候,都从当前配置文件中,检测rdb和aof文件是否存在,如果存在,则使用rdb和aof文件中的内容,做数据恢复。

bgsave命令
127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379>

bgsave属于异步非阻塞式命令,后台执行。不阻塞。不影响其他命令的执行。

自动触发

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储

说明: clipboard.png

具体实现

说明: clipboard.png

将内存中的数据以快照的方式写入二进制文件中,默认的文件名是dump.rdb

1、修改redis.conf配置文件

此处表示,如果在60秒内,有100次的修改的话,就会触发RDB

说明: clipboard.png

2、需要在运行Redis时,同时加载配置文件,window系统下的做法,创建redis-server.exe的快捷方式,并在启动路径后追加redis.windows.conf

但是,可能会因此丢失部分数据,在上一次save和这一次save之间的数据,这一次的save还没有发生,此时宕机,则会丢失区间数据。

AOF(Append Only File)

采用的是一种日志追加的方式来记录Redis相关操作,AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录

说明: clipboard.png

具体实现

修改redis.conf配置文件,yes为开启

说明: clipboard.png

事务

Redis中事务仅仅保证了隔离性(串行化),保证了一段事务在开启以后,不会被其他的事务所打扰,例如一致性,Redis就无法保证,同一个事务中,出现异常的命令将不会执行,但是其他的正常的命令会执行。

命令

命令 描述
multi 开始事务用于标记事务块的开始。Redis会将后续的命令逐个放入队列中,然后才能使用EXEC命令原子化地执行这个命令序列
exec 执行事务在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。当使用WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的命令,这种方式利用了检查再设置(CAS)的机制。这个命令的返回值是一个数组,其中的每个元素分别是原子化事务中的每个命令的返回值。 当使用WATCH命令时,如果事务执行中止,那么EXEC命令就会返回一个Null值
discard 回滚清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。如果使用了WATCH命令,那么DISCARD命令就会将当前连接监控的所有键取消监控
watch 监控指定的key当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的
unwatch 撤销监控

Redis的Watch

watch命令,需要与事务进行配合,在一个用户修改数据之前,如果watch key这个数据,那么如果在这个事务没有提交之前,有其他的用户进行修改此数据的话,那么当前用户的该事务,将被全部丢弃,当前,可以通过逻辑,做到自旋

使用watch+自旋,解决秒杀超卖的问题

public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = new Jedis();
        System.out.println(jedis.ping());
        //查询数据库
        while(true) {
            Integer count = Integer.parseInt(jedis.get("count"));//查询数据库的库存
            //做数据操作之前,必须要watch住,才能保证你做这个数据的时候,没有人进行修改
            jedis.watch("count");
            Transaction multi = jedis.multi();//事务的开启
            multi.set("count", (count - 1) + "");
            Thread.sleep(20000);
            List<Object> exec = multi.exec();
            if(exec != null){
                System.out.println("购买成功");
                return;
            }else{
                System.out.println("购买失败,自旋");
            }
        }
    }
}