• 分布式系统敏感操作的并发处理(并发锁)


    在实际工作中经常遇到对账户的操作(账户充值和账户消费),处理的逻辑如下:

    // 1 查询账户当前的金额
    // 2 根据操作,计算操作后的金额
    // 3 更新账户的金额
    

    然而,在实际中经常会有并发操作的问题,下面通过在数据中执行SQL的方式,模拟下不做并发处理的情况:

    数据库是MySQL,隔离级别采用默认的可重复读,表为t_money,只有两列:id、money,只有一条记录id=1, money=1000。分别起两个客户端,模拟并发操作的行为:

    • 事务1,账户消费100元
    • 事务2,账户充值200元
    序号 事务1 事务2
    1 start transaction;
    2 start transaction;
    3 select * from t_money where id=1;
    4 select * from t_money wehre id=1;
    5 update t_money set money=900 where id=1;
    6 update t_money set money=1200 where id=1; (不能执行,被阻塞)
    7 select * from t_money where id=1;
    8 commit; (事务1执行commit后,被阻塞的update执行)
    9 select * from t_money where id=1; select * from t_money where id=1;
    10 commit;
    11 select * from t_money where id=1; select * from t_money where id=1;

    按照上面的步骤执行完成后,11步查出来账户id=1的money=1200。

    按照业务的逻辑,消费和充值后,账户的金额应该为1100,而系统中id=1的账户金额居然为1200,这是绝对不能接受的!

    解决方案

    1. 利用MySQL的当前读

    将更新金额的语句,使用:

    update t_money set money=money-100 where id=1;
    

    update会使用“当前读”,可以读取到其它事物未提交的数据。当前读遇到其它事务的写操作时,会被阻塞,引起当前读的语句:

    select ... for update;
    select ... lock in share mode;
    update
    delete
    insert
    

    2. redis并发锁

    也就是,操作前要获得锁,操作完成释放锁;没有获得锁,不允许进行操作,直接返回并发错误。

    在实际系统中,往往是分布式部署的,那么就需要加分布式锁。最容易想到(本人)的就是使用redis,在redis中使用setnx,伪代码如下:

    if(redis.setnx(id)){
        // 加锁成功
        // 账户操作
    } else {
        // 返回并发错误,由调用者处理后续逻辑(重试等)
    }
    

    2. 优雅的redis并发锁

    在方案1中,在加锁失败后,直接返回并发异常,调用方需要重试。实际上,第一次请求时,虽然不能获得锁,但是可能在1s之后就可以获得锁了,我们何不如稍微等待下再重试呢?

    更加优雅的加锁,伪代码:

    if (redis.setnx(id)) {
        // 加锁成功
        // 账户操作
    } else {
        // 第一次加锁失败
        Thread.sleep(1000); // 等待1s,也可以等待并指定多次重试
        if (redis.setnx(id)) {
            // 账户操作
        } else {
            // 返回并发错误
        }
    }
    

    对于redis实现并发锁,有很多可以研究的细节,比如:setnx成功后,系统挂了,后续加锁就永远不能成功了,该如何处理?更多细节,可以看看他人是如何用redis实现分布式并发锁的。

  • 相关阅读:
    删除指定目录下的文件及子文件
    PHP简单实现“相关文章推荐”功能的方法(此方法不是自创)
    微信开发中自动回复(扫码、关注推事件)
    方式三(API方式)C++手动加载CLR运行托管程序(CLR Hosting)
    分享 N种方法使用C++调用C#.NET库
    redis 持久化之 RDB & AOF
    redis 慢查询、Pipeline
    redis 发布订阅、geo、bitmap、hyperloglog
    redis5.0 数据结构与命令
    Linux 下安装 redis5.0
  • 原文地址:https://www.cnblogs.com/acode/p/8882699.html
Copyright © 2020-2023  润新知