redis-7-Spring客户端-持久化
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
1. Spring客户端

spring:
data:
redis:
host: 127.0.0.1
port: 6379
@RestController
public class MyController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
}
StringRedisTemplate 是RedisTemplate的子类,专门用来处理文本数据的
这个类提供的方法和jedis中提供的方法就很不一样了
1.1 使用String

RedisTemplate对各个类型的方法进行了分门别类的分装
比如opsForValue,就是专门提供string的方法的
@GetMapping("/test")
public void test(){
stringRedisTemplate.opsForValue().set("key","111");
stringRedisTemplate.opsForValue().set("key2","222");
String key = stringRedisTemplate.opsForValue().get("key");
System.out.println(key);
}

1.2 使用list
stringRedisTemplate.execute()
这个可以只需redis原生的命令,execute的参数是一个RedisCallback
RedisCallback这是一个回调函数
我们可以在这个函数里面写我们要执行的redis命令
这个回调函数就会被redisTemplate内部执行了
这个RedisCallback就是一个函数式接口
stringRedisTemplate.execute((RedisConnection connection)->{
connection.flushAll();
return null;
});
connection就是相当于我们原来的jedis对象,可以调用原生的api了
然后lambda表达式返回的值其实就是execute返回的值
stringRedisTemplate.execute((RedisConnection connection)->{
connection.flushAll();
return null;
});
stringRedisTemplate.opsForList().leftPush("key","111");
stringRedisTemplate.opsForList().leftPush("key","222");
stringRedisTemplate.opsForList().leftPush("key","333");
String key = stringRedisTemplate.opsForList().rightPop("key");
System.out.println(key);
key = stringRedisTemplate.opsForList().rightPop("key");
System.out.println(key);
key = stringRedisTemplate.opsForList().rightPop("key");
System.out.println(key);

1.3 使用set
stringRedisTemplate.execute((RedisConnection connection)->{
connection.flushAll();
return null;
});
stringRedisTemplate.opsForSet().add("key","111","222","333");
Set<String> key = stringRedisTemplate.opsForSet().members("key");
System.out.println(key);
Boolean key1 = stringRedisTemplate.opsForSet().isMember("key", "111");
System.out.println(key1);
Long key2 = stringRedisTemplate.opsForSet().size("key");
System.out.println(key2);
stringRedisTemplate.opsForSet().remove("key","111","222");
key = stringRedisTemplate.opsForSet().members("key");
System.out.println(key);

1.4 使用hash
stringRedisTemplate.execute((RedisConnection connection)->{
connection.flushAll();
return null;
});
stringRedisTemplate.opsForHash().put("key","f1","111");
stringRedisTemplate.opsForHash().put("key","f2","222");
stringRedisTemplate.opsForHash().put("key","f3","333");
String o = (String)stringRedisTemplate.opsForHash().get("key", "f1");
System.out.println(o);
Boolean b = stringRedisTemplate.opsForHash().hasKey("key", "f1");
System.out.println(b);
stringRedisTemplate.opsForHash().delete("key","f1");
Long key = stringRedisTemplate.opsForHash().size("key");
System.out.println(key);

1.5 使用zset
stringRedisTemplate.execute((RedisConnection connection)->{
connection.flushAll();
return null;
});
stringRedisTemplate.opsForZSet().add("key","zhangsan",10);
stringRedisTemplate.opsForZSet().add("key","lisi",20);
stringRedisTemplate.opsForZSet().add("key","wangwu",30);
Set<String> key = stringRedisTemplate.opsForZSet().range("key", 0, -1);
System.out.println(key);
Set<ZSetOperations.TypedTuple<String>> key1 = stringRedisTemplate.opsForZSet().rangeWithScores("key", 0, -1);
System.out.println(key1);
Double score = stringRedisTemplate.opsForZSet().score("key", "zhangsan");
System.out.println(score);
stringRedisTemplate.opsForZSet().remove("key","zhangsan");
Long key2 = stringRedisTemplate.opsForZSet().size("key");
System.out.println(key2);
Long rank = stringRedisTemplate.opsForZSet().rank("key", "lisi");
System.out.println(rank);

