- 定时删除(redis不使用):
在设置键的过期时间的同时,创建一个定时器,让定时器执行对键的删除操作,占用大量CPU
- 惰性删除:
每次取的时候先判断 expires 对象里面的键是否已经过期,如果过期,则删除键,否则,返回该键 - 定期删除:
每隔一段时间,程序对数据库遍历检查一遍,然后删除过期的键
setnx redisLock true
...业务逻辑执行...
del redisLock
异常情况下锁自动释放
setnx redisLock true
expire redisLock 5
... 业务逻辑执行 ...
del redisLock
原子命令
set redisLock true ex 10 nx
... 业务逻辑执行 ...
del redisLock
- 在zookeeper中一但服务器进程down掉或者心跳超时,zk中的临时序列会自动释放。但是Redis中没有这样的机制。
- 如果业务在加锁和释放锁之间的逻辑执行的太长,超出了锁的超时时间,锁就会自动超时释放。甚至在第一个业务执行结束后,释放了后进入业务的分布式锁,打乱了整个锁的持有和释放。
合理设定锁持有时间,使用lua脚本,乐观锁的方式删除锁
String random = Math.random() + "";
jedis.set("redisLock", random, "NX", "EX", 5);
... 业务逻辑执行 ...
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script,Collections.singletonList(lockKey), Collections.singletonList(random));
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常
缓存雪崩解决方案:
- 给缓存的失效时间,加上一个随机值,避免集体失效。
- 使用互斥锁,但是该方案吞吐量明显下降了。
- 双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。
- 然后细分以下几个小点:从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。 缓存穿透解决方案:
- 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。
- 采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
- 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回。