都是并发惹的祸

都是并发惹的祸

在工作中遇到并发的场景太多太多、今天线上环境又出现同样的bug不得不排查问题并修复。其实也知道自己写的程序并发量上来的时候会有问题只是忙于其他。因为线上没有出现问题(现在系统用户数低)就偷懒没有修复^_^!现在将处理问题的思路记录下来:

  • 1.锁的介绍
  • 2.场景回放
  • 3.处理方式
  • 4.如何避免

1.锁的介绍

锁分为两类分别为:

  • 悲观锁
  • 乐观锁

悲观锁,正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。----百度百科。我们自己的理解:悲观就是指代我们没有所谓的经验和经历、或者说历史上证明你这样做是不正确的、历史只证明了另一面。

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。----百度百科。我们自己的理解:乐观就是指代我们乐观的认为大多数情况下没有问题。

2.场景回放

我们的应用场景是:第三方支付公司在每天的某一时刻发送一批支付订单结算通知信息、我们平台根据第三方支付公司发送的通知信息去做我们的业务操作。比如:更新订单、用户实际可用余额增加、对账等操作。拿用户实际可用余额增加举例:可能用户在之前发生了多笔同样的交易、这样第三方支付公司就有多次通知平台通知信息发送给平台服务器。平台获得到信息后开始同步处理用户账户余额。前提:第三方发送通知是同步并发发送的。平台处理请求理想是一条一条可惜做不到。人家并发发送过来、平台没有做到限制因此就遇到了并发的问题。所以“都是并发惹的祸”!

3.处理方式

处理方式总结下来有如下几种

  • 1.并发就并发我不管(其实应该管的)
  • 2.使用数据库乐观锁(考虑处理失败的情况)
  • 3.使用数据库悲观锁(考虑效率问题)
  • 4.程序代码中加锁(如Java中的 lock等)

程序调用如下sql(伪代码)、事务默认使用Spring提供的。

3.1并发就并发我不管

直接使用常规的用法:

update user_account_amount set balance=balance+x where account_id=xxx

以上用法是最常规的更新操作用法只要涉及到并发资金一定出错

3.2使用数据库乐观锁

数据库表字段添加version字段:

update user_account_amount set balance=balance+x where account_id=xxx and version='做update前获得的version值'

使用此种发送如果同一个用户同时有n次更新请求系统依然会出现异常。因为这条sql在执行 过程中有可能会返回 :0 ,如果没有并发的情况下返回: 1 。所以如果使用这种方式在程序代码中要注意处理 返回值为:0 的情况不能疏忽。 以上用法是可以解决问题的只要注意处理返回值为零的情况就可以。建议采用该方法

3.3.使用数据库悲观锁

使用悲观锁数据库效率是硬伤、参考如下伪代码:

select  * from user_account_amount where   account_id=xxx and version='做update前获得的version值' 
for  update user_account_amount set balance=balance+x 

谨慎使用以上sql、有可能会应用上述sql锁定了某一行数据造成你后边的程序要等待。 因此建议在使用悲观锁时一定要小心谨慎。

3.4程序代码中加锁

使用java同步关键字 synchronized在java方法中或者在方法块中使用、或者使用

Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){
     
}finally{
    lock.unlock();   //释放锁
}

以上伪代码只是在关键处理中使用程序去锁定代码的执行不能从根本上解决场景中描述的问题

4.如何避免

根据不同的应用场景采用不同的方式方法避免程序和数据不一致效果。最终达到的目的就是和人脑的想法一样、该并发的并发处理、该单独处理的就一条条单独处理。当然处理的是否理想是程序员应该考虑的问题和应用具备的智慧。

最后经过同事讨论最终的处理方式为采用 3.2使用数据库乐观锁并做循环处理直到sql返回值为1为最终。这样处理有以下缺点分别为:

  • 代码循环执行等待时间太长影响系统运行效率
  • 如果返回值一直不为1系统一直卡死在那里等死了。

因此后续还要进行改良和处理。初步想到了两种改良的方式:

  • 1.采用队列和线程池。如果系统掉电队列没有持久化、信息丢失也不行
  • 2.采用job执行。本来是简单的事情复杂复杂化了。

如果你有好的想法可以提供呀。

转载于:https://my.oschina.net/zzuqiang/blog/644748

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值