網站首頁 編程語言 正文
問題復現
客戶端并發請求扣費接口,導致重復扣費;
服務端的加鎖邏輯不正確,鎖加到了非核心邏輯上,即加到了消費記錄表,而不是加到了核心業務表。
有問題的代碼
- 首次進入聊天房則扣費,再次進入不重復扣費
- 這個思路無法規避并發問題
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();
}
.
.
.
}
優化后的代碼
- 引入事務,引入try catch
- 查詢約會數據加鎖,當未扣費時扣費,已扣費則修改扣費狀態
- 修改扣費狀態成功提交事務,釋放鎖
- 扣費失敗回滾事務
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();
}
.
.
.
}
期間的嘗試和思考
- 要給核心邏輯加鎖,進入聊天房只是一種記錄,而不是核心邏輯,不能作為鎖定條件
原文鏈接:https://blog.csdn.net/w425772719/article/details/120237037
- 上一篇:提高接口并發量,防止崩潰
- 下一篇:GoFrame garray使用實踐
相關推薦
- 2022-07-18 spring boot 中解決 Invalid character found in the req
- 2022-03-25 Go語言什么時候該使用指針(go指針的作用)
- 2022-08-18 python數據可視化pygal模擬擲骰子實現示例_python
- 2023-03-04 C++容器適配器的概念與示例_C 語言
- 2022-08-08 python中Pytest常用的插件_python
- 2022-04-26 C++?Primer?Plus詳解_C 語言
- 2022-06-21 Python+Django實現簡單HelloWord網頁的示例代碼_python
- 2022-10-05 Iptables防火墻string模塊擴展匹配規則_安全相關
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支