Redis介绍
Redis是一个使用C语言开发的数据库,Redis数据存储在内存中,所以读写速度非常快。
应用场景
- 数据(热点)⾼并发的读写
- 海量数据的读写
- 对扩展性要求⾼的数据
分布式缓存和本地缓存的区别
分布式缓存
本地缓存
缓存一致性
较好
较弱,每个实例都有自己的缓存
堆内存占用
不占用
占用,影响垃圾回收
速度
较慢,因为需要网络传输和序列化
较快
使用场景
要求数据一致性,访问量大的场景
对数据一致性没有特别高的要求,且访问次数多的场景
本地缓存的实现:
- 使用特定数据结构,比如ConcurrentHashMap
- 使⽤开源的缓存框架 Ehcache,Ehcache 封装了对于内存操作的功能
- Guava Cache 是 Google 开源的⼯具集, 提供了缓存的边界操作⼯具、
Redis和Memcache的区别
Redis
Memcache
存储方式
持久化
断电丢失
支持数据类型不同
String,hash,list,set,zset
只支持key-value
速度
快
慢
缓存处理流程
常见数据结构
- String(字符串)
最大容量为 512M
- list(列表)
list 是字符串列表,按照插⼊顺序排序。元素可以在列表的头部(左边)或者尾部(右边)进⾏添加。最大容量为 2^32-1 个。可以做消息队列。
- hash(哈希)
Redis hash 是⼀个键值对(key-value)集合。Redis hash 是⼀个 String 类型的 field 和 value 的映射表,hash 特别适合⽤于存储对象。最大容量为 2^32-1 个。
- set(集合)
Redis 的 set 是 String 类型的⽆序集合。最大容量为 2^32-1 个。
- zset(sorted set:有序集合)
Redis zset 和 set ⼀样也是 String 类型元素的集合,且 不允许重复的成员。不同的 zset 是每个元素都会关联⼀个 double 类型的分数。zset 通过这个分数来为集合中所有元素进⾏从⼩到⼤的排序。zset 的成员是唯⼀的,但分数(score)却可以重复。最大容量为 2^32-1 个。适合做排行榜。
zset底层实现
跳表,这篇文章对跳表进行了详细的讲解:
过期删除策略
常见的删除策略:
- 定时删除:设置过期时间的同时,创建一个timer,过期时间一到就主动删除
- 惰性删除:放任不管,每次获取时,才判断是否过期,过期就删除,属于被动删除
- 定期删除:每隔一段时间就对数据库进行一次删除过期键的操作
Redis 采用惰性删除 + 定期删除的方式管理键。既减小 cpu 压力的同时,也保证了数据的准确性。
内存淘汰机制
由于可能发生,既没有被惰性删除也没有被定期删除,但内存很快满了的情况出现,所以需要一定的内存淘汰机制。有 6 中淘汰策略:
- no-eviction:不会继续服务写请求,读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
- volatile-lru:尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的key不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。(这个是使用最多的)
- volatile-ttl:跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰,即淘汰将要过期的数据。
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中随机选择数据淘汰。
- allkeys-lru:区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
- allkeys-random:从全体的key集合(server.db[i].dict)中任意选择数据淘汰。
持久化机制
- RDB:将Redis在内存中的数据库记录定时dump到磁盘上的RDB持久化。
- AOF:将Redis的操作日志以追加的方式写入文件。
RDB
RDB 持久化是指在指定的时间间隔内将内存中的数据集快照写⼊磁盘,实际操作过程是 fork ⼀个⼦进程,先将数据集写⼊临时⽂件,写⼊成功后,再替换之前的⽂件,⽤⼆进制压缩存储。
优点:
- RDB 是紧凑的⼆进制⽂件,⽐较适合备份,全量复制等场景
- RDB 恢复数据远快于 AOF
缺点:
- 无法实现实时或者秒级持久化
- 新老版本无法兼容RDB
AOF
AOF 持久化以⽇志的形式记录服务器所处理的每⼀个写、删除操作,查询操作不会记录,以⽂本的⽅式记录,可以打开⽂件看到详细的操作记录。
优点:
- 更好地保护数据不丢失
- append-only模式写入性能比较高
- 适合做灾难性的误删除紧急恢复
缺点:
- 对于同一份文件,AOF文件要比RDB快照大
- 会对QPS有所影响
- 数据库恢复慢,不适合做冷备
缓存穿透
查询缓存中没有,数据库也没有的数据会导致缓存穿透。
解决方法:
- 布隆过滤
将所有查询的参数都存储到一个 bitmap 中,查询之前先去 bitmap 里面验证,如果存在就进行底层缓存的数据查询;如果不存在就进行拦截。
可以用于实现数据字典,进行数据的判重,集合求交集。
- 缓存空对象
直接缓存一个空对象,但是会有两个问题:
- 缓存将存储更多的键值对,可能会遭到恶意攻击,至于内存空间的浪费;可以通过设置过期时间来控制。
- DB与缓存数据不一致,可以通过异步消息来进行数据更新的通知。
缓存雪崩
一段时间内,大量的缓存失效,导致数据库压力突然增大,导致缓存雪崩。
解决方法:
- 分散失效时间
- DB访问限制,进行限流
- 多级缓存设计
缓存击穿
缓存中没有,但是数据库中油的数据,这时由于并发用户多,就会造成数据库压力瞬间增大。
解决方法:
- 设置热点数据永不过期
- 加互斥锁,使写数据的只有一个线程执行:
缓存更新策略
先更新数据库,再更新缓存
- 会导致线程安全问题
两个线程一起更新数据,就会造成脏数据的问题。
- 更新缓存的复杂度相对较高
因为一般存入缓存的数据都要经过一系列的计算。
先删除缓存,再更新数据库
可能会导致数据不一致的问题,比如,刚删掉缓存,另一个线程马上读取请求,缓存还是旧的。
解决方法只能是写数据成功后,再更新一次缓存。
先更新数据库,再删除缓存
可能会造成短暂的数据不一致,在更新数据库成功后和删除缓存之前,会有一定的数据不一致现象,不过可以接受。
参考:《2020最新Java基础精讲视频教程和学习路线!》
链接:https://juejin.cn/post/693720...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。