摘要:記錄下整體的設計思路以及運營過程中的各種問題。如果錢是負數了,還得從已生成的小紅包中抽取回來將紅包放入隊列之中創建紅包失敗,請檢查參數生產和之間的隨機數,但是概率不是平均的,從到方向概率逐漸加大。
公司前段時間根據業務方需求需要做一個搶紅包的活動,網上也搜索了很多資料。記錄下整體的設計思路以及運營過程中的各種問題。
產品需求:1.紅包支持配置開始時間、結束時間、類型(隨機金額或固定金額)、單個最小紅包金額、單個最大紅包金額
2.可領取紅包的業務條件(根據業務信息指定某些滿足條件的人可以搶)
難點1:紅包算法(根據紅包配置最大、最小金額、數量生成符合條件的紅包集合)
因為紅包有配置單個紅包的最大和最小金額,所以不能完全使用隨機分配的方式。
所以要求:
? ? * 單個紅包金額既要大于最小金額,又要小于最大金額
? ? * 根據紅包總金額和個數要正好將錢分完
* 單個紅包精確到分,也就是小數點后兩位
實現代碼:
/* * @todo 設置隨機紅包金額 * return array */ public function setRandMoney() { $result = []; //取小數點后兩位將金額乘100 $this->total = $this->total * 100;//紅包總金額 $this->min = $this->min * 100;//單個紅包最小金額 $this->max = $this->max * 100;//單個紅包最大金額 //獲取紅包平均金額 $average = $this->total / $this->num; for ($i = 0; $i < $this->num; $i++) { //因為小紅包的數量通常是要比大紅包的數量要多的,因為這里的概率要調換過來。 //當隨機數>平均值,則產生小紅包 //當隨機數<平均值,則產生大紅包 if (rand($this->min, $this->max) > $average) { // 在平均線上減錢 $temp = $this->min + $this->xRandom($this->min, $average); $result[$i] = $temp; $this->total -= $temp; } else { // 在平均線上加錢 $temp = $this->max - $this->xRandom($average, $this->max); $result[$i] = $temp; $this->total -= $temp; } } // 如果還有余錢,則嘗試加到小紅包里,如果加不進去,則嘗試下一個。 while ($this->total > 0) { for ($i = 0; $i < $this->num; $i++) { if ($this->total > 0 && $result[$i] < $this->max) { $result[$i]++; $this->total--; } } } // 如果錢是負數了,還得從已生成的小紅包中抽取回來 while ($this->total < 0) { for ($i = 0; $i < $this->num; $i++) { if ($this->total < 0 && $result[$i] > $this->min) { $result[$i]--; $this->total++; } } } if (!empty($result)) { //將紅包放入隊列之中 foreach ($result as $val) { $this->redis->lPush($this->redpack_money_queue . $this->act_id, $val / 100); } return ["code" => "0", "msg" => "success"]; } return ["code" => "1", "msg" => "創建紅包失敗,請檢查參數"]; } /** * 生產min和max之間的隨機數,但是概率不是平均的,從min到max方向概率逐漸加大。 * 先平方,然后產生一個平方值范圍內的隨機數,再開方,這樣就產生了一種“膨脹”再“收縮”的效果。 */ private function xRandom($bonus_min, $bonus_max) { $sqr = intval($this->sqr($bonus_max - $bonus_min)); $rand_num = rand(0, ($sqr - 1)); return intval(sqrt($rand_num)); } private function sqr($n) { return $n * $n; }
因為取最小和最大金額之間隨機數的時候使用了intval()函數導致該算法只能處理整數,故在處理的時候將金額乘100 ,在最后入隊列的時候再將其 除100,這樣就將其精確到小數點后兩位。
難點2:高并發時對服務器的訪問壓力
類似搶紅包、1元搶購,秒殺等業務場景都是在同一時間大量請求堆積到服務器,從而導致服務器資源緊張,程序處理不過來。那么我們要做的就是將流量控制住,不讓大量的請求透過web服務器直接打到數據庫層。那么從用戶訪問url到收到返回結果整體流程是什么樣子呢?
客戶端層,用戶在微信中打開URL,DNS解析域名至服務器
web服務器層, Apache、Nginx或Tomcat等
服務器層,分配php-fpm進程,代碼接收參數進行邏輯處理
數據持續化層次,將結果保存至mysql或Redis層次
客戶端層優化方案:(限流)
前端URL使用html靜態頁面顯示內容,并將頁面顯示圖片盡量壓縮,減少服務器帶寬壓力。推薦使用base64解碼圖片
使用連接池控制流量,用戶點擊搶紅包時,發起ajax請求,調用后臺使用java寫的redis incr 接口,每次調用則鍵值 +1,并將自增id返回,當后臺代碼處理完后再將其鍵值減掉,因為incr自增為原子級別,所以前端可以根據當前有多少用戶在等待中。 根據自身服務器配置以及業務場景預估N多請求會導致服務器出現問題,如果當前等待處理的請求數大于N則前端提示用戶 "當前請求過多,請稍后再試",反之則可以正常發起請求。
Web層優化方案(lua+nginx實現頻率控制)
Nginx來處理訪問控制的方法有多種,實現的效果也有多種,訪問IP段,訪問內容限制,訪問頻率限制等。
用Nginx+Lua+Redis來做訪問限制主要是考慮到高并發環境下快速訪問控制的需求。
Nginx處理請求的過程一共劃分為11個階段,分別是:
`post-read、server-rewrite、find-config、rewrite、post-rewrite、 preaccess、access、post-access、try- files、content、log`. 在openresty中,可以找到: `set_by_lua`,`access_by_lua`,`content_by_lua`,`rewrite_by_lua`等方法。 那么訪問控制應該是,`access`階段。
2.根據請求的ip段來控制訪問流量,每次接收到搶紅包的url后將redis連接池中id自增,當超過某個峰值時跳轉到等待頁。
具體配置方案參考:http://homeway.me/2015/08/11/...
php代碼層(防止出現發多、重復領取、權限等情況)
使用redis queue 隊列功能來控制超發的情況,將每個算出來的小紅包lpush至隊列中,每次收到請求后消費最后一個小紅包,因為redis的的隊列為阻塞模式,所以當隊列中為空時是不返回數據的,也就可以保證出現并發時不會一個紅包分配給多人。
使用 redis list集合來控制重復領取的情況,每次接收到請求后將用戶id放置已領取的集合中(這點很重要,一定要在消費隊列前放置集合中,要不會出現因為并發導致重復領取),消費成功則跳出,反之則將其移出已領取集合。
因為業務需求處理起來很繁瑣,所以在活動創建的時候就根據活動規則將可領取的人員放置集合中,權限判斷可以使用待領取集合來控制。
以下為我的代碼實現(小菜一枚,大神勿噴)
/* * @todo 獲取紅包金額 * @return array */ public function doRush() { $act_info = $this->getPackInfo($this->act_id); if(empty($act_info)){ return ["code"=>"1","msg"=>"活動信息錯誤,請聯系管理員"]; } if($act_info["start_time"] > now()){ return ["code"=>"2","msg"=>"紅包尚未開搶,請稍后再試"]; } if($act_info["end_time"] <= now()){ return ["code"=>"1","msg"=>"活動已結束"]; } //將請求用戶先放置已領取的集合中 if(!$this->redis->sAdd($this->rushed_list_key,$this->user_id)){ return ["code"=>"1","msg"=>"每個紅包只能領取一次哦"]; } $money = $this->redis->lPop($this->redpack_money_queue); if(empty($money)){ $this->redis->sRem($this->rushed_list_key,$this->user_id); return ["code"=>"1","msg"=>"您來完了呦,紅包已搶光"]; } //將已搶的用戶和金額記錄至隊列中 $add_res = $this->amountAdd($money); if($add_res["code"] != 0){ return ["code"=>"1","msg"=>"系統繁忙,請稍后再試"]; } return ["code"=>"0","msg"=>"success","data"=>$money."元"]; }
數據層(使用異步持續化)
用戶領取成功后,將用戶id及領取的金額存至已領取的redis queue中,異步進程根據其中的user_id和money值將其數據更新至mysql表中
--------------------------------------------------我是萬惡的分割線------------------------------------------------------------------
補充說明:
本人第一次將實際開發過程以及想法落實到書面上,對于我這種小菜來說已經很不錯了,懇求各位大神勿噴。其中紅包算法和一些處理方案也是第一次接觸,參考了網上很多資料,學到了很多。如果你有更好的方案的話多多交流~~
----PHP小菜一枚------
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/39923.html
摘要:記錄下整體的設計思路以及運營過程中的各種問題。如果錢是負數了,還得從已生成的小紅包中抽取回來將紅包放入隊列之中創建紅包失敗,請檢查參數生產和之間的隨機數,但是概率不是平均的,從到方向概率逐漸加大。 公司前段時間根據業務方需求需要做一個搶紅包的活動,網上也搜索了很多資料。記錄下整體的設計思路以及運營過程中的各種問題。 產品需求: 1.紅包支持配置開始時間、結束時間、類型(隨機金額或固定金...
摘要:記錄下整體的設計思路以及運營過程中的各種問題。如果錢是負數了,還得從已生成的小紅包中抽取回來將紅包放入隊列之中創建紅包失敗,請檢查參數生產和之間的隨機數,但是概率不是平均的,從到方向概率逐漸加大。 公司前段時間根據業務方需求需要做一個搶紅包的活動,網上也搜索了很多資料。記錄下整體的設計思路以及運營過程中的各種問題。 產品需求: 1.紅包支持配置開始時間、結束時間、類型(隨機金額或固定金...
摘要:,大家好,很榮幸有這個機會可以通過寫博文的方式,把這些年在后端開發過程中總結沉淀下來的經驗和設計思路分享出來模塊化設計根據業務場景,將業務抽離成獨立模塊,對外通過接口提供服務,減少系統復雜度和耦合度,實現可復用,易維護,易拓展項目中實踐例子 Hi,大家好,很榮幸有這個機會可以通過寫博文的方式,把這些年在后端開發過程中總結沉淀下來的經驗和設計思路分享出來 模塊化設計 根據業務場景,將業務...
閱讀 849·2023-04-25 21:21
閱讀 3231·2021-11-24 09:39
閱讀 3072·2021-09-02 15:41
閱讀 2001·2021-08-26 14:13
閱讀 1833·2019-08-30 11:18
閱讀 2775·2019-08-29 16:25
閱讀 510·2019-08-28 18:27
閱讀 1585·2019-08-28 18:17