Contents

The Design of a Instant Buying System

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
  • Get a CDN service

    • Domain:cdn.shop.com
    • Set 11.11.11.11 as source node

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, status etc.
        • status is NOT_SENT by default.
    • Polling the table,retry to send when status is NOT SENT
      • Update status to SENT when sends the msg successfully.

Receive the msg

  • What if repeated receiving?
    • Update status to RECEIVED

Create order and handle payment

Time out to pay

Create order

  • The order status is UNPAID by default.

Send the order msg to a delay MQ

Receive the order msg and check its status

  • If status is still UNPAID, rollback the stock.
    redis.call('INCRBY', KEYS[1], 1);