2. 持久化
2.1 认识持久化
持久就是重启之后,数据是否还存在
redis数据存在内存中,数据不持久,所以要保存到硬盘中
redis是内存和硬盘中都会存储的
读取数据的时候,还是从内存中读取,但是重启之后,要从硬盘中获取数据,然后恢复内存数据
有两个策略,一个是RDB,一个是AOF
RDB是定期备份,每隔一段时间就去备份
AOF是实时备份,只要使用了就去备份
2.2 RDB触发时机
就是定期的把redis内存中的所有的数据,都给写入硬盘中,生成一个快照,比如拍照
RDB 持久化是把当前进程数据⽣成快照保存到硬盘的过程,触发 RDB 持久化过程分为⼿动触发和⾃动触发。
⼿动触发就是通过redis客户端命令,来触发快照生成,save,bgsave
自动触发:在redis配置文件中设置,让redis每隔多长时间就触发
⼿动触发分别对应 save 和 bgsave 命令:
• save 命令:阻塞当前 Redis 服务器,直到 RDB 过程完成为⽌,对于内存⽐较⼤的实例造成⻓时间阻塞,基本不采⽤。就是全力进行生成快照,其他命令都不执行了,所以就阻塞了,因为是单线程的,所以不好
• bgsave 命令:Redis 进程执⾏ fork 操作创建⼦进程,RDB 持久化过程由⼦进程负责,完成后⾃动结束。阻塞只发⽣在 fork 阶段,⼀般时间很短。
Redis 内部的所有涉及 RDB 的操作都采⽤类似 bgsave 的⽅式
2.3 bgsave流程

- 执⾏ bgsave 命令,Redis ⽗进程判断当前进是否存在其他正在执⾏的⼦进程,如 RDB/AOF ⼦进程,如果存在 bgsave 命令直接返回。
- ⽗进程执⾏ fork 创建⼦进程,fork 过程中⽗进程会阻塞,通过 info stats 命令查看latest_fork_usec 选项,可以获取最近⼀次 fork 操作的耗时,单位为微秒。
- ⽗进程 fork 完成后,bgsave 命令返回 “Background saving started” 信息并不再阻塞⽗进程,可以继续响应其他命令。
- ⼦进程创建 RDB ⽂件,根据⽗进程内存⽣成临时快照⽂件,完成后对原有⽂件进⾏原⼦替换。执⾏ lastsave 命令可以获取最后⼀次⽣成 RDB 的时间,对应 info 统计的rdb_last_save_time 选项。
- 进程发送信号给⽗进程表⽰完成,⽗进程更新统计信息。
但是这两个进程都还是单线程的
2.4 RDB的rdb镜像文件
这个是存在redis的工作目录中—》工作目录是由redis配置文件配置的
找到文件redis.windows.conf,这个就是配置文件

找到dir这个配置
这个就描述了工作目录
在这个目录下找到文件
dump.rdb
这个就是rdb机制生成的镜像文件,redis服务器默认开启的就是rdb
这个镜像文件是二进制文件
把内存数据压缩存在这个镜像文件中,这个文件不要乱改—》如果乱改,redis服务重启的时候,会加载这个文件,数据同步到内存中—》改了可能就加载失败了
redis提供了一个这个镜像文件的检测工具
就是文件redis-check-rdb.pdb,这个就是检测的
2.5 rdb效果演示
我们执行flushall

看到镜像文件中有这些
> set key aaa
OK

发现没有变化,因为rdb文件不是说插入了数据就会立即更新的
因为我们没有手动触发,然后也没有达到自动触发条件,所以没有更新

这个配置就是自动触发条件
意思就是15分钟之后,且十五分钟之内有一次修改:就自动触发
五分钟之内有十次修改:也会触发
60s之内有10000次修改触发
说明生成rdb快照是一个高成本操作,所以不要太频繁
所以这就导致了rdb里面快照数据和实时数据不太一样
但是如果大量请求来了,然后开始生成快照,结果redis服务挂了,那怎么办呢—》数据都丢了,快照也没生成–》aof
我们来手动执行bgsave来生成快照

因为数据比较少,所以生成很快的

发现这个里面有key了
然后重启redis服务器
还是有key的,数据还是有的
然后我们插入新的key,不手动执行bgsave
> set key3 333
OK
> set key4 444
OK
然后重启redis服务:restart,key仍然存在
执行命令restart—》会自动触发rdb
redis进行主从复制的时候,也是会自动触发的
修改多次也会自动触发
如果是突然关闭redis服务----》key不会存在的
注意执行bgsave,就算内容没有变化,也是会生成一个新文件的
我们可以看文件的生成时间,就算redis没有变化,执行bgsave,镜像文件也是最新的,说明把原来的文件删除了的
如果使用的save,就是直接在同一个文件中修改的,不会删除原来文件

