This commit is contained in:
parent
cbfb9c0a50
commit
994fa7d270
|
@ -7,7 +7,7 @@ lastModifiedDate: 2021-11-28
|
|||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
举个 linux 上的例子:
|
||||
设置文件权限时, 可以用7代表可读可写可执行, 6代表可读可写...
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ tags: [os, middleware, hdd]
|
|||
---
|
||||
|
||||
|
||||
## 引言
|
||||
|
||||
固态硬盘已经出现n年了, 大概还是价格的原因, 工程师们依旧与机械硬盘奋战着. 举几个例子, 感受下 if else 之外的代码乐趣.
|
||||
|
||||
|
|
|
@ -23,6 +23,38 @@ tags: [middleware, redis]
|
|||
像这种地方,改成 setnx() 也就够了。但自己心里要清楚,只从技术角度看,分布式锁的实现还可以再深入很多步,做得更精致。如果有条件,应该把这类代码放入公共包里给更高级的开发人员维护。
|
||||
|
||||
|
||||
### 击穿
|
||||
|
||||
击穿的意思是在高并发的前提下,某个缓存因为过期或淘汰策略等原因,暂时在缓存里取不到,大量的的请求几乎同时打到数据库。
|
||||
|
||||
如何避免大量的请求同时打到数据库,是不是跟前面 消息折叠 中的毛刺问题很像。但是侧重点不同,消息折叠只需要 setnx() 就够了, 也只偏向 tryLock() 的语义。讨论击穿的问题,因为没有具体的业务和功能场景,作为一个通用的解决方案,更强调纯粹的锁的技术。在 setnx() 的基础上,再考虑下面几个问题,把锁的实现再完善一点。
|
||||
|
||||
1. 需要把 setnx() 完善成 lock(), tryLock(), unLock() 三个最基本的操作。(没有工程思想的算法工程师不是一个好程序员)
|
||||
2. 如何避免 程序未执行完,锁就过期自动失效了?
|
||||
3. 如何避免 自己加的锁,被别人解锁了?或者自己去释放别人的锁?
|
||||
|
||||
问题1中,unLock() 对应删除缓存。 tryLock() 的行为与 setnx() 一致,把是否设置成功的 true/false 返回给业务代码就行。 lock() 则需要在内部处理 setnx() 的返回值,设置失败的时候需要阻塞,最简单的实现可以是随机等待几十毫秒重试。等待的实现,可以看看 LockSupport 这个类,不能只会 sleep()。
|
||||
|
||||
|
||||
对于问题2, 可以增加一个守护线程,在工作线程执行期间,专门负责检查锁的自动过期时间,延长过期时间。注意别跟主动解锁的操作打架了,别等工作线程主动解锁了,你这还打算去延长过期时间。
|
||||
|
||||
对于问题3,可以从 redis 的 value 值下手,加锁时把 value 设置成自己的唯一值。解锁前检查一下。
|
||||
|
||||
进一步深入的问题还有很多,但是简单处理下上面的问题,就可以说这是一把可用的分布式不可重入自旋锁了。
|
||||
|
||||
|
||||
是否一定要用分布式锁呢?JVM 提供的锁同样可以把大量的并发限制到服务实例的个数,这就可以用了,没必要精确限制到1。@Cacheable 注解的 sync 属性就提供这个功能。
|
||||
|
||||
|
||||
### 雪崩
|
||||
|
||||
雪崩与击穿的区别,是它失效的 key 不止单个, 而是一大批 key 同时失效。
|
||||
|
||||
想办法把时间打散,实在没招就套用击穿的方案:
|
||||
|
||||
![](/images/redis-xuebeng-solution.jpg)
|
||||
|
||||
|
||||
### redission
|
||||
|
||||
我一直是反感 redission 的(同样反感 spring-data),恰恰是因为它的 开源、活力、长期的积累。导致它的模型复杂而庞大。它实现了完整的 jdk 定义的锁的接口。
|
||||
|
@ -37,7 +69,7 @@ unlock()
|
|||
|
||||
何必引入完整的jdk接口实现呢?团队里的人都那么牛逼,hold得住嘛?
|
||||
|
||||
这天,时间大概在一年一度的组织架构调整期间,收到一个原本是其他小组维护的服务,组织架构调整调过来的,代码来了但人没来。里面有段bug是这样的。
|
||||
这天,时间大概在一年一度的组织架构调整期间,收到一个原本是其他小组维护的服务,组织架构调整调过来的,代码来了但人没来。里面有段不停打异常日志的代码是这样的。
|
||||
|
||||
```java
|
||||
RLock xLock = null, xxLock = null;
|
||||
|
@ -70,21 +102,6 @@ try {
|
|||
|
||||
|
||||
|
||||
## 击穿
|
||||
|
||||
|
||||
击穿的意思, 是
|
||||
@cacheable
|
||||
|
||||
|
||||
|
||||
|
||||
## 穿透
|
||||
|
||||
|
||||
|
||||
|
||||
## 雪崩
|
||||
|
||||
|
||||
## keys *
|
||||
|
@ -101,7 +118,7 @@ try {
|
|||
|
||||
把内存dump下来,看到占内存最多的,除了日志打印,就都是同一个sql语句的缓存。特点是 where 条件里包含 `in (?, ? ...)` , 这里面的 `?` 可能有上万个, 而且不是固定数量。
|
||||
|
||||
ORM 框架会把sql语句缓存下来,但是因为 `?` 的个数有几万种可能,就缓存了几万遍。
|
||||
ORM 框架会把 sql 缓存下来,但是因为 `?` 的个数有几万种可能,就缓存了几万遍。
|
||||
|
||||
ORM 框架是动不了了,在用的低版本的没法处理这个问题, 又不敢升级版本。保底咱还能把 `?` 分批,多执行几次sql,至少让内存不爆是吧。但我又挣扎了一下,想看看完整的业务逻辑,感觉不会有什么功能是必须要用这么奇葩的sql实现的。结果就发现,相关的数据库操作明显有优化过一波的痕迹,mysql前面加过一层redis,确认这个sql查询是没删干净的,直接删了就完事。
|
||||
|
||||
|
@ -141,5 +158,16 @@ ORM 框架是动不了了,在用的低版本的没法处理这个问题, 又
|
|||
|
||||
|
||||
|
||||
|
||||
## 穿透
|
||||
|
||||
接口查询的是 DB 里都不存在的数据。
|
||||
|
||||
1. 把 null 值也缓存下来 (原本把 null 特殊对待,不让它进缓存,就是一件很奇怪的事情)
|
||||
2. 布隆过滤器之类的东西,提前判断出 DB 里没数据。
|
||||
|
||||
|
||||
|
||||
|
||||
* 文章目录
|
||||
{:toc}
|
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
Loading…
Reference in New Issue