并發(fā)搶票

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

// 高并發(fā)搶票服務(wù)

func main() {
    //startServer()
    fmt.Println("啟動(dòng)項(xiàng)目")
    r := RoutersInit()
    err := r.Run(":9090")
    if err != nil {
        panic(err)
    }
}

func startServer() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })

    r.Run(":9090")
}

func RoutersInit() *gin.Engine {

    r := gin.New()

    c := r.Group("/index")
    {

        p := c.Group("/post")
        {
            p.GET("/", Buy)

        }
    }

    return r
}

package main

import (
    "github.com/garyburd/redigo/redis"
)

var constLuaScript = `
        local ticket_key = KEYS[1]
        local ticket_total_key = ARGV[1]
        local ticket_sold_key = ARGV[2]
        local ticket_total_nums = tonumber(redis.call('HGET', ticket_key, ticket_total_key))
        local ticket_sold_nums = tonumber(redis.call('HGET', ticket_key, ticket_sold_key))
        -- 查看是否還有余票,增加訂單數(shù)量,返回結(jié)果值
       if(ticket_total_nums >= ticket_sold_nums) then
            return redis.call('HINCRBY', ticket_key, ticket_sold_key, 1)
        end
        return 0
`

var (
    redisPool *redis.Pool
)

func init() {
    redisPool = NewPool()
}

func NewPool() *redis.Pool {
    return &redis.Pool{
        MaxIdle:   10000,
        MaxActive: 12000, // max number of connections
        Dial: func() (redis.Conn, error) {
            //c, err := redis.Dial("tcp", "127.0.0.1:6379", redis.DialPassword("123456"))
            c, err := redis.Dial("tcp", "127.0.0.1:6379")
            if err != nil {
                panic(err.Error())
            }
            return c, err
        },
    }
}

// RemoteDeductionStock 遠(yuǎn)端統(tǒng)一扣庫(kù)存
func RemoteDeductionStock(conn redis.Conn) bool {
    lua := redis.NewScript(1, constLuaScript)
    result, err := redis.Int(lua.Do(conn, "a", "total", "order"))
    if err != nil {
        return false
    }
    return result != 0
}

package main

import (
    "os"
    "strings"

    "github.com/gin-gonic/gin"
)

var done = make(chan struct{}, 1) // 控制并發(fā),一次只能有一個(gè)請(qǐng)求通過
var localTicket = &LocalTicket{}

type LocalTicket struct {
    TotalTicket int32
    OrderCount  int32
}

func init() {
    localTicket = &LocalTicket{
        TotalTicket: 150,
        OrderCount:  0,
    }
    done <- struct{}{}
}

func Buy(c *gin.Context) {
    <-done
    localTicket.OrderCount++
    if localTicket.OrderCount > localTicket.TotalTicket {
        writeLog("本地已售罄", "./stat.log")
        c.String(200, "本地已售罄")
        return
    }
    if RemoteDeductionStock(redisPool.Get()) {
        writeLog("購(gòu)買成功", "./stat.log")
        c.String(200, "購(gòu)買成功")
        return
    }
    writeLog("全部售罄", "./stat.log")
    c.String(200, "全部售罄")
    return
}

func writeLog(msg string, logPath string) {
    fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
    defer fd.Close()
    content := strings.Join([]string{msg, "\r\n"}, "")
    buf := []byte(content)
    fd.Write(buf)
}


并發(fā)買票

1、初始化 redis中設(shè)置總票數(shù)和0訂單數(shù)
2、本地初始化 根據(jù)評(píng)估,本地初始化票數(shù),只能是本地有票的情況下才去redis中訪問,兩個(gè)地方同時(shí)有票才可以買票成功,否則失敗
3、買票時(shí)本地票已賣完的話,直接返回失敗,不需要訪問redis,減少訪問
4、為了保證票沒有超賣,本地買票的解決方法是通過channel控制,緩存長(zhǎng)度為1的channel,請(qǐng)求嘗試往里面寫入數(shù)據(jù),如果可能寫入,說明可以拿到鎖,否則就阻塞,缺點(diǎn)是會(huì)一直阻塞,可通過上下文超時(shí)時(shí)間自動(dòng)取消
redis防止超賣是用LUA腳本解決,串行讀寫,先判斷有票,再Hincrby增加即可

訂單

上面描述的是并發(fā)去買票的經(jīng)過,保證了買票的順序進(jìn)行,并且不會(huì)超賣,但是搶到票后,還需要?jiǎng)?chuàng)建訂單,付款和發(fā)貨

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容