redis笔记大全
- 1、Redis命令和数据类型
-
- [(1) 通用命令](#(1) 通用命令)
- [(2) 字符串类型](#(2) 字符串类型)
- [(3) KEY命名规范](#(3) KEY命名规范)
- [(4) Hash类型](#(4) Hash类型)
- [(5) List类型](#(5) List类型)
- [(6) Set类型](#(6) Set类型)
- [(7) SortedSet类型](#(7) SortedSet类型)
- 2、Java操纵Redis👉SpringDataRedis
- 3、ThreadLocal和HandlerInterceptor实现简单接口拦截
- 4、使用HttpSession实现登录功能,存储用户信息的弊端
- 5、数据库和缓存数据一致性
- 6、缓存穿透
- 7、缓存雪崩
- 8、缓存击穿
-
- [8.1 基于redis的setnx实现互斥锁](#8.1 基于redis的setnx实现互斥锁)
- [8.2 基于逻辑过期实现互斥锁的时候要开启新线程进行缓存重建](#8.2 基于逻辑过期实现互斥锁的时候要开启新线程进行缓存重建)
跟着bilibili黑马程序员的老师学习的笔记😀
1、Redis命令和数据类型
(1) 通用命令
- KEYS:查看符合模板的所有key
- DEL:删除指定的key
- EXISTS:判断key是否存在
- EXPIRE :给一个key设置有效期,有效期到期时该key会被自动删除(①
-1表示永久有效期,②-2表示已过期,③ 单位是秒) - TTL:查看一个KEY的剩余有效时间
(2) 字符串类型



如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储:


(3) KEY命名规范

(4) Hash类型

(5) List类型
Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表。
- 有序
- 元素可以重复
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。


(6) Set类型
Redis的Set结构与Java中的HashSet类似。
- 无序
- 元素不可重复
- 支持交集、并集、差集等功能



(7) SortedSet类型
Redis的SortedSet是一个可排序的set集合。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序。
- 可排序
- 元素不重复
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。




2、Java操纵Redis👉SpringDataRedis

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。
官网地址:https://spring.io/projects/spring-data-redis
- 提供了对不同Redis客户端的整合(Lettuce和Jedis)
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
支持Redis哨兵和Redis集群 - 支持基于Lettuce的响应式编程
- 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。

为了节省内存空间,一般并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
java
@Test
public void testStringRedisTemplate() throws JsonProcessingException {
// JSON序列化工具
ObjectMapper objectMapper = new ObjectMapper();
// 把学生对象转换为JSON字符串
Student stu = Student.builder().id(1).name("迪丽热巴").score(666.666).build();
String stuStr = objectMapper.writeValueAsString(stu);
// 存入Redis
stringRedisTemplate.opsForValue().set("success:stu:001", stuStr);
// 读数据
String stuJson = stringRedisTemplate.opsForValue().get("success:stu:001");
// 把学生JSON字符串转换为学生对象
Student stuFromRedis = objectMapper.readValue(stuJson, Student.class);
System.out.println(stuFromRedis);
}
3、ThreadLocal和HandlerInterceptor实现简单接口拦截
- ThreadLocal在同一线程内共享登录用户信息
- HandlerInterceptor拦截接口,判断当前用户是否登录(通过Session或携带Redis登录令牌)。登录了则往ThreadLocal中存储用户信息,否则返回401
4、使用HttpSession实现登录功能,存储用户信息的弊端
集群的session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。
session的替代方案应该满足:
- 数据共享(让多个微服务,多台Tomcat都可以访问登录用户的信息)
- 内存存储(和session一样都是基于缓存的)
- key、value结构
Redis就非常合适😄
-
发送验证码的时候:用手机号作为KEY,验证码值作为VALUE存储到Redis
-
登录/注册的时候:根据手机号从Redis查询验证码,判断用户传递的验证码是否正确;登录/注册完成后,生成随机字符串(token)作为KEY,用户信息作为VALUE存储到Redis。然后,把token传递给客户端。客户端需要保存token,以后每次访问接口的时候都要携带token。我会在LoginInterceptor中从Header中获取token值,根据token值从redis中查询用户信息。如果没有查询到用户信息就会返回401,查询到了用户信息后才会放行。存储登录的用户信息的时候,VALUE用的是Redis的Hash类型。
-
保存登录的用户信息,可以使用String结构,以JSON字符串的形式来保存,但会有额外内存占用,并且不方便修改用户信息中的某一个值。
-
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD,并且内存占用更少。

5、数据库和缓存数据一致性
-
自己写代码实现👉当更新数据库数据的时候,把更新的数据同步同步到redis中
-
在同步redis数据的时候,采取
删缓存的策略(可以避免过多无效的写操作),等下次查询缓存的时候,缓存未命中的时候,再查询数据库并更新缓存 -
缓存和数据库操作保证一致性(要么同时成功,要么同时失败)❓ ① 如果是单体架构👉把缓存操作和数据库操作放在同一个事务中;② 如果是分布式架构👉使用分布式事务保证两个操作的一致性
-
应该操作完数据库后,再删除缓存(修改完数据库后,删除缓存的操作执行速度是很快的,产生并发问题的可能性低)


6、缓存穿透
-
缓存穿透:客户端请求的数据在缓存中和数据库中都没有,这些请求都会打到数据库。
-
(1) 缓存空对象,并设置较短的过期时间(实现简单😀 但是会有额外的内存消耗,并且可能造成数据的短期不一致)
-
(2) 布隆过滤(占用内存少😀但是实现复杂,会有误判的可能)
🌼 提前预判:布隆过滤器是一种空间高效的概率型数据结构,会预先将所有合法的缓存 key(如数据库中存在的用户 ID、商品 ID)存入其中。
🌼 拦截无效请求:当请求到达时,先通过布隆过滤器校验 key(如数据库中存在的用户 ID、商品 ID) 是否存在:
① 若过滤器判定 "不存在",则直接返回空结果,无需查询缓存和数据库,拦截无效请求;
② 若判定 "可能存在" ,再继续查询 Redis 缓存,缓存未命中时再查数据库。
🌼核心特性:布隆过滤器可能误判 不存在为存在,判定不存在则一定不存在,确保所有无效 key 都被拦截,从源头避免缓存穿透(即请求不断查询不存在的 key,绕过缓存直击数据库)。

7、缓存雪崩
- 缓存雪崩:同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
📕解决方案:
① 缓存预热的时候,缓存的数据的过期时间增加随机值(保证这些数据不会在同一时刻都过期)
② 利用Redis集群提高服务的可用性
③ 给缓存业务添加降级限流策略(当redis服务宕机的时候,拒绝部分请求,避免给数据库带来巨大压力)
④ 给业务添加多级缓存
8、缓存击穿
- 缓存击穿问题(热点Key问题):一个被高并发访问 并且缓存重建较复杂的key突然失效了,无数的请求会在瞬间给数据库带来巨大的冲击。

- 互斥锁解决缓存击穿:当热点key过期,多个请求到达系统的时候,只允许一个请求(线程)执行缓存重建操作,其他请求都等待(每个一段时间尝试从redis中获取缓存重建的数据)。保证了数据的一致性😀,但是会有多个线程等待缓存重建,系统性能收到影响。

- 添加逻辑过期时间解决缓存击穿:给热点key增加逻辑过期时间(不要给热点key增加redis的ttl,让热点key永不过期)。当一个热点key过期的时候,当一个线程A发现它过期了,该线程A创建一个新线程B进行缓存重建操作,然后返回redis中的旧数据。其他线程发现热点key过期并且已经有其他线程进行缓存重建了,其他线程就直接返回旧数据。

8.1 基于redis的setnx实现互斥锁
- redis的
setnx命令会当某个key不存在的时候才给该key设置值。Set the value of a key, only if the key does not exist(只有这个key不存在的时候才给其设置值) setnx lock 6是获取锁del lock是释放锁(删除锁是释放锁)- 要给key设置过期时间,避免因为某些原因导致死锁


- 热点key一般会提前进行缓存预热,把热点key数据提前存入redis
8.2 基于逻辑过期实现互斥锁的时候要开启新线程进行缓存重建
- 这里可以用JDK的线程池
java
private ExecutorService executors= Executors.newFixedThreadPool(8);