为什么使用缓存
高性能,高可用,高并发。
什么是缓存击穿?缓存穿透?缓存雪崩?
击穿:redis中没有查询到数据。解决:设置热点数据永不过期。加载DB时防止并发。
穿透:redis和mysql中都没有查询到数据。解决:参数校验,将没有数据的情况也存入redis中,引入布隆过滤器
雪崩:redis中大量数据同时过期。解决:设置不同的过期时间。
布隆过滤器:
- 如果判断一个元素不在集合中则一定不在
- 如果判断一个元素在集合中则存在一定误判率
- 布隆过滤器只能加数据不能减数据
对数据进行修改时,如何保证Redis与数据库的数据一致?
- 先删缓存,后写数据库。存在脏读。
解决:
1.先把缓存修改为特殊值(-999),客户端读到特殊值延时查询。
2.延时双删,先删缓存,再写数据库,再删缓存。 - 先写数据库,再删缓存。存在缓存删除失败,数据不一致。
解决:
1.缓存设置过期时间
2.引入MQ,保证原子操作。mq设置两个消费者同时删除缓存和db,删除失败消息还在,MQ重试机制会删除。
通用解决:
将热点数据设置为永不过期,但再value中写入逻辑过期,另起线程扫描key,对于逻辑上过期的缓存进行删除。
总结:始终只能保证一定时间内的最终一致性。
如何设计一个分布式锁?并优化
分布式锁:所有进程都能访问到的地方,设置一个锁资源,让进程来竞争。(数据库,zookeeper,Redis)
Redis实现分布式锁:
SETNX key value:当key不存在时,就将key设置为value。并返回1.如果key存在,就返回0.
EXPIRE key locktime:设置key的有效时长。
DEL key:删除
GETSET key value:先GET,在SET,先返回key对应的值,如果没有,将key设置成value。
1.最简单的分布式锁:SETNX 加锁,DEL解锁。
问题1:锁获取失败后不回主动解锁,这个锁就被锁死了。
解决1:给锁设置时长。
问题2:SETNX和EXPIRE不是原子性的,获取锁进程没到EXPIRE指令就挂了。
解决2:将锁的内容设置为过期时间(客户端时间+过期时长),SETNX获取失败时,拿这个时间和当前时间比对,如果过期,就先删除锁,再重新上锁。
问题3:在高并发场景下,会产生多个进程同时拿到锁的情况。
解决3:SETNX失败后,获取锁上时间戳,然后用GETSET,将自己的过期时间更新上去,并获取旧值,和之前获取的时间戳不一致,就表示这个锁被其他线程占用。
java
public boolean tryLock(RedisConnection conn) {
long nowTIme = System.currentTimeMills();
long expireTime = nowTime + 1000;
if(conn.SETNX("myKey", "1") == 1) {
conn.EXPIRE("myKey", 1000);
return true;
} else {
long oldVal = conn.get("myTime");
if(oldVal != null && oldVal < nowTime) {
long currnetVal = conn.GETSET("myKey", expireTime); //CAS,如果和之前获取的锁不一样,就失败
if(oldVal == currentVal) {
conn.SET("mykey", 1000);
return true;
}
return false;
}
return false;
}
}
注:上面优化根本问题是SETNX和EXPIRE指令无法保证原子性。Redis2.6提供了直接执行Lua脚本方式。通过Lua脚本保证原子性。Redission就是在此基础上实现公平锁,非公平锁等。
Redis如何设置Key的过期时间?
redis设置过期时间:1.EXPIRE 2.SETEX
实现原理:
1.定期删除:每隔一段时间执行一次删除过期key的操作。
2.懒汉式删除:当使用get,getset等指令获取数据时,判断key是否过期,过期先把key删除,在执行后面操作。
定期删除会遍历每个databasae(默认16个),检查当前库中指定个数的key(默认20个)。随机抽查这些key,过期就删除。
海量数据下,如何快速查找一条记录?
1.使用布隆过滤器,快速过滤不存在的记录。使用redis的bitmap结构来实现布隆过滤器。
2.在Redis中建立数据缓存。以普通字符串形式存储(userId->UserInfo)。以hash来存储(userId key -> Username field)。以整个hash来存储所有的数据。UserInfo -> field用userId,value就用user.json。一个hash最多支持2^32-1个键值对。
缓存击穿:对不存在的数据也建立key。
缓存过期:将热点数据设置成永不过期,定期重建缓存,使用分布式锁重建缓存。
3.查询优化:redis按槽位分配数据,自己实现槽位激素那,找到记录应分配的机器,直接去目标机器上去找。