自從上次整理了秒殺系統的文章(php+golang商品秒殺)後,知識遷移一新項目,商品競拍。php
1、實現商品、競拍場次和訂單的CRUD;
2、定時將秒殺場次、商品、庫存等信息提早寫入redis;
3、配置Redis持久化;
4、實現秒殺下單邏輯;
5、秒殺過程redis優化;
6、使用golang併發編程模擬秒殺。前端
1、實現商品、競拍場次和訂單的CRUD;mysql
商品表:laravel
CREATE TABLE `goods` ( `id` int(12) unsigned NOT NULL AUTO_INCREMENT COMMENT 'pk', `num` varchar(64) NOT NULL COMMENT '商品編號', `users_id` int(12) unsigned NOT NULL COMMENT '擁有者', `create_users_id` int(12) unsigned NOT NULL COMMENT '商品建立人', `contract_roles_id` int(10) unsigned NOT NULL COMMENT '商品合約級別外鍵', `name` varchar(255) NOT NULL COMMENT '商品名稱', `img` int(11) NOT NULL COMMENT '封面圖', `price` decimal(10,2) unsigned NOT NULL COMMENT '當前價格', `area_id` int(11) NOT NULL COMMENT '區域id', `trade_num` int(11) unsigned NOT NULL COMMENT '交易次數', `user_name` varchar(100) DEFAULT NULL COMMENT '收貨人名稱', `user_phone` varchar(11) DEFAULT NULL COMMENT '收貨人聯繫電話', `user_address` varchar(255) DEFAULT NULL COMMENT '收貨人地址', `express_id` int(11) DEFAULT NULL COMMENT '物流ID', `express_no` varchar(255) DEFAULT NULL COMMENT '物流單號', `is_auction` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否可競拍,1=》可 2=》不可', `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '狀態1=>可交易 2=>待支付 3=>交易完成 4=>待發貨 5=》配送中 6=>完成 7 =>待收款', `next_time` timestamp NULL DEFAULT NULL COMMENT '下次最先顯示時間', `trade_time` timestamp NULL DEFAULT NULL COMMENT '下次可交易時間', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間', `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新時間', `deleted_at` timestamp NULL DEFAULT NULL COMMENT '刪除時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
競拍場次表:git
CREATE TABLE `auctions` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', `area` tinyint(4) NOT NULL COMMENT '拍賣區域,1=>新手區,2=>競拍區,3=>星級區', `name` varchar(64) DEFAULT NULL COMMENT '場次名稱', `start` time NOT NULL COMMENT '開始時間', `end` time NOT NULL COMMENT '結束時間', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間', `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新時間', `deleted_at` timestamp NULL DEFAULT NULL COMMENT '刪除時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='拍賣場次表';
訂單表:golang
CREATE TABLE `orders` ( `id` int(12) unsigned NOT NULL AUTO_INCREMENT COMMENT 'pk', `serial_num` varchar(32) DEFAULT NULL COMMENT '流水號,沒交易前爲空', `goods_id` int(12) unsigned NOT NULL COMMENT '商品id', `sell_users_id` int(12) unsigned NOT NULL COMMENT '競拍商品擁有者id', `buy_users_id` int(12) unsigned DEFAULT NULL COMMENT '購買商品用戶id', `buy_price` decimal(10,2) NOT NULL COMMENT '購買價格-成本價格', `pay_time` datetime DEFAULT NULL COMMENT '支付時間', `status` char(5) NOT NULL COMMENT '狀態10000=>待支付 20000=>支付超時 30000=>確認支付 30001=>確認收款 40000=>賣家申訴中 40001=>買家申訴中 45000=>申訴完成 50000=>完成', `contract_roles_id` int(10) NOT NULL COMMENT '購買時商品合約外鍵', `charge_rate` decimal(10,4) unsigned DEFAULT NULL COMMENT '手續費', `remark` varchar(255) DEFAULT NULL COMMENT '備註-能夠填寫申訴結果', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間', `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新時間', `deleted_at` timestamp NULL DEFAULT NULL COMMENT '刪除時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
2、定時提早寫入redisredis
一、競拍場次時間是爲天天固定的三個時間,定時提早寫入並設置過時時間。sql
二、緩存數據結構設計有兩個版本:數據庫
a、第一個版本的數據結構設計在商品列表查詢時,沒法排除自身商品信息而且分頁。express
不一樣區域可秒殺的用戶set (判斷用戶所屬競拍區)
key: prefix + area_id + start + end + auctions_id, value:uid
不一樣區域下的商品信息zset (可支持分頁)
key: prefix + area_id + start + end + auctions_id, score:goods_id, member:goods_detail
庫存字面量
key: random value:1
是否購買佔位
key: prefix + area_id + start + end value:1
爲了知足排除自身的商品功能和分頁,思考了一些實現方案:
(1) 徹底放棄從緩存中獲取競拍商品信息,這樣增長數據庫壓力,同時沒法使用競拍隨機碼。
(2)爲每一個用戶單獨存放一個排除自身商品信息的集合,這樣會存放重複數據形成增長內容空間。
(3)查詢到redis有一個SCAN命令來迭代獲取數據,並可利用glob模式匹配,可是獲取數量沒法肯定而沒法分頁。
以上(1)(3)點都被排除,咱們從第(2)出發從新設計第二版數據結構,單獨存放商品數據和用戶可查詢的商品id集合來減小重複,但又會出現keys過多的狀況,須要進行優化。
b.第二個版本的數據結構設計
key: prefix + area_id +users_id + auction_id+ start + end, score:goods_id, member:goods_id
key: prefix + area_id + auction_id + goods_id + start + end, value:goods
key: random value:1
key: prefix + area_id + start + end value:1
3、配置Redis持久化
持久化兩種模式都開啓:RDB(快照模式)+ AOF(日誌模式)
配置文件:save/append_only
區別:二者數據保存間隔週期不一樣,RDB存儲間隔大於AOF存儲間隔
4、實現秒殺下單邏輯
一、查詢場次和當前秒殺商品
查詢redis中的緩存數據,當併發量大時可能出現:
緩存穿透:key值不存在,重複請求壓垮數據庫 => 布隆過濾器或設置緩存爲空。
緩存擊穿:key值存在可是失效,需從新請求數據庫形成併發問題 => SETNX鎖
緩存雪崩:緩存重啓或集中失效,則都請求往DB => 過時時間設置分散
二、正式競拍是單獨的秒殺下單功能。
三、具體的下單邏輯:
登陸校驗 => 秒殺過程校驗 => 經過隊列進行異步下單同時返回訂單號orderSN
秒殺過程當中校驗點以下:
秒殺時間:是否在秒殺時間內;
用戶是否在該區有可競拍商品
隨機碼:商品是否可秒殺;
是否已購買過:經過redis的SETNX設置Key=場次id_商品id_用戶id來判斷是否購買過。
秒殺庫存數量:在獲取對應庫存信息前,將隨機碼做爲key設置SETNX來實現併發鎖,設置超時時間,秒殺成功或失敗都釋放該鎖。
5、秒殺過程redis優化
因緩存數據結構的設計,可能會在redis存儲大量的key,若經過keys命令查詢會是O(n)複雜度,查詢會卡頓而緩慢,redis有提供scan迭代來代替keys,可是根據本項目無需使用它。
優化大體有兩個方面:
一、在提早將競拍信息寫入redis時,因key數量大,可採用redis的pipeline管道來提升寫入效率
二、儘量將場次和開始結束時間返回前端讓其在查詢或競拍時傳給後端,後端拼接key值獲取數據的時間複雜度是O(1)。
6、使用golang併發編程模擬秒殺
golang併發調度項目碼雲: