Laravel解决高并发问题的思路

20 篇文章 0 订阅
7 篇文章 0 订阅

问题复现

客户端并发请求扣费接口,导致重复扣费;
服务端的加锁逻辑不正确,锁加到了非核心逻辑上,即加到了消费记录表,而不是加到了核心业务表。

有问题的代码

  1. 首次进入聊天房则扣费,再次进入不重复扣费
  2. 这个思路无法规避并发问题
    public function agoraToken(Request $request)
    {
        .
        .
        .
            try {
                DB::connection('footprint')->beginTransaction();
                if ($this->_userid == $appointmentInfo->userid) {
                    //如果已经存在则不重复扣费 但是并发问题会导致重复扣费
                    if (!AppointmentAction::existAction($this->_userid, $request->appointmentId, AppointmentAction::TYPE_ACTION_ENTER)) {
                        $propCount = UserConsume::getAppointmentSendConsumeCouponCount($this->_userid, $request->otherUserid);
                        $userConsume = new UserConsume($this->_userid);
                        Log::error("营业中约会 \n消费啤酒个数:" . $propCount . " \n用户id:" . $this->_userid . " \n对方id:" . $request->otherUserid . " \n时间戳:" . time());
                        $userConsume->consumeCommon(CouponInfo::PROP_COUPON_CHAMPAGNE_ID, PropInfo::PROP_CHAMPAGNE_ID, $propCount);
                        DB::connection('footprint')->commit();
                    }
                }
                
                //记录进入房间
                AppointmentAction::record($this->_userid, $request->appointmentId, AppointmentAction::TYPE_ACTION_ENTER);
            } catch (\Exception $exception) {
                Log::error("扣费失败:" . $exception);
                DB::connection('footprint')->rollBack();
            }
        .
        .
        .
    }

优化后的代码

  1. 引入事务,引入try catch
  2. 查询约会数据加锁,当未扣费时扣费,已扣费则修改扣费状态
  3. 修改扣费状态成功提交事务,释放锁
  4. 扣费失败回滚事务
    public function agoraToken(Request $request)
    {
        .
        .
        .
        //try catch 捕获异常,避免崩溃
            try {
                //开启事务 因为只是事务中的锁才生效 锁包括lockForUpdate()和sharedLock()
                DB::connection('footprint')->beginTransaction();
                //校验获取token的合法性
                $appointmentInfo = AppointmentInfo::query()->selectRaw('id,userid,"inviteeUserid","prepareId",status,"isConsume"')
                    ->where('id', $request->appointmentId)->lockForUpdate()->first();
                if (empty($appointmentInfo) || !in_array($this->_userid, [$appointmentInfo->inviteeUserid, $appointmentInfo->userid])) {
                    return ErrorCode::TYPE_ILLEGAL_REQUEST;
                }
   
                //这里是关键 根据isConsume标记是否扣费了
                if ($appointmentInfo->isConsume == AppointmentInfo::TYPE_IS_NOT_CONSUME) {
                    $propCount = UserConsume::getAppointmentSendConsumeCouponCount($this->_userid, $request->otherUserid);
                    $userConsume = new UserConsume($this->_userid);
                    Log::error("营业中约会,个数:" . $propCount . " 用户id:" . $this->_userid . ' 对方id:' . $request->otherUserid);
                    $userConsume->consumeCommon(CouponInfo::PROP_COUPON_CHAMPAGNE_ID, PropInfo::PROP_CHAMPAGNE_ID, $propCount);
                    //消费成功后修改isConsume的值,提交事务
                    $appointmentInfo->isConsume = AppointmentInfo::TYPE_IS_CONSUME;
                    $appointmentInfo->save();
                    DB::connection('footprint')->commit();
                }
                .
                .
                .
            } catch (\Exception $exception) {
                //异常情况打印log 回滚事务
                Log::error("约会扣费异常:" . \GuzzleHttp\json_encode($exception));
                DB::connection('footprint')->rollBack();
            }
        .
        .
        .
    }

期间的尝试和思考

  1. 要给核心逻辑加锁,进入聊天房只是一种记录,而不是核心逻辑,不能作为锁定条件
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王中阳Go

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值