Redis进阶
- 1. Jedis客户端
- 2. SpringBoot整合Redis
- 3. Redis持久化
1. Jedis客户端
访问链接:https://github.com/redis/jedis
- Jedis为Redis的Java客户端;
- 可通过Java编程实现Redis操作;
1.1 Jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.0</version>
</dependency>
1.2 Jedis使用前提
- Redis服务器防火墙处于关闭状态;
- Redis服务器允许远程访问:1)redis.conf配置文件中注释掉
bind 127.0.0.1
;2)redis.conf配置文件中设置protected-mode no
;3)重启Redis服务;
1.3 连通性测试
private static String host="Redis服务器IP地址";
private static int port=6379;
public static void main(String[] args) {
// 创建Jedis对象
Jedis jedis = new Jedis(host, port);
// 测试是否连通
String pong = jedis.ping();
System.out.println(pong);
jedis.close();
}
1.4 Jedis实例-模拟手机验证码功能
分析:
1)用户在前端输入手机号,点击发送验证码将手机号传送到后端接收;
2)后端接收手机号,1)记录手机号,为其绑定发送次数为3,每发送一次,次数减1;2)发送并保存验证码,验证码有过期时间;
3)用户输入验证码,后端在验证码有效时间内核对验证码是否正确;
代码:
/**
* Author:NorthSmile
* Create:2023/6/2 16:55
* 模拟手机验证码
*/
public class AuthCode {
/**
* 获取Jedis对象
*/
public Jedis getJedis(){
String host="192.168.139.100";
int port=6379;
return new Jedis(host,port);
}
/**
* 1.获取并保存电话号
* 2.为手机号绑定发送次数
* 3.发送验证码;
* 4.验证码具有过期时间;
*/
public String verifyCode(String phone){
Jedis jedis = getJedis();
// 获取手机号
// 获取验证码
String code=getCode();
System.out.println(code);
// 手机号是否存在
if (!jedis.exists(phone)){
String num="3";
jedis.setex(phone,24*60*60,num);
// 发送验证码,为其绑定过期时间
jedis.setex("authCode",120,code);
// 发送次数减1
jedis.decr(phone);
}else{
// 判断是否还有发送次数
if (Integer.parseInt(jedis.get(phone))<=0){
System.out.println("今日发送次数已经用光,请明天再尝试!");
jedis.close();
}else{
// 发送验证码,为其绑定过期时间
jedis.setex("authCode",120,code);
// 发送次数减1
jedis.decr(phone);
}
}
return code;
}
/**
* 生成6位随机数
* @return
*/
private String getCode() {
// 生成6位随机数字
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < 6; i++) {
code.append(random.nextInt(10));
}
return code.toString();
}
/**
* 验证码校验
* @param phone
* @param code
* @return
*/
private void checkCode(String phone,String code){
Jedis jedis = getJedis();
// 确保该电话号存在且验证码没有超时
if (jedis.exists(phone)){
if (jedis.exists("authCode")) {
String authCode = jedis.get("authCode");
if (authCode.equals(code)) {
System.out.println("验证成功");
} else {
System.out.println("输入验证码有误");
}
}else{
System.out.println("请先发送验证码");
}
}else{
System.out.println("输入电话有误");
}
jedis.close();
}
@Test
public void testVerify() {
String phone="12345";
// String code=verifyCode(phone);
checkCode(phone,"351225");
}
}
2. SpringBoot整合Redis
2.1 整合步骤
- 在pom.xml文件中引入redis相关依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
- application.properties配置redis配置
#Redis服务器地址
spring.redis.host=Redis服务器IP地址
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
- 添加redis配置类
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
- 测试一下
RedisTestController中添加测试方法
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis() {
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
3. Redis持久化
Redis 提供了2个不同形式的持久化方式:
- RDB(Redis DataBase);
- AOF(Append Only File);
3.1 RDB(Redis DataBase)
-
介绍:在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是Snapshot快照,它恢复时是将快照文件直接读到内存里;
-
持久化流程:Redis会单独创建(fork)一个子进程来进行持久化 1)将数据写入到一个临时文件中 ;2)持久化过程结束以后,使用该临时文件替换上次持久化好的文件;
-
优缺点:
1)优点:整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能;如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效;
2)缺点:最后一次持久化后的数据可能丢失; -
写时复制技术:使用临时文件替换上一次持久化文件的好处是保证数据一致性和完整性;
-
Fork介绍:
1)Fork的作用是复制一个与当前进程一样的进程:新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程;
2)在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”;
3)一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程;
3.1.1 配置文件redis.conf中RDB持久化相关设置
1)持久化文件名
- 默认RDB持久化文件名:dump.rdb;
2)持久化文件位置
- 默认保存路径:启动Redis服务时所在目录,可使用
config get dir
查询rdb文件的目录;
3)命令save VS bgsave
save
:save时只管保存,其它不管,全部阻塞,手动保存,不建议;
格式:save 秒钟 写操作次数
;
RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件;
默认是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次;
禁用方式:不设置save指令,或者给save传入空字符串;bgsave
:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求;- 可以通过
lastsave
命令获取最后一次成功执行快照的时间; - 可通过在配置文件中注释掉save命令或为save命令传入空参禁用RDB持久化方式;
4)flushall命令
- 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义;
5)stop-writes-on-bgsave-error
- 当Redis无法写入磁盘的话,直接关掉Redis的写操作,推荐yes;
6)rdbcompression 压缩文件
- 对于存储到磁盘中的快照,可以设置是否进行压缩存储,如果是的话,redis会采用LZF算法进行压缩。
- 如果不想消耗CPU来进行压缩的话,可以设置为关闭此功能,推荐yes;
7)rdbchecksum 检查完整性
- 存储快照后,可以让redis使用CRC64算法来进行数据校验;
- 这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能,推荐yes;
3.1.2 RDB的备份恢复操作
- 1)通过
config get dir
查询rdb文件的目录,默认为Redis启动目录; - 2)将备份数据rdb文件放置在该目录下,并将其命名为RDB持久化文件名(默认为dump.rdb);
- 3)启动Redis服务即可将备份数据进行加载;
3.2 AOF(Append Only File)
-
介绍:以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作;
-
AOF默认不开启,需要在redis.conf文件中设置
appendonly yes
; -
AOF持久化文件名默认为 appendonly.aof;
-
AOF文件的保存路径,同RDB的路径一致,默认为服务启动的当前目录;
-
AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失);
-
持久化流程:
1)客户端的请求写命令会被append追加到AOF缓冲区内;
2)AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
3)AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
4)Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;
- 优点:
1)备份机制更稳健,丢失数据概率更低。
2)可读的日志文本,通过操作AOF稳健,可以处理误操作。 - 缺点:
1)比起RDB占用更多的磁盘空间;
2)恢复备份速度要慢;
3)每次读写都同步的话,有一定的性能压力;
4)存在个别Bug,造成恢复不能;
3.2.1 配置文件redis.conf中持久化相关设置
1)AOF同步频率设置
appendfsync always
始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好;appendfsync everysec
每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失;appendfsync no
redis不主动进行同步,把同步时机交给操作系统;
2)Rewrite压缩
- AOF持久化方式采用文件追加方式进行日志记录,带来的影响则是日志文件会越来越大,磁盘负荷巨大;
- Redis采用重写机制,当AOF文件达到一定条件时,对AOF持久化文件进行重写;
- 重写过程采用“写时复制技术”通过子进程使用临时文件记录新数据,最后替换旧的记录文件;
- 可使用命令
bgrewriteaof
手动触发重写机制; - 重写时机:下列两个条件必须同时满足,即Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写
1)auto-aof-rewrite-percentage
:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发);
2)auto-aof-rewrite-min-size
:设置重写的基准值,最小文件64MB,达到这个值开始重写; - 重写原理:AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据
1)no-appendfsync-on-rewrite=yes
,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能);
2)no-appendfsync-on-rewrite=no
, 还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低);
3.2.2 AOF的备份恢复操作
1) 正常恢复
- 1)通过
config get dir
查询rdb文件的目录,默认为Redis启动目录; - 2)将备份数据aof文件放置在该目录下,并将其命名为AOF持久化文件名(默认为appendonly.aof);
- 3)启动Redis服务即可将备份数据进行加载;
2) 异常恢复
- 使用
redis-check-aof --fix appendonly.aof
命令修复发生异常的appendonly.aof文件; - 启动Redis服务即可将备份数据进行加载;
3.3 两种持久化方式对比
- 如果对数据不敏感,可以选单独用RDB;
- 不建议单独用 AOF,因为可能会出现Bug;
- 如果只是做纯内存缓存,可以都不用;
- RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储;
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾;
- Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大;
- 建议同时开启两种持久化方式,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整;
- 性能建议:
1)RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则;
2)如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了;代价:一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的;
3)只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上;
4)默认超过原大小100%大小时重写可以改到适当的数值;
参考资料:《尚硅谷》