本片文章为 分布式锁 相关的系列文章
本系列文章 将从 缓存的使用 本地锁 ** 一直写到 **分布式锁 以及相关的一些 知识点 问题
废话少说 先上例子
缓存如何使用?
- 请求获取数据 先从缓存中获取
- 如果缓存中没有 则 从 DB中获取 并且将获取到的数据存到 DB中
1 | // 下面的伪代码 将 描述 缓存的使用 |
上面的例子 引出了缓存常见的 三个问题
- 缓存击穿
- 缓存雪崩
- 缓存穿透
缓存击穿
定义:在某一时刻 缓存中的 某一个 key失效 此时 正好出现高并发 数据库产生压力 并且可能导致数据库宕机
上面的伪代码中 如果 第一个 if判断 直接为 false 也就意味着缓存中没有数据 将会直接访问数据库 在没有限制的情况下产生高并发 请求都将直接访问数据库获取数据
解决方案: 加锁 (每次只能一个请求过来 请求数据库后 将获得到的数据 再存入缓存中 其他的请求 自旋)由此引出 分布式锁
缓存雪崩
定义: 在某一时刻 缓存中的 所有 key 同时失效 此时 正好出现高并发
所有的key都失效了 意味着 redis 挂了
解决方案:过期时间不要设置一样 或者 设置过期时间后面 加一个 随机数 (雪崩出现的几率很小)
缓存穿透
定义: 用户查询数据的时候 当前数据在 缓存 中没有 在数据库 中 也没有 则会返回null 没有吧null 放入缓存中 此时 如果出现高并发访问这个数据 将对数据库造成压力 数据库可能宕机
1 | // 缓存 和 数据库 中 都没有数据 if(obj!=null) 也没有 else 操作 |
解决方案:如果数据库中也不存在的数据出现了 将null放入缓存 并且 给缓存一个过期时间(这个过期时间相对而言时间可以短一些)
缓存击穿 雪崩 穿透三者 关系
击穿是一个key 雪崩是全部key 穿透是缓存和数据库同时都没有这个数据 并且也没有处理这个数据
本地锁的局限性
想要实现的问题
- 需要在缓存中 设置一个 初始值 0
- 通过应用程序 查询缓存
- 如果缓存中有数据 则 数据 +1
- 如果缓存中没有数据 则 返回
1 | // controller 层 |
1 | // service 和 serviceImpl |
使用ab压力测试工具
ab -n(一次发送的请求数) -c(请求的并发数) 访问路径
ab -n 5000 -c 100
http://192.168.200.1:8206/admin/product/test/testLock此处没有使用网关 直接访问该项目
使用redis桌面管理工具连接上此redis 最后显示的num数必定远小于 5000
为什么?
出现并发的情况下 有很多线程获取到的是同一个资源
多线程情况下 多线程会抢占共用资源 此时需要加锁
1 | // 添加synchronized同步锁 |
添加了synchronized或者是lock后 再次执行压力测试
此时num数 为 5000
此数据 与 预期一致
那么 问题又来了 如果是集群状态下呢?
- 将测试的微服务 启动 右键 Copy Configuration
- 在Edit Configuration 中 点击 Environment
- 在第四栏的 Environment variables 输入 server.port=未被占用的端口号
使用ab压力测试工具
启动网关微服务 使用网关 启用负载均衡 访问数据
ab -n 5000 -c
100 http://192.168.200.1/admin/product/test/testLock使用redis桌面管理工具连接上此redis 最后显示的num数 预计在2400左右
这也意味着 本地锁 在集群状态下 无效(或者说达不到预期)
结论
本地锁 只能 锁住 在同一 工程内的 资源 在 分布式系统中 存在局限性 此时需要 分布式锁