我们修改一下配置文件,只需要在60s内修改两次就可以自动触发了
然后重启redis服务器
> set key2 222
OK
> set key3 333
OK
然后等待时间到达60s
如果设置save ""就是关闭自动生成快照了
如果把镜像文件乱修改—干掉redis服务,不能重启—》会再次自动生成覆盖坏掉了—》干掉之后–》redis服务还是可以启动,还是有key,为什么呢,因为在镜像文件的末尾添加的对象,如果在镜像文件的中间部分乱改—》redis重启不了了
redis-check-rdb.exe这个就是检查镜像文件的检查工具
保存:RDB ⽂件保存再 dir 配置指定的⽬录(默认 /var/lib/redis/)下,⽂件名通过 dbfilename配置(默认 dump.rdb)指定。可以通过执⾏ config set dir {newDir} 和 config set dbfilename{newFilename} 运⾏期间动态执⾏,当下次运⾏时 RDB ⽂件会保存到新⽬录。
压缩:Redis 默认采⽤ LZF 算法对⽣成的 RDB ⽂件做压缩处理,压缩后的⽂件远远⼩于内存⼤⼩,默认开启,可以通过参数 config set rdbcompression {yes|no} 动态修改。
虽然压缩 RDB 会消耗 CPU,但可以⼤幅降低⽂件的体积,⽅便保存到硬盘或通过⽹络发送到从节点,因此建议开启。
校验:如果 Redis 启动时加载到损坏的 RDB ⽂件会拒绝启动。这时可以使⽤ Redis 提供的 redischeck-dump ⼯具检测 RDB ⽂件并获取对应的错误报告
2.6 RDB 的优缺点
RDB 是⼀个紧凑压缩的⼆进制⽂件,代表 Redis 在某个时间点上的数据快照。⾮常适⽤于备份,全量复制等场景。⽐如每 6 ⼩时执⾏ bgsave 备份,并把 RDB ⽂件复制到远程机器或者⽂件系统中(如 hdfs)⽤于灾备。
• Redis 加载 RDB 恢复数据远远快于 AOF 的⽅式。因为是二进制的形式,而aof是文本方式
• RDB ⽅式数据没办法做到实时持久化 / 秒级持久化。因为 bgsave 每次运⾏都要执⾏ fork 创建⼦进程,属于重量级操作,频繁执⾏成本过⾼。
• RDB ⽂件使⽤特定⼆进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有⻛险。
2.7 aof基本使用
AOF(Append Only File)持久化:以独⽴⽇志的⽅式记录每次写命令,重启时再重新执⾏ AOF⽂件中的命令达到恢复数据的⽬的。AOF 的主要作⽤是解决了数据持久化的实时性,⽬前已经是Redis 持久化的主流⽅式。理解掌握好 AOF 持久化机制对我们兼顾数据安全性和性能⾮常有帮助。
当aof和rdb同时开启的时候,rdb就不生效了
开启 AOF 功能需要设置配置:appendonly yes,默认不开启。

换成yes就生效了

下面的appendonly.aof表示aof的文件名,目录就是工作目录
> set key 111
OK
> set key2 222
OK
然后就可以在appendonly.aof里面看到对应的数据了
每次进行的操作都会记录到这个文本文件中,这个文本文件我们是看得懂的
然后直接干掉服务
重新链接,redis服务里面是有key的
AOF ⽂件名通过appendfilename 配置(默认是 appendonly.aof)设置。保存⽬录同 RDB 持久化⽅式⼀致,通过 dir配置指定。AOF 的⼯作流程操作:命令写⼊(append)、⽂件同步(sync)、⽂件重写(rewrite)、重启加载(load)
2.8 aof是否会影响redis性能
引入aof之后,又要写内存,又要写硬盘,所以redis性能有影响吗
并没有,因为是先写入内存中的缓冲区,写了一定程度上,然后再写入硬盘中,这样就大大降低了写硬盘的次数,写硬盘次数与写硬盘多少,肯定是次数更加影响性能啊
然后aof是顺序写入,每次都写入文件末尾—》顺序写入比随机写入更快一点
2.9 aof缓冲区刷新策略
如果写入在缓冲区中,突然挂了—》就丢数据了
-----》缓冲区有刷新策略的,怎么刷新到硬盘中—》刷新频率高–》性能差,但是可靠性高
Redis 提供了多种 AOF 缓冲区同步⽂件策略,由参数 appendfsync 控制
可配置值 说明
always 命令写⼊ aof_buf 后调⽤ fsync 同步,完成后返回。只要写入了就立即刷新
everysec 命令写⼊aof_buf 后只执⾏ write 操作,不进⾏fsync。每秒由同步线程进⾏ fsync。这个就是每秒刷新一次
no 命令写⼊ aof_buf 后只执⾏ write 操作,由 OS 控制fsync 频率。刷新频率最低
默认情况下配置文件中配置的是everysec
2.10 aof的重写机制
随着命令不断写⼊ AOF,⽂件会越来越⼤,为了解决这个问题,Redis 引⼊ AOF 重写机制压缩⽂件体积。AOF ⽂件重写是把 Redis 进程内的数据转化为写命令同步到新的 AOF ⽂件
因为aof有些内容是冗余的,比如多次lpush,其实就可以弄为一次lpush
多次set,其实就关注最后一次set就可以看,比如一次set一次del,其实就说明都没干的----》所以这个文件记录了中间的过程了,但是redis重启的时候,关注的是最终结果
所以aof的重新机制就是剔除冗余操作,保留最终的结果的相关的命令
AOF 重写过程可以⼿动触发和⾃动触发:
• ⼿动触发:调⽤ bgrewriteaof 命令。
• ⾃动触发:根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定⾃动触发时机。
◦ auto-aof-rewrite-min-size:表⽰触发重写时 AOF 的最⼩⽂件⼤⼩,默认为 64MB。
◦ auto-aof-rewrite-percentage:代表当前 AOF 占⽤⼤⼩相⽐较上次重写时增加的⽐例

