分布式锁实战

背景

柯苏远写于2024年4月7日 20点10分

公司的主要业务线是国外电商,之前是在客户下订单,以及厂商对客户订单进行打包发货等关键节点都会给客户发送对应邮件。

可是由于邮件没有短信那么及时,方便,为了提高客户的体验和下单率准备在整个系统接入短信。

所以这里就涉及到要和国外通讯商对接短信发送的接口。

这里主要是两个接口:一个是获取token接口,一个是发送短信接口。获取token的接口一个小时之内只能获取5次,一次获取到的token有效期是一个小时。

简单 token 缓存设计

根据背景描述,显而易见的是,token必须要缓存起来,因为一个小时就只能获取5次,所以必须尽量重复使用,以下是我的设想的接口调用情况:

从直观感受上来讲,确实是可以了,我们通常的设计一般情况下都不会考虑多线程并发的情况。

但是!!! 我们的业务项目是电商欸,肯定会存在并发的可能。

所以我将方案给我老大看了之后,我老大一眼就看出我没有考虑并发问题,要我重新去设计。

并发 token 缓存设计

由于上一版的核心就是没考虑并发,如果此时100个线程同时去缓存获取并且同时没获取到,再然后同时去调用了短信的api,那么这个api一分钟5次的限制一下子就到了峰值。

考虑到并发点,我引入了分布式锁,对之前方案做了更新,如下图所示:

整个流程图看起来复杂了很多,其实和上面方案比较起来主要就两点:

  1. 在调用api之前加了分布式锁,在取得token之后,释放分布式锁。
  2. 如果在一个线程持有分布式锁的情况,其它线程进来没获取锁,就让它们冷处理,类似一个while循环操作,然后睡眠200毫秒,循环15次去缓存里取。如果15次都没有从缓存里取到token对象,那么就要进行去抢夺分布式锁,如果抢夺成功那么执行调用api流程,如果失败,那么再去缓存看看,有的话直接返回token对象,没有的话再去抢夺分布式锁。

具体实现 & 当前问题

具体实现分布式锁:

  • 在项目里用的是redisTemplate,有一个setIfAbsent,然后再结合expire(项目里是10s),用这两个命令来获取分布式锁。
  • 释放的时候直接就是delete。

其实这种实现方式是不严谨的,有如下缺点:

  • setIfAbsent和expire:这两个不是原子命令,如果setIfAbsent成功,由于某种原因expire没有成功,那么就会造成死锁,这个分布式锁会得不到释放。
  • expire 这里设置的过期时间是10s,但是如果我业务执行时间大于10s呢?分布式锁就失效了,那还搞个毛线?其它线程也进我的临界区了,线程不安全了。
  • delete是直接删除的,没有考虑某种未知错误造成删除失败的原因。这个其实是小问题,即使删除失败也有超时的保证。

这三个目前存在的问题和老大聊了,老大说确实是有这问题,然后后面研究一下redisson,然后完善一下这个分布式锁,后面等手头活少点去研究一下。

总结

  1. api token 缓存设计

  2. 多线程下 api token 缓存设计

  3. 分布式锁的实现 & 目前存在的问题

  4. 后续目标:研究redisson实现分布式锁。

相关推荐
间彧1 分钟前
在微服务架构下,如何结合Spring Cloud实现动态数据源的路由管理?
后端
间彧2 分钟前
动态数据源切换与Seata分布式事务如何协同工作?
后端
该用户已不存在5 分钟前
Gemini CLI 核心命令指南,让工作从从容容游刃有余
人工智能·程序员·aigc
间彧10 分钟前
除了AOP切面,还有哪些更灵活的数据源切换策略?比如基于注解或自定义路由规则
数据库·后端
已黑化的小白1 小时前
Rust 的所有权系统,是一场对“共享即混乱”的编程革命
开发语言·后端·rust
程序定小飞3 小时前
基于springboot的健身房管理系统开发与设计
java·spring boot·后端
Moonbit3 小时前
你行你上!MoonBit LOGO 重构有奖征集令
前端·后端·设计
华仔啊4 小时前
开源一款 SpringBoot3 + Vue3 数据库文档工具,自动生成 Markdown/HTML
vue.js·spring boot·后端
Lethehong4 小时前
百万迁移费成历史?金仓数据库“零代码”替换Oracle,我们扒了扒它的技术底牌
后端·mysql·架构
吴祖贤4 小时前
5.1Spring AI Ollama 嵌入模型
后端