缓存穿透
是什么
查询一个数据库不存在的数据,缓存没有,一直从数据库查。浪费数据库性能,严重一点db宕;
怎么办
- 缓存空对像 就算数据不存在,我们也缓存一个空对象进去,下次查询的时候就不会去数据库查了。弊端是需要消耗一定的内存,如果此刻数据加进去了,存在一段时间的数据不一致,可以在加入数据的时候清缓存。
- 布隆过滤器
- 接口增加校验 例如数据库id都是>0的,你来个查id为-1的数据,这时候直接return
布隆过滤器
原理
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
弊端
- 存在误判,可能要查到的元素并没有在容器中,但是hash之后得到的k个位置上值都是1。如果bloom filter中存储的是黑名单,那么可以通过建立一个白名单来存储可能会误判的元素。
- 删除困难。一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。
代码
对于一个确定的场景,我们预估要存的数据量为n,期望的误判率为fpp,然后需要计算我们需要的Bit数组的大小m,以及hash函数的个数k,并选择hash函数
// guava
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), total);
// redisson
RBloomFilter<Integer> bloomFilter = redissonClient.getBloomFilter("test");
// 数据量,误判率
bloomFilter.tryInit(100000000L,0.03);
缓存雪崩
是什么
缓存集中失效,发生大量缓存穿透。所有的查询落在数据库上,导致雪崩
怎么办
- 加锁排队 一个key只能有一个线程去数据库查询,添加到缓存中
- 数据预热 预先更新缓存
- 过期时间加随机值 防止大量缓存在同一时间过期
缓存击穿
某个扛着大并发的key失效了,大量请求打到数据库,导致db宕
Redis的持久化
持久化有两种形式、一种是RDB即内存数据的二进制序列化形式、第二种是AOF即内存数据修改的日志文本。
- RDB
Redis使用操作系统的多进程COW(Copy On Write)机制来实现快照持久化即调用glibc函数fork产生一个子进程、父进程继续处理客户端请求、两者共享内存。这时候如果客户端请求过来修改内存数据、就会使用COW机制进行数据段分离、将要修改的那一页数据复制一份、父进程对复制的进行修改、随着修改的越来越多、会慢慢接近原来的2倍。
// 相关配置
//900秒内有1个更改 || 300秒内有10个更改 || 60秒内有10000个更改,满足其一就快照一次
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
- AOF
重新执行这些指令即可恢复。随着运行时间的增加、日志文件越来越大、这时候可以使用bgwriteaof指令进行重写即fork一个子进程对内存遍历转化为set指令+期间的增量指令形成新的日志文件。默认配置是aof文件大小是上次rewrite后大小的一倍且文件大于64M。
// 相关配置
// no:表示等操作系统进行数据缓存同步到磁盘(快)
// always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
// everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
appendonly no
appendfilename appendonly.aof
- Redis4.0混合持久化
Redis重启的时候、先加载RDB文件、然后重放增量AOF日志、大大提高了重启效率。
Redis过期键的删除策略
Redis中同时使用了惰性过期和定期过期两种过期策略。
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
- 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
分布式锁
Redis2.8之后可以使用该指令,临界区代码控制在60s内执行完毕,超过60s将无法保证严格串行,需要人工介入修正数据。
set lockName true ex 60 nx
Redis的使用场景
- 分布式锁
- 分布式缓存
- 分布式session
- 限流器
- 计数器
- 排名
Redis的数据类型
- 字符串(String) 内部结构类似ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配即分配空间(capacity)> 长度(len)。1M以下翻倍扩容,以上每次增加1M(最大512M)。
- 列表(list) 内部结构类似LinkedList,插入快、查询慢。内部采用quicklist,元素较少的情况下使用一块连续的内存存储、无前驱和后继指针即压缩列表(ziplist),元素多的时候以ziplist为单位加上前驱和后继指针构成的quicklist。
- 字典(hash) 内部结构类似HashMap,数组+列表构成,redis采用渐进式rehash,同时保存新旧两个哈希结构,然后后续通过定时任务逐渐将旧的迁移到新的,迁移完成,旧的删除。
- 集合(set) 内部结构类似HashSet,无序唯一。
- 有序集合(zset) 跳跃列表