diff --git a/_posts/2016-01-12-play-with124.md b/_posts/2016-01-12-play-with124.md index 8c67df9..f9703ea 100644 --- a/_posts/2016-01-12-play-with124.md +++ b/_posts/2016-01-12-play-with124.md @@ -7,7 +7,7 @@ lastModifiedDate: 2021-11-28 --- -## 背景 + 举个 linux 上的例子: 设置文件权限时, 可以用7代表可读可写可执行, 6代表可读可写... diff --git a/_posts/2021-12-07-hdd.md b/_posts/2021-12-07-hdd.md index 48d4d32..c258537 100644 --- a/_posts/2021-12-07-hdd.md +++ b/_posts/2021-12-07-hdd.md @@ -6,7 +6,6 @@ tags: [os, middleware, hdd] --- -## 引言 固态硬盘已经出现n年了, 大概还是价格的原因, 工程师们依旧与机械硬盘奋战着. 举几个例子, 感受下 if else 之外的代码乐趣. diff --git a/_posts/2021-12-09-exquisite-redis.md b/_posts/2022-12-09-exquisite-redis.md similarity index 70% rename from _posts/2021-12-09-exquisite-redis.md rename to _posts/2022-12-09-exquisite-redis.md index ba47f3e..f587880 100644 --- a/_posts/2021-12-09-exquisite-redis.md +++ b/_posts/2022-12-09-exquisite-redis.md @@ -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} \ No newline at end of file diff --git a/_posts/2021-12-11-select-epoll.md b/_posts/2022-12-11-select-epoll.md similarity index 100% rename from _posts/2021-12-11-select-epoll.md rename to _posts/2022-12-11-select-epoll.md diff --git a/images/redis-xuebeng-solution.jpg b/images/redis-xuebeng-solution.jpg new file mode 100644 index 0000000..456d21e Binary files /dev/null and b/images/redis-xuebeng-solution.jpg differ