Redian新闻
>
并发场景下如何保证数据操作的准确性?

并发场景下如何保证数据操作的准确性?

公众号新闻

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 
来源:juejin.cn/post/
7179791593287876664


🔖Part One

我们假想一个场景,在并发场景下,假如要对账户余额(tb_balance.balance)进行增加和减少余额,要怎么设计才能保证数据不出错呢?

🔅下面是我的一些设想(假设余额更新20或减少20):

🔢直接sql更新

直接balance自增20,怎么并发都余额都不会错了是不是~,但这里会有问题,1.假如是减余额的话,要注意余额不能小于0 2.假如后续需要用这个余额结果再进行一些业务操作的话,是取不到这个余额的sql

update tb_balance set balance=balance+20 where balance=#{old_bal} and user_id=#{user_id}

🔢CAS

经典的compare and swap,就是入库时去对比旧值是否与现数据库中的值相等,若相等则更新否则不更新,但是会有风险,就是经典的aba问题。假如更新返回的影响数为0的话,说明余额已经发生了变化,所以需要抛出异常并进行重试。(这里需要写一些补偿的业务逻辑去处理余额更新失败的问题)

/**
* 用户余额增加20
* newBal 为 新值
* oldBal 为 库中查出的值
**/

oldBal = balService.getBal(userId);
newBal = oldBal + 20;

int count = balService.updateBal(newBal,oldBal,userId);
Assert.businessInvalid(count==0,"update error");

// todo some buisness
update tb_balance set balance=#{new_bal} where balance=#{old_bal} and user_id=#{user_id}

🔢乐观锁

乐观锁其实就是使用version去进行版本控制,在更新时判断是否更新数据的版本与现库内版本是一致的,若一致则更改并上升版本号,否则认为在此期间有其它线程进行数据更新。假如更新失败的话,同样进行重试处理。这样的好处就是可以避免aba问题,同时也可以使用增加后的余额进行后续的操作。目前已知有些公司就是这么操作的

