缓存逻辑
代码实例
springboot2.0以后默认使用lettuce作为操作rendis的客户端,在高并发下回产生“堆外内存溢出”,可以通过切换使用jedis。
上面的基础逻辑回存在一定的问题,为了避免缓存穿透、缓存雪崩、缓存击穿的问题,可以通过下面方法解决
当并发请求时,只让其中一个请求拿到锁以后,去查一次数据库,然后将查询的结果放入缓存中,其他请求就不需要再次查询数据库了,
当一个请求拿到锁以后,需要再次查询缓存,如果没有才继续进行数据库查询。
代码实例如下
在并发情况下,当一个请求拿到锁以后,当执行完“确认缓存” 和 “查数据库” 操作后,就会释放锁,当下一个请求获取锁后继续执行“确认缓存” 和 “查数据库” 操作,但此时第一个请求还没有将数据放入缓存成功,就会导致后面的请求重复查询数据库。
所以就需要将“结果放入缓存” 也加到锁中。
本地锁只能锁住当前进程,在多个服务节点下需要使用分布式锁。
分布式锁的基本原理就是,让过个请求去同一个地方去抢占锁,如果能抢到,则执行业务逻辑,否则就必须等待,直到释放锁,抢锁可以去redis,也可以是数据库,等待可以采用自旋的方式。
一个请求进来获取到锁以后,开始执行业务逻辑,如果在业务代码执行的过程中发生异常,后面的删除锁的逻辑没有执行,就会永远锁在这里,造成死锁。
解决方式就是设置锁的自动过期,这样即使没有删除,过期后也会自动删除
为了避免死锁发生,上面设置的锁的过期时间,但是如果在这是锁的过期时间的过程中有发生了异常、宕机,删除锁的操作依然没有执行,又会出现死锁。
为了避免这种现象,必须将“抢锁”和“设置过期时间” 放到一个事务当中
由于设置了锁的过期时间,在并发请求下,由于业务执行时间比较长,如果直接删除锁,有可能把别人持有的锁给删除了。
解决办法,可以加一个uuid,在执行删除锁的时候判断一下,自己删除的锁是不是自己从redis中获取的那一个,实例代码如下
如果程序执行到 “判断是删除的自己的锁” 此时锁正好过期,此时下一个请求获取了新的锁,后面在删除锁的时候,那么删除的就又是别人的锁了。
解决办法就是将“获取当前锁的值”、“判断删除的是否是自己的锁”和“删除锁” 放到一个事务中去,保证原子性
上面通过redis 实现的分布式锁比较复杂,如果控制不好就会出现“锁不住”或者“死锁”的问题,Redis 提供了一个更强大的分布式锁,Redisson ,Redisson 的宗旨是促进使用者对redis关注分离,从而将更多精力放到业务处理上.
参考官方文档
示例代码如下
通过redisson,不需要在关心是否出现死锁问题,只需要关注业务逻辑即可
redisson提供读写锁,读数据的气候加读锁,写数据的时候加写锁,保证一定能够获取到最新数据,读写锁是同时存在的,例如,在写入数据前加锁,在写入逻辑完成之前,读数据的操作会一直在等待,知道写操作完成后释放锁,才能正常读到最新的数据,示例代码如下
读锁
写锁
总结
通过redisson的闭锁,可以实现当gogogo方法调用5次的时候,lockDoor方法才会执行完成
例如在redis中初始化一个key为park ,value 为3的键值,示例代码中每执行一次go 方法,value值就会减1,当值减到0的时候,park方法返回false ,值大于零的时候park方法返回true ,信号量可以在分布式系统中实现限流的功能,