網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
問題復(fù)現(xiàn)
客戶端并發(fā)請(qǐng)求扣費(fèi)接口,導(dǎo)致重復(fù)扣費(fèi);
服務(wù)端的加鎖邏輯不正確,鎖加到了非核心邏輯上,即加到了消費(fèi)記錄表,而不是加到了核心業(yè)務(wù)表。
有問題的代碼
- 首次進(jìn)入聊天房則扣費(fèi),再次進(jìn)入不重復(fù)扣費(fèi)
- 這個(gè)思路無法規(guī)避并發(fā)問題
public function agoraToken(Request $request)
{
.
.
.
try {
DB::connection('footprint')->beginTransaction();
if ($this->_userid == $appointmentInfo->userid) {
//如果已經(jīng)存在則不重復(fù)扣費(fèi) 但是并發(fā)問題會(huì)導(dǎo)致重復(fù)扣費(fèi)
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("營(yíng)業(yè)中約會(huì) \n消費(fèi)啤酒個(gè)數(shù):" . $propCount . " \n用戶id:" . $this->_userid . " \n對(duì)方id:" . $request->otherUserid . " \n時(shí)間戳:" . time());
$userConsume->consumeCommon(CouponInfo::PROP_COUPON_CHAMPAGNE_ID, PropInfo::PROP_CHAMPAGNE_ID, $propCount);
DB::connection('footprint')->commit();
}
}
//記錄進(jìn)入房間
AppointmentAction::record($this->_userid, $request->appointmentId, AppointmentAction::TYPE_ACTION_ENTER);
} catch (\Exception $exception) {
Log::error("扣費(fèi)失敗:" . $exception);
DB::connection('footprint')->rollBack();
}
.
.
.
}
優(yōu)化后的代碼
- 引入事務(wù),引入try catch
- 查詢約會(huì)數(shù)據(jù)加鎖,當(dāng)未扣費(fèi)時(shí)扣費(fèi),已扣費(fèi)則修改扣費(fèi)狀態(tài)
- 修改扣費(fèi)狀態(tài)成功提交事務(wù),釋放鎖
- 扣費(fèi)失敗回滾事務(wù)
public function agoraToken(Request $request)
{
.
.
.
//try catch 捕獲異常,避免崩潰
try {
//開啟事務(wù) 因?yàn)橹皇鞘聞?wù)中的鎖才生效 鎖包括lockForUpdate()和sharedLock()
DB::connection('footprint')->beginTransaction();
//校驗(yàn)獲取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;
}
//這里是關(guān)鍵 根據(jù)isConsume標(biāo)記是否扣費(fèi)了
if ($appointmentInfo->isConsume == AppointmentInfo::TYPE_IS_NOT_CONSUME) {
$propCount = UserConsume::getAppointmentSendConsumeCouponCount($this->_userid, $request->otherUserid);
$userConsume = new UserConsume($this->_userid);
Log::error("營(yíng)業(yè)中約會(huì),個(gè)數(shù):" . $propCount . " 用戶id:" . $this->_userid . ' 對(duì)方id:' . $request->otherUserid);
$userConsume->consumeCommon(CouponInfo::PROP_COUPON_CHAMPAGNE_ID, PropInfo::PROP_CHAMPAGNE_ID, $propCount);
//消費(fèi)成功后修改isConsume的值,提交事務(wù)
$appointmentInfo->isConsume = AppointmentInfo::TYPE_IS_CONSUME;
$appointmentInfo->save();
DB::connection('footprint')->commit();
}
.
.
.
} catch (\Exception $exception) {
//異常情況打印log 回滾事務(wù)
Log::error("約會(huì)扣費(fèi)異常:" . \GuzzleHttp\json_encode($exception));
DB::connection('footprint')->rollBack();
}
.
.
.
}
期間的嘗試和思考
- 要給核心邏輯加鎖,進(jìn)入聊天房只是一種記錄,而不是核心邏輯,不能作為鎖定條件
原文鏈接:https://blog.csdn.net/w425772719/article/details/120237037
相關(guān)推薦
- 2022-01-28 Hyper集成laravel中使用的blade模板
- 2022-07-11 npm 查看全局安裝和卸載全局安裝
- 2023-05-08 C語(yǔ)言中關(guān)于樹和二叉樹的相關(guān)概念_C 語(yǔ)言
- 2022-07-04 如何用python實(shí)現(xiàn)結(jié)構(gòu)體數(shù)組_python
- 2022-12-02 淺析Tomcat使用線程池配置高并發(fā)連接_Tomcat
- 2022-08-05 利用Python?list列表修改元素_python
- 2023-01-23 React中props使用介紹_React
- 2023-02-09 MongoDB中aggregate()方法實(shí)例詳解_MongoDB
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支