競拍系統設計(秒殺系統知識遷移)

自從上次整理了秒殺系統的文章(php+golang商品秒殺)後,知識遷移一新項目,商品競拍。php

技術:php、mysql、redis、laravel
業務對象:商品、場次、訂單
競拍過程:

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.第二個版本的數據結構設計

  • 用戶可查詢的商品id的zset (判斷用戶是否有可競拍商品)
key: prefix + area_id +users_id + auction_id+ start + end, 
 score:goods_id, 
 member:goods_id
  • 商品信息string (可支持分頁)
key: prefix + area_id + auction_id + goods_id + start + end, 
 value:goods
  • 庫存字面量
key: random  
 value:1
  • 是否購買佔位
key: prefix + area_id + start + end  
  value:1

redis緩存數據

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併發編程模擬秒殺

圖片請參考另一篇文章:PHP+Golang 商品秒殺功能
================================================================

golang併發調度項目碼雲:

https://gitee.com/jasonlxs/se...

相關文章
相關標籤/搜索