分布式缓存、分布式锁笔记
本地缓存
由于 spring boot 默认是单例的,可以利用map集合存放数据。
问题1:缓存击穿 在大量请求同时请求一个缓存中不存在的数据,由于查询数据需要一定的时间。在这段时间内的请求无法在缓存中取得数据,也会直接请求到数据库。
解决方案:将查询数据库的代码上锁,进入被锁住的代码块时先查看缓存中是否存在数据,如果存在直接返回。
问题2:
在单机应用程序时没用问题,但是在分布式集群部署时会造成以下问题:
1.不同主机间的缓存数据不能共享(数据不一致)
2.每台主机都需要去查询数据库的问题。
解决方案:使用分布式缓存(比如 redis )
分布式缓存
将数据以 json 的格式存到 redis 中,当需要获取数据时,先查看 redis 中是否存在数据,不存在再去查数据库。
问题1: 缓存穿透 当大量并发请求一个缓存中不存在,并且数据库中也不存在的数据时,会造成这些请求同时命中数据库。
解决方案:将查询不到的数据也存入 redis 中,并且设置一个过期时间。
问题2:缓存雪崩 缓存中大批量数据到过期时间,数据库查询数据量巨大,引起数据库压力过大甚至down机
解决方案:增加随机过期时间
问题3:缓存击穿 对于一个缓存中不存在key,在某个时间点被大量并发访问,这些请求都不会命中缓存,都会命中数据库,导致数据库崩溃。
解决方案:加锁(只让一个人去查,查完后放入缓存,其他人去缓存获取)
本地锁
使用synchronized锁住方法或者代码块;
访问数据时先查看缓存中是否存在,如果不存在则查询数据库,查询数据库的代码需要上锁。
问题1:当大量并发请求进入时,都检测到缓存中没用数据,都会执行锁住的代码块查询数据库
解决方案:在进入上锁代码块后再进行一次判断,查看缓存中是否存在数据,如果存在直接在缓存中拿数据。
分布式锁
在 redis 中设置一个key比如设置一个名为 lock 的key,值为1,利用
set lock 1 NX
来设置,如果返回nul则代表key已经存在,已经有人在查询,如果返回OK则代表占用锁成功,执行查询数据库的方法,执行结束后将lock删除即可。
但是这样依然存在问题,假设当拿到锁之后出现了异常,导致无法释放锁。就会造成没有查到数据,并且其他人无法进入查询的方法。
可以在 finally 中释放lock锁。
但是当拿到锁之后服务器停电,就会造成finally中的代码无法执行,依然没有解决问题。
为了解决这个问题,我们可以在设置lock这个key的时候给他设置一个过期时间,这样即使一台服务器断电了,lock在到达过期时间后,其他人依然可以去查询数据库。
问题:
当程序运行时间超过key的过期时间时,其他人可能已经拿到了锁。
如果这时运行结束,删除lock锁,删除的会是其他人的锁。
解决方案:
1.在设置lock锁的时候,生成一个随机的UUID,并且将这个lock的值设置为这个UUID
2.删除时先看断一下lock的值是不是自己设置的UUID,如果一致则删除lock
注意事项:删除时不可以使用代码逻辑来进行判断删除,可能在你判断完UUID后redis的key刚好失效,这时候删除的依然是别人的lock。
一定要使用 lua 脚本进行删除,这个脚本一定是同一时刻执行完毕的:
注意事项:
删除时不可以使用代码逻辑来进行判断删除,可能在你判断完UUID后redis的key刚好失效,这时候删除的依然是别人的lock。
一定要使用 lua 脚本进行删除,这个脚本一定是同一时刻执行完毕的:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end