Redis笔记

Redis官方文档

Redis速成(小白也可掌握)

安装

Linux终端输入:

1
2
3
4
5
sudo apt install redis-server   # 安装Redis

redis-cli --version # 安装后查看Redis版本

sudo nano /etc/redis/redis.conf # 修改Redis配置

redis.conf文件中进行如下修改:

1
2
3
4
5
6
7
8
9
# 将绑定地址改为 0.0.0.0,让Redis可远程访问
# bind 127.0.0.1 ::1
bind 0.0.0.0

#取消注释 requirepass 启动密码认证,后面跟Redis密码
requirepass 自定义密码

# 以守护进程运行Redis
daemonize yes

修改保存后重新启动Redis并登录:

1
2
3
systemctl restart redis-server  # 启动Redis

redis-cli -h 127.0.0.1 -p 6379 -a 密码 # 登录

数据类型

Redis有5种最常用的数据类型:

数据类型 样例 描述
String Hello World 字符串
Hash {name: “saoke”, age: 18} JSON形式的字符串
List [A → B → B] 有序列表,元素可重复
Set {A, B, C} 无序集合,元素不可重复
SortedSet {A: 1, B: 2, C: 3} 有序集合,元素不可重复

操作命令

可以在Redis官方文档查询。

String 类型

存储:set key value

获取

SpringBoot 操作 Redis

可使用SpringDataRedis。 引入依赖:

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>

SpringDataRedis为了能使用对象作为键和值,实现了自动的序列化与反序列化。如果不经过自主配置,默认会对字符串键进行繁琐的序列化操作。

配置序列化方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 创建 Template
RedisTemplate<String, Object> template = new RedisTemplate<>();

// 设置连接工厂
template.setConnectionFactory(connectionFactory);

// key 和 value 使用 string 序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());

// value 和 hashValue 使用 json 序列化
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);

return template;
}
}

但由于使用JSON序列化器需要Redis中额外记录包名用于反序列化,占用了太多空间,实际开发中一般直接使用StringRedisTemplate,程序员自己利用fastJson等工具转换JSON。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@SpringBootTest
class RedisTest {

@Autowired
private StringRedisTemplate stringRedisTemplate;

@Autowired
private RedisTemplate<String, Object> jsonRedisTemplate;

@Test
public void stringRedisTemplateTest() {
// String 类型操作
stringRedisTemplate.opsForValue().set("name", "骚客");
String name = stringRedisTemplate.opsForValue().get("name");

// Hash 类型操作
HashMap<String, String> map = new HashMap<>();
map.put("telephone", "13888888888");
map.put("password", "123456");
stringRedisTemplate.opsForHash().putAll("admin", map);
Map<Object, Object> mapResult = stringRedisTemplate.opsForHash().entries("admin");

stringRedisTemplate.opsForHash().put("key", "field", "value");
String fieldResult = (String) stringRedisTemplate.opsForHash().get("key", "field");
}

@Test
public void jsonSerializerTest() {
jsonRedisTemplate.opsForValue().set("name", "骚客");
String name = (String) jsonRedisTemplate.opsForValue().get("name");
}
}

缓存更新策略

可分为三类主动更新Redis的方案。

Write Through(写穿)

Write Back(写回)

只操作缓存,由其他线程异步地将缓存更新到数据库,保证最终一致。

Cache Aside(旁路缓存)

由程序员在代码中更新数据的同时操作缓存。

需要注意线程安全。操作数据库和缓存的先后顺序的两种情况都会导致数据不一致。

先删除缓存再操作数据库

先操作数据库再删除缓存

由于第二种方法只有当Redis中无该缓存数据时才会导致不一致,且Redis操作比数据库快,不容易被中断,因此先操作数据库再删除缓存更好。

缓存穿透

缓存穿透是指客户端请求的数据在缓存和数据库中都不存在,这样缓存永远都不会生效,这些请求都会到达数据库。

解决方法包括以下几种:

  1. 缓存空对象。当数据库查询不到时在Redis中缓存一个对应的null,并设置较短的过期时间。该方法的优点是简单方便,缺点是需要额外的缓存空间,如果有攻击者一直请求不存在的数据,缓存就会被占满。
  2. 布隆过滤器
  3. 增强查询id数据的复杂度,避免被猜测id规律
  4. 对请求数据进行合法性校验,可以直接排除一些无效请求
  5. 热点参数限流

缓存雪崩

缓存雪崩是指在同一时间大量的缓存key失效,或Redis服务器宕机,导致大量请求到达数据库。

例如,为了给缓存预热,提前把数据批量导入缓存,导致这批缓存的过期时间是一样的,就可能出现缓存雪崩。这种问题比较好解决,给过期时间加个随机数就好了。

对于Redis服务器宕机,可以搭建Redis集群,利用Redis的哨兵机制可以避免缓存雪崩。此外还可以给缓存业务添加降级限流策略,或给业务添加多级缓存,如Nginx、JVM和Redis的多级缓存。

缓存击穿

缓存击穿是指某个被高并发访问的key失效了,且重建该缓存比较耗时,则在重建缓存的过程中的大量请求都会到达数据库。

可以加上互斥锁重建缓存,当其他请求到来时获取不到锁说明已经在重建缓存了,则休眠一段时间再尝试查询缓存直到命中。

互斥锁

但该方法所有其他线程都只能等待缓存重建,性能较差,且如果重建缓存需要得到其他服务的锁,就容易造成死锁。

另一种方法逻辑过期,不为该热点缓存直接设置过期时间,而是把过期的时间作为数据的一部分存在value中,然后把重建缓存的任务开启一个专门的线程去做,这样线程通过value中的过期时间判断是否过期,若过期则尝试获取锁去开启重建缓存的线程,若获取锁失败说明已经在重建缓存了,则直接返回过期的数据。

逻辑过期

这里的互斥锁可以用Redis中的setnx指令实现(SpringDataRedis中是setIfAbsent),只有在该键不存在时才能写入。setnx相当于获取锁,del删除相当于释放锁。

这种方法的优点是线程无需等待性能较好,缺点是实现复杂,不能保证一致性。