一、場景
之前做的電商平臺(tái),用戶在收到貨之后,大部分都不會(huì)主動(dòng)的點(diǎn)擊確認(rèn)收貨,導(dǎo)致給商家結(jié)款的時(shí)候,商家各種投訴,于是就根據(jù)需求,要做一個(gè)訂單在發(fā)貨之后的x天自動(dòng)確認(rèn)收貨。所謂的訂單自動(dòng)確認(rèn)收貨,就是在在特定的時(shí)間,執(zhí)行一條update語句,改變訂單的狀態(tài)。
二、思路
最笨重的做法,通過linux后臺(tái)定時(shí)任務(wù),查詢符合條件的訂單,然后update。最理想情況下,如果每分鐘都有需要update的訂單,這種方式也還行。奈何平臺(tái)太小,以及賣家發(fā)貨時(shí)間大部分也是密集的,不會(huì)分散在24小時(shí)的每分鐘。那么,定時(shí)任務(wù)的話,查詢過多,不適合。這里可以先把將要自動(dòng)確認(rèn)收貨的訂單信息存儲(chǔ)到其他介質(zhì)上,比如redis,memcache,rabbitmq,然后執(zhí)行的腳本從前面的介質(zhì)獲取到訂單信息來判斷,這里可以大大的減少數(shù)據(jù)庫的查詢壓力。
redis隊(duì)列的生產(chǎn)者
對(duì)此,我們選擇每天在凌晨兩點(diǎn)的時(shí)候,通過linux的定時(shí)任務(wù)把即將要確認(rèn)收貨的訂單信息查詢出來,然后存儲(chǔ)在redis上,redis上我們選擇的隊(duì)列,隊(duì)列處理的特點(diǎn)就是先進(jìn)先出,前面的數(shù)據(jù)在查詢訂單時(shí),通過發(fā)貨時(shí)間排序,所以最先出隊(duì)列的肯定是距離規(guī)定的自動(dòng)收貨時(shí)間最近的訂單。代碼如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$successCount =0; $failCount =0; $screen_time = 3600*24*9; //設(shè)置篩選天數(shù) $data = array (); $now_time = time(); //查詢符合要求的數(shù)據(jù) $sql ="select id,send_time as deliver_time from `order` where is_send=1 and is_del=0 and is_cancel=0 and is_token=0 and send_time>0 and send_time + { $screen_time } < $now_time order by send_time asc"; $res = $con ->query( $sql ); //當(dāng)隊(duì)列還有數(shù)據(jù)時(shí)將數(shù)據(jù)記錄并清除 while ( $redis ->LLEN( 'auto_recevice_order' )){ $txt = '執(zhí)行時(shí)間:' . date ( 'Y-m-d H:i:s' ). ',信息:' . $redis ->RPOP( 'auto_recevice_order' ); $failCount ++; } //重新填充數(shù)據(jù)進(jìn)隊(duì)列 while ( $row = $res ->fetch_assoc()) { $successCount ++; $redis ->LPUSH( 'auto_recevice_order' ,json_encode( $row1 )); } $con ->close(); $success = date ( 'Y-m-d H:i:s' ). ':[推送成功]:本次成功推送數(shù)據(jù):' . $successCount . '條;記錄上次處理失敗數(shù)據(jù):' . $failCount . "條\r\n" ; file_put_contents ( './success_log.txt' , $success . "\r\n" .PHP_EOL,FILE_APPEND); |
redis隊(duì)列的消費(fèi)者
隊(duì)列的消費(fèi)者沒有通過linux的定時(shí)任務(wù)去做,用linux的screen+php cli模式執(zhí)行php腳本,消費(fèi)者只需要不斷的從隊(duì)列中讀取訂單信息,然后判斷訂單信息中的發(fā)貨時(shí)間,如果達(dá)到自動(dòng)收貨的要求,就執(zhí)行update語句。同時(shí)如果沒有達(dá)到收貨的時(shí)間,而且與收貨時(shí)間間距比較大的時(shí)候,可以讓php腳本休眠sleep一定的時(shí)間數(shù),這個(gè)時(shí)間數(shù)自己調(diào)節(jié)設(shè)計(jì),獲取出來的未達(dá)到時(shí)間要求的訂單,需要重新推送到redis隊(duì)列中去,而且還是隊(duì)列的頂端。以便下次獲取。代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
$set_time = 3600*24*10; //設(shè)置幾天后自動(dòng)收貨 while (true){ if ( $i %30==0){ usleep(10); //防止while 循環(huán)使CPU使用率過高 } if ( $redis ->LLEN( 'auto_recevice_order' )){ $data = json_decode( $redis ->RPOP( 'auto_recevice_order' )); $id = (int) $data ->id; //將數(shù)據(jù)轉(zhuǎn)化為整形 $deliver_time = (int) $data ->deliver_time; //將數(shù)據(jù)轉(zhuǎn)化為整形 $res1 = $res2 =false; $now_time = time(); if (( $deliver_time + $set_time )< $now_time ){ $sql1 = "update `order` set `is_token`='1',`token_time` = $now_time where id=$id and is_send=1 and is_del=0 and is_cancel=0 and is_token=0 and send_time + {$set_time} < $now_time" ; $res1 = $con ->query( $sql1 ); //更新數(shù)據(jù) $rows = mysqli_affected_rows( $con ); if ( $rows ){ $ip = $this ->getIp(); $sql2 = "insert into `order_log`(`order_id`,`log_msg`,`log_ip`,`log_role`,`log_user`,`log_order_state`,`log_time`) VALUES($id,'系統(tǒng)自動(dòng)收貨','$ip','系統(tǒng)','服務(wù)器','收貨',$now_time)" ; //寫入訂單日志 $res2 = $con ->query( $sql2 ); //添加日志數(shù)據(jù) } } if ( $res1 ==false){ //將沒達(dá)到條件的數(shù)據(jù)重新插入隊(duì)列中 $redis ->RPUSH( 'auto_recevice_order' ,json_encode( array ( 'id' => $id , 'deliver_time' => $deliver_time ))); } } $i ++; } |
這里執(zhí)行php腳本,需要用到linux的screen或者supervisor、nohup守護(hù)進(jìn)程。具體用法可自行百度.同樣腳本里面最好有必須的日志記錄。
三、思考
隨著業(yè)務(wù)的增長,在隊(duì)列中同一秒內(nèi),存在的多個(gè)需要處理的訂單,而一次只能從隊(duì)列中取出一個(gè)相關(guān)訂單信息的時(shí)候,可以采用一個(gè)生產(chǎn)者多個(gè)消費(fèi)者的模式,這種情況下,可以用到鎖機(jī)制,保證一條消息只能到達(dá)一個(gè)消費(fèi)者。當(dāng)redis數(shù)據(jù)達(dá)到一定的量之后,也可以適當(dāng)?shù)恼{(diào)整生產(chǎn)者的執(zhí)行頻率和對(duì)應(yīng)的條件。
以上這篇PHP實(shí)現(xiàn)電商訂單自動(dòng)確認(rèn)收貨redis隊(duì)列就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持服務(wù)器之家。