The Design of a Instant Buying System
Contents
Use the static web page
-
Get a domain such as
shop.com -
Get a server as the source node for CDN
- Public IP such as
11.11.11.11 - Deploy a web server to serve web pages on this sever
- Public IP such as
-
Get a CDN service
- Domain:
cdn.shop.com - Set
11.11.11.11as source node
- Domain:
Disable the snaching button
- The snaching button is disabled until the beginnng of the buying event. Refresh the CDN to enable.
Check the product to be snached
Avoid cache breakdown (避免缓存击穿)
if p, ok := redis.getProduct(productId); ok{
discountStock(p)
}else{
// 没抢到锁,等待再抢
tryTimes := 3
for !lock() {
time.Sleep(100*time.Millisecond)
tryTimes--
if tryTimes == 0{
// 秒杀失败
return errFailToSnatch
}
}
if p, ok := redis.getProduct(productId); ok{
// 有缓存,立马解锁
unlock()
discountStock(p)
}else{
p = db.getProduct(productId)
redis.setProduct(productId, p)
unlock()
}
}
Avoid cache penetration(避免缓存穿透)
if p, ok := redis.getProduct(productId); ok{
if p.Id == 0{
// 秒杀商品不存在
return errProductNotExist
}
discountStock(p)
}else{
// 没抢到锁,等待再抢
tryTimes := 3
for !lock() {
time.Sleep(100*time.Millisecond)
tryTimes--
if tryTimes == 0{
// 秒杀失败
return errFailToKill
}
}
if p, ok := redis.getProduct(productId); ok{
// 有缓存,立马解锁
unlock()
if p.Id == 0{
return errProductNotExist
}
discountStock(p)
}else{
p = db.getProduct(productId)
// 数据库也没查到商品,缓存零值,防止缓存穿透
if p == nil{
p = new(Product)
}
redis.setProduct(productId, p)
unlock()
}
}
Discount the stock
-- atomic operation with lua scripts
if (redis.call('EXISTS', KEYS[1]) == 1) then
local stock = tonumber(redis.call('GET', KEYS[1]));
if (stock > 0) then
redis.call('INCRBY', KEYS[1], -1);
return stock;
end;
return 0;
end;
When succeeds to discount the stock, it’s time to create order. Otherwise, response back an error or a msg ‘SNATCHED OUT’.
Create order
Send the msg to a MQ
- What if fail to send?
- Save the msg to a db
- Table: panic_buying_msg
- Primary colums:
product_id,user_id,statusetc.statusisNOT_SENTby default.
- Polling the table,retry to send when
statusisNOT SENT- Update
statustoSENTwhen sends the msg successfully.
- Update
- Save the msg to a db
Receive the msg
- What if repeated receiving?
- Update
statustoRECEIVED
- Update
Create order and handle payment
Time out to pay
Create order
- The order
statusisUNPAIDby default.
Send the order msg to a delay MQ
Receive the order msg and check its status
- If
statusis stillUNPAID, rollback the stock.redis.call('INCRBY', KEYS[1], 1);