2024秋招虽然感觉上已经快要结束了,但还是在这里记录下秋招学习的一些心得,也希望自己能够在总结的过程中梳理的更加清楚。
Redis缓存与分布式锁
起因是面试的时候,面试官问,为什么Redis作为缓存,可以实现分布式锁。
因此这里总结和梳理一下Redis缓存和分布式锁相关的内容,希望可以帮助到大家。
缓存
缓存参考
将数据放到缓存中,可以加速访问。
缓存与内存
请求一个数据先去看缓存中有没有,如果缓存命中的话,则直接返回结果,如果没有命中的话,就去db里查,查完之后将数据放入缓存。
注意:在开发中,凡是放入缓存中的数据应该指定过期时间,使其可以在系统没有主动更新数据也能自动出发数据加载近缓存的流程中。
一般将下列数据放入到缓存中:
1.数据一致性要求不高
2.访问量大且更新频率不高的数据
在分布式系统中,商品服务可能部署好多个服务器,每一个商品服务都带一个本地缓存,那么问题就暴露出来了。
问题:如果收到了一个修改商品价格的请求,这个请求被负载均衡到第一个服务器,然后成功的进行了修改,并且成功的更新了缓存。但其它的服务器的缓存并没有进行更新,用户很有可能会拿到一个错误的缓存,出现数据一致性的问题。
本地缓存模式在分布式系统中不能用。
本地锁
这里贴一篇我看到的写的比较好的关于本地锁的文章。
锁的介绍
Redis
Redis是一种基于内存的数据库,所以缓存数据默认情况下是存储在内存的。如果是分布式的Redis的话,一般是服务器的内存中。
这里Redis基于内存是很重要的。
分布式锁
如何确保每一个JVM上的线程共用一把锁:把锁抽取出来,让线程们在同一片内存相遇。锁是不能凭空存在的,本质还是要在内存中,此时可以使用Redis缓存作为锁的宿主环境。
锁是为了保证“唯一”。
Redis提供了setnx指令,如果某个key当前不存在则设置成功并返回true,否则不再重复设置,直接返回false。
Redis与分布式锁
简单版
Redis分布式锁基于setnx命令的特性:向Redis发送setnx命令,然后判断Redis返回的结果是否为1,结果是1表示setnx成功,那本次就获得锁了,可以继续执行业务逻辑;如果结果是0,则表示setnx失败,那本次就没有获取到锁,可以通过循环的方式一直尝试获取锁。
升级版
问题是,如果释放锁之前宕机了,那么这个key就会永久的存储在Redis中了,其它客户端也永远获取不到这把锁。
解决办法:基于setnx key value的基础上,同时给key设置一个过期时间。
Redis命令为:set key value ex seconds nx
引入问题:存在误删锁的情况,也就是把别人加的锁释放了。比如client1在执行时间较长,锁过期被client2获取,过了一会儿client1的业务处理结束了,然后向Redis发送删除key的命令来释放锁,Redis接收到命令后,就直接将key删除掉了,但此时这个key是属于client2的。
再次升级
value使用唯一值,删除锁时判断value是否是当前线程的。
set key value ex seconds nx时,把value设置成一个唯一值,每一个线程的value都不一样,在删除key之前,先通过get key命令得到value,然后判断value是否时自己线程生成的,如果是,则删除掉key释放锁,如果不是,则不删除key。
存在问题:判断锁是否属于当前线程和释放锁两个步骤不是原子操作。
终极版本
Lua脚本。
Redis提供了Lua脚本的支持。Lua脚本是一种轻量小巧的脚本语言,支持原子性操作,Redis会将整个Lua脚本作为一个整体执行,中间不会被其它请求插入,因此Redis执行Lua脚本是一个原子操作。
在上面流程中,把get key value,判断value是否属于当前线程、删除锁这三步写到Lua脚本中,使它们变成一个整体交给Redis执行。
分布式的Redis
我觉得除了Redis本身的性能,Redis对于分布式友好也是一个很重要的方面。
Redis 本身就是为分布式环境设计的。它可以作为集群运行,支持分布式部署,并提供了一些分布式系统中常用的功能,如发布订阅、持久化等。