一段簡單的購買程式,看起來沒有任何問題。
剩餘餘額、商品庫存、購買許可權等判斷面面俱到,從頭到腳包裝的嚴嚴實實。
但是為何人一多就頻頻漏點吶?何解?
還是以**購買為例,****是web程式和資料庫兩部分,業務處理流程:
#!shell
使用者金額是否大於商品**—>商品庫存是否充足—>購買操作:生成訂單—>扣除使用者金額—>商品庫存減1
流程的每一部分都是web與資料庫打交道,查詢或者運算元據庫。
#!php
$goods=$db->firstrow("select * from ".tb('goods')." where goods_id=''");
if(empty($goods)) showerror('商品不存在');
/* 金額是否充足 */
if($user->money
/* 商品庫存 */
if($goods['num']==0) showerror('庫存不足');
/* 購買操作 begin */
//生成訂單
createorder($goods,$user,time());
$user->update('money'=>$user->money-$goods['price']); //使用者金額減少
$db->execute("update ".tb('goods')." set num=num-1 where goods_id=''");//商品庫存-1
showsuccess('購買成功');
/* 購買操作 end */
正常來看這個業務處理是沒有問題的,下面想象下多人同時購買(併發請求,如秒殺活動)的情境可能會引發的問題?
如果乙個使用者同時有兩次購買請求,一次購買已進行到新增訂單但未扣除使用者金額,另一次購買在第一步使用者金額判斷便不準確了。
當商品庫存僅為1時,同時有多個請求,而當前沒有乙個請求走到商品庫存減少位置,多次購買都能成功,而**卻無貨可發。
總結來說,當有大量的購買操作同時進行,如果資料庫的處理速度跟不上程式的請求速度,就會出現判斷不準確的問題,造成使用者以單個商品的金額購買多個商品、某些使用者付款了但得不到商品等,算是乙個安全風險。
核心思想:將一次業務處理流程(如購買操作)作為乙個最小操作單元,同一時間只能有乙個操作。
1. 整個操作加記憶體鎖。如在memcache裡,開始購買時設定購買狀態為進行中,購買結束後清除購買狀態,程式開始時即從memcache裡判斷是否有正在進行的購買操作,如有則退出。
2. 限制每個使用者的購買間隔,如10秒內僅允許購買一次,最好也是放在記憶體裡。
3. 當然,優化資料庫及程式以加快處理速度也是有必要的。
解決方案程式示例(php+mysql+memcached)
#!php
/** * 通過memcache解決併發購買問題
*/$goods=$db->firstrow("select * from ".tb('goods')." where goods_id=''");
if(empty($goods)) showerror('商品不存在');
$mmc=memcache_init();
$lastbuytime=$mmc->get('lastbuytime_'.$user->userid);
if($lastbuytime>0 && $lastbuytime>time()-10) showerror('10秒內只能進行一次購買');
$buying=$mmc->get('buying');
if($buying==1) showerror('有正在進行的購買,請稍候');
/* 金額是否充足 */
if($user->money
/* 商品庫存 */
if($goods['num']==0) showerror('庫存不足');
/* 購買操作 begin */
//生成訂單
createorder($goods,$user,time());
$user->update('money'=>$user->money-$goods['price']); //使用者金額減少
$db->execute("update ".tb('goods')." set num=num-1 where goods_id=''");//商品庫存-1
/* 購買操作 end */
$mmc->set('buying',0);
$mmc->set('lastbuytime_'.$user->userid,time());
showsuccess('購買成功');
併發及併發的執行緒安全處理
目錄 執行緒安全性 原子性提供了互斥訪問,同一時刻只有乙個執行緒可以來對它操作 原子包 具有原子性,執行緒安全的,atomicint 原始碼實現unsafe類的getandaddint實現原理 迴圈判斷當前的值和主記憶體值是否一致,相等就加一,用到的算 法cas全稱compareandswapint...
如何處理重複請求 併發請求的
你可能會想到的是,只要請求有唯一的請求編號,那麼就能借用redis做這個去重 只要這個唯一請求編號在redis存在,證明處理過,那麼就認為是重複的 string key req12343456788 請求唯一編號 long expiretime 1000 1000毫秒過期,1000ms內的重複請求會...
Java使用限流處理大量的併發請求
在web應用中,同一時間有大量的客戶端請求同時傳送到伺服器,例如搶購 秒殺等。這個時候如何避免將大量的請求同時傳送到業務系統。第一種方法 在容器中配置最大請求數,如果大於改請求數,則客戶端阻塞。該方法有效的阻止了大量的請求同時訪問業務系統,但對使用者不友好。第二種方法 使用過濾器,保證一定數量的請求...