微服务缓存你要这样玩
微服务项目当中,缓存已经是必不可少的一个模块了,都2020年了不会微服务缓存怎么能行。今天我就给大家说说什么是微服务缓存,缓存应该怎么玩。
在开始之前,先简单整合一下redis,如果这块没有问题的大佬可以直接略过这部分内容。
1、springBoot整合redis
springboot已经帮我们把redis抽取成了一个startes ,也就是场景启动器。我们可以看一下官网:
我们需要在我们的pom文件当中引入redis:
之所以没有写版本号,是因为由父项目做了统一版本控制。
我们引入了redis之后就可以看
配置redis
我们需要关注的几个配置项:
- host:redis所在服务器的IP地址
- port:redis的端口号。一般为6379
- password:redis的密码
redis如果没有设置密码,那就不需要写password这个配置项。
使用StringRedisTemplate来操作redis
首先需要通过@Autowired注入StringRedisTemplate
接下来编写测试类
至此,我们已经整合成功redis了
2、分布式系统下使用缓存的问题
-
缓存击穿
缓存击穿是指对于一些设置了过期时间的key,假设这些key会在某个时间点被超高并发的访问。并且这个key在大量请求进来之前刚刚好失效,那么所有的请求就会全部转发到数据库,这种场景我们称之为缓存击穿。
解决方案
可以通过加锁解锁,大量并发先让一个请求去查,其他请求等待。其他人获取到锁,先去查缓存,命中缓存,就不会再去请求数据库了。关于加锁可能引发的一些问题,我们之后讨论。
-
缓存穿透
缓存穿透指查询一个不存在的数据,由于缓存没有命中,将会去数据库查询,但是数据库也没有这条记录,我们也没有将这次查询到的null写入缓存,这将导致这个不存在数据每次都会去数据库查询,缓存就失去了意义。
风险
如果被人恶意利用,会使数据库瞬时压力过大,导致数据库崩溃。
解决方案
将查询到的null值写入缓存,并设置短暂的过期时间。
-
缓存雪崩
缓存雪崩指的是在我们设置缓存key时采用了相同的过期时间,导致缓存在某一个时刻同时失效,所有请求同时转发到数据库,数据库瞬时压力过大,导致数据库崩溃、
解决方案
在原有是失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发缓存集体失效事件。
3、加锁解决缓存击穿
-
使用同步代码块(synchronized)
只要是同一把锁,就可以锁住需要获取这把锁的所有线程。结合springboot中的所有组件都是单例的。我们可以使用
synchronized(this)
假设三个请求同时请求。请求没有命中缓存,A获取到了锁,B和C都处于一个阻塞等待状态,A去查数据库,查到数据后插入缓存中,请求返回,释放锁。B,C再次查询缓存,缓存命中,返回结果。
另外需要注意的是锁的时序问题未命中缓存,查数据库,插入缓存。以上三个操作一定是原子性的。不然就会引发锁时序问题,从而导致没有锁住。
- 分布式环境下如何加锁
本地锁只能锁住当前进程,所以我们需要分布式锁。
4、分布式锁
分布式锁原理
我们可以同时去抢同一把锁,抢到了,就执行相关业务逻辑,否则就必须等待,直到释放锁。
关于这把锁,我们可以使用redis,mysql等,可以去任何大家都能访问到的地方。
等待可以自旋的方式。
分布式锁的实现(基于redis)
先来看官网的命令介绍
- EX seconds:设置键key的过期时间,单位时秒
- PX milliserconds:设置键key的过期时间,单位时毫秒
- NX:只有键key不存在的时候才会设置key的值
- XX:只有键key存在的时候才会设置key的值
我们可以通过SET NX
参数来实现分布式锁。
阶段一
很多时候有一些人会这样设计分布式锁,这样的分布式锁会带来一些问题:
- setnx占好了锁,业务代码异常或者程序意外宕机了。没有执行删除锁的逻辑,造成了死锁
那出现这个问题的时候又该如何解决呢?
- 设置锁的自动过期时间,即使没有删除锁,到期后也会自动删除
阶段二
这样的设计总没问题了吧?
其实不然:
- 如果由于业务执行时间很长,锁过期了,我们直接删除,可能会把别人的锁删了。
解决:占锁的时候,指定为uuid,每个人匹配到自己的锁才删除。
此时我们已经可以保证加锁(占锁+过期时间)和删除锁(判断+删除)是原子操作,但是有一点我们仍然不能忽略,那就是锁的自动续期。
关于锁的自动续期我们可以使用一个线程每隔单位时间就去检查锁,如果还持有锁,那么久延长锁的时间。
当然有一个Redisson框架已经为我们封装好了这所有的操作,下次我们再来聊聊Redisson分布式锁。
感谢你的阅读,希望我的分享可以対你有所帮助。
我是武四三二一。