天天看點

golang 秒殺活動元件

使用go進行商品秒殺

package main

import (
  "errors"
  "fmt"
  "log"
  "runtime"
  "sync"
  "time"
)

var GoodsA = SecondObject{&sync.RWMutex{}, 10}

func main() {
  // 單例消費
  fmt.Println("案例一: 單筆消費")
  GoodsA.SecondKill(5*time.Second, 3, func(r chan Result) {
    // 支付訂單
    time.Sleep(4 * time.Second)
    r <- Result{1, nil}
  })
  fmt.Println("消費成功,剩餘:", GoodsA.Num)

  // 逾時消費
  fmt.Println("案例2: 消費逾時")
  n, e := GoodsA.SecondKill(3*time.Second, 3, func(r chan Result) {
    // 支付訂單
    time.Sleep(4 * time.Second)
    r <- Result{1, nil}
  })
  fmt.Println(n, e)

  // 庫存不足
  fmt.Println("案例3: 庫存不足")
  n, e = GoodsA.SecondKill(3*time.Second, 11)
  fmt.Println(n, e)

  fmt.Println("案例4: 1000個請求,搶購剩餘7個")
  var wg sync.WaitGroup
  runtime.GOMAXPROCS(runtime.NumCPU())
  wg.Add(1000)
  for i := 0; i < 1000; i++ {
    go func(i int) {
      defer wg.Done()
      n, e := GoodsA.SecondKill(5*time.Second, 1)
      fmt.Println(fmt.Sprintf("request %d result:", i), n, e)
    }(i)
  }
  wg.Wait()

}

type SecondObject struct {
  M   *sync.RWMutex
  Num int
}

// 下單到支付成功之間的handler result
// 1,nil 成功
// 2, error 錯誤
type Result struct {
  Status int
  Err    error
}

// 0,nil success
// 500, error 系統錯誤
// 400, error 請求錯誤
// 2, error 庫存不足,賣完了
// 3, error 購買數量大于庫存
// 4, 逾時
func (s *SecondObject) SecondKill(timeout time.Duration, buyNum int, handlers ... func(results chan Result)) (int, error) {

  defer func() {
    if e := recover(); e != nil {
      log.Println(e)
    }
  }()
  s.M.Lock()
  defer s.M.Unlock()
  if buyNum > s.Num {
    return 3, errors.New(fmt.Sprintf("buy number %d more than saved number %d", buyNum, s.Num))
  }
  if s.Num < 0 {
    return 2, errors.New("save out")
  }

  var jobNum = len(handlers)
  result := make(chan Result)

  for _, v := range handlers {
    go v(result)
  }

  for {
    if jobNum == 0 {
      break
    }
    select {
    case <-time.After(timeout):
      return 4, errors.New("time out")
    case jobResult := <-result:
      switch jobResult.Status {
      case 1:
        jobNum --
      case 2:
        return jobResult.Status, jobResult.Err
      }
    }
  }

  s.Num -= buyNum
  return 0, nil
}