- 执⾏ AOF 重写请求。
如果当前进程正在执⾏ AOF 重写,请求不执⾏。如果当前进程正在执⾏ bgsave 操作,重写命令延迟到 bgsave 完成之后再执⾏。 - ⽗进程执⾏ fork 创建⼦进程。
- 重写
a. 主进程 fork 之后,继续响应其他命令。所有修改操作写⼊ AOF 缓冲区并根据 appendfsync 策略同步到硬盘,保证旧 AOF ⽂件机制正确。
b. ⼦进程只有 fork 之前的所有内存信息,⽗进程中需要将 fork 之后这段时间的修改操作写⼊AOF 重写缓冲区中。 - ⼦进程根据内存快照,将命令合并到新的 AOF ⽂件中。
- ⼦进程完成重写
a. 新⽂件写⼊后,⼦进程发送信号给⽗进程。
b. ⽗进程把 AOF重写缓冲区内临时保存的命令追加到新 AOF ⽂件中。
c. ⽤新 AOF ⽂件替换⽼ AOF ⽂件。
重写就只需要把内存中的数据整理就可以了,,内存中的数据就是最终的状态了,所以这个操作和rdb是类似的,不过这里是写入文本,rdb是写入二进制
这个重写过程是创建一个子进程,写入一个新的aof文件,这个父进程还在接受新的请求—》写入旧的aof文件
但是创建子进程的时候,这个子进程就继承了当前父进程的内存状态,所以子进程只是包含了fork之前的内容,fork之后造成的内存修改—》子进程是不知道的
所以还有一个aof_rewite_buf,这个就是放着fork之后收到的数据,然后同步到新的aof文件,最终写好了同步到旧的aof文件中
如果这次aof重写都还没完成,又来了一次新的aof重写,那么这次新的aof重写就不会再次执行了
如果发起aof的时候正在rdb–》会等待rdb完成之后才会aof
注意rdb对fork之后的新数据就不理了,—》数据有差异性,但是rdb其实就是定期备份,所以和最新数据不一致也很正常
所以aof是实时备份
所以aof的场景就更多一点,虽然aof开销比较大
注意一定要有旧的aof文件,如果只有新的—》万一突然服务器挂了呢
> set key 111
OK
> BGREWRITEAOF
Background append only file rewriting started
> bgsave
Background saving started

我们手动执行aof
appendonly.aof看一下这个

我们看到,还是二进制,为什么呢,因为文本文件加载成本高
所以redis引入了混合持久化
就是
一开始的请求操作—》写入文件,文本格式
aof重写之后,就会把内存状态用rdb的二进制的格式写入到aof文件中
后续操作还是文本格式,文本格式追加到这个二进制文件中

这个配置就是默认开启了混合持久化
aof和rdb都存在的时候,会以aof加载为主,因为aof的数据更全
信号解释
信号就是linux的神经系统
信号:信号源,信号的类型,信号的处理函数
比如kill -9就是给指定进程发送一个9号信号
总结
更多推荐



所有评论(0)