try{
  //dosome buisness
  int count = balService.updateBal(newBal,old_version,new_version,userId);
  Assert.businessInvalid(count==0,"update error");
  //dosome buisness
catch(){
  //if fail todo something try again
}
update tb_balance set balance = #{balance} and balance = #{new_version} where version = #{old_version} and user_id=#{user_id}

-- if affect count > 1 success
-- if affect count = 0 retry

🔢redission分布式锁

像集群的项目,我们经常会使用分布式锁去保证幂等性,如果只有单一接口会去操作账户余额那使用分布式锁没有问题,只要在该接口加上锁,保证同一时间只有单一线程进行该业务操作即可;但往往实际业务场景并不会那么简单,比如一个商城,可能会有几十上百个入口可以对余额进行变更,比如定时扣费、订单支付、替他人代付等等;那其实可以通过面向对象的思想,将余额的操作进行抽象,抽象出一个余额类,将该类的方法加上锁,然后所有的业务都强制通过该类进行余额变更的操作,即可保证操作的可靠性。

🤔所以也就是说,订单系统、服务管理中心等等服务都不应该能直接操作到余额数据,还是得抽出一个类型财务中台的东东去统一处理余额,然后财务中台中又维护这么一个余额类去保证更新的可靠性? 这其实也就是软件工程中的单一入口 思想吧

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

🔖Part Two

业务背景:现在实际开发过程中会有这样一种场景,订单表a作为订单业务核心表,而会有a1,a2,a3...an个业务会去更新订单表a的状态,而同时a.status(订单状态)也是作为b,c,d...业务操作的核心判断依据(也就是在不同的订单状态下可能做的操作是截然相反的)

在代码层面上,我们一般的写法是直接用注解开个事务,以保证操作的原子性。这种写法目前让我们的业务还是平稳运行的~

// @apiLock是redis锁
@ApiLock
@Transactional
public void dosomething(){

}

那么假设用户量激增,并发量暴涨,那么会出现什么情况呢?

🤔搞事情的时间来了,事务只能保证单个操作的事务性,假如并发量高的时候,可能就会出现(假设b业务的前提是a订单再待接单[wait_acceipt]的状态):

b业务查询时,a.status=wait_acceipt,然后b业务开始处理;而此时,a1业务对a订单进行操作,并且先于b业务处理完,这时a订单走到了待服务(wait_service),而b还在进行业务处理;然后b业务处理完成,并进行提交。

☢️那么就会产生很多异常的数据了,而且影响范围会很大

解决方案&一些思考

🤔我们可以关注到,这个场景的问题点其实并不在数据的插入与更新,而是在读取,在业务处理过程怎么在确定订单状态没有发生改变

🔢使用redis锁,在读取订单时候对订单进行上锁,业务结束之后再释放

单一入口 :将订单类的操作抽象成一个order类,然后在这个order类中去进行查询 操作,或者在不同服务中用一个redis-key也是OK的

public class OrderService{
  
  lock()
  
  select()
  
  unlock()

}
public void dosomeB(){
  try{
    orderService.lock(serviceOrderId);
    // todo
  }finally(){
    orderService.unlock(serviceOrderId);
  }
}

🔢使用mysql共享锁(读锁),在数据库层面进行阻塞,更加精准,并且不使用单一入口也是可以的,但是可能存在索引失效锁表的风险(核心表被锁就炸了)

public class OrderService{
  /**
  * 读取并且上锁
  */

  selectAndLock()

  /**
  * 读取
  */

  select()
}
-- 读锁会阻塞写(X),但是不会堵塞读(S),在事务提交后,读锁会自动释放
select xxx from order where service_order_id = xxx lock in share mode;

总体来说 ,思想就是对正在操作的数据进行加读锁,阻塞其它的线程。当然这种方案也是双刃剑,毕竟会减少吞吐量,还是应该进行业务梳理,确定加锁的必要性,避免过分设计~像现在的系统,我就没去动它,hh~



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
财相支持继续加息. 银行提高按揭利率. 多图解读最新签证数据. 行业机构呼吁恢复房东减税政策《月光恋》&《情人锁》今天下午2点30🤩云技术社区创始人·肖力坐镇主讲「办公场景下的企业安全建设指南」《挖呀挖》,小黄老师为什么会红?准确性极低!OpenAI下架AI检测器,ICML杰出论文被打脸在线教育场景下客户端实践与优化——RTC服务在线教育纽约地区EB5专场线下讲座——新政下如何在美快速取得身份本地生活场景下的探索之如何写好Prompt?提高识别肺动脉高压准确性,这2种方式不可不知!保证录取!UC首次为社区大学转学生提供UC系统录取保证!高考最后几天 如何保证作息时间合理有效?RDMA在典型场景下的技术应用分析与探索高温预警!大西雅图即将迎来滚烫的母亲节周末,如何保证炎热天气下的安全寂寞梧桐深院化学语言模型polyBERT,以前所未有的速度和准确性在聚合物「宇宙」中搜索所需聚合物羊肉中检出猪肉,张亮麻辣烫查清了:加盟商的错,保证金被没收!网友:所以总部赚了保证金?波士顿地区EB5专场线下讲座——新政下如何在美快速取得身份同事一分钟替换的LUMION模板,效果甩我十条街!(附lumion场景下载)英国2023第二季度签证数据公布!创新签证9月1日实施新政云原生背景下如何配置JVM内存4月签证数据丨EB5投资移民持续走高签发699张,留学签迎来小高峰签发3549张机票:我如何保证边上的座位是空的(免费及收费选项)Redis和MySQL双写一致性如何保证?这个方案够优雅!Kubernetes 如何保障容器可用性?一文介绍探针的使用什么情况? 实控人无法保证半年报真实、准确、完整腾讯云V265/TXAV1直播场景下的编码优化和应用心像美国顶尖大学如何保证本科教育质量?新一代直播场景下,视频加速卡在狂奔简历骗局!如何保证自己求职不受骗,这是你需要知道的……5个高并发场景优化的衡量指标定义Serverless场景下BaaS访问的统一范式林昭的话比张志新的好听;木心的话比陈丹青的好听;毛主席的话比习席的好听;马英九来了,大陆炸了:瞧人家的样子,说话,写字!直播丨美国签证数据月月谈JDK 21中的结构化并发:并发编程的一次飞跃
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。