并发抢票
2022-12-25 本文已影响0人
彳亍口巴
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
// 高并发抢票服务
func main() {
//startServer()
fmt.Println("启动项目")
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))
-- 查看是否还有余票,增加订单数量,返回结果值
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 远端统一扣库存
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) // 控制并发,一次只能有一个请求通过
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("购买成功", "./stat.log")
c.String(200, "购买成功")
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)
}
并发买票
1、初始化 redis中设置总票数和0订单数
2、本地初始化 根据评估,本地初始化票数,只能是本地有票的情况下才去redis中访问,两个地方同时有票才可以买票成功,否则失败
3、买票时本地票已卖完的话,直接返回失败,不需要访问redis,减少访问
4、为了保证票没有超卖,本地买票的解决方法是通过channel控制,缓存长度为1的channel,请求尝试往里面写入数据,如果可能写入,说明可以拿到锁,否则就阻塞,缺点是会一直阻塞,可通过上下文超时时间自动取消
redis防止超卖是用LUA脚本解决,串行读写,先判断有票,再Hincrby增加即可
订单
上面描述的是并发去买票的经过,保证了买票的顺序进行,并且不会超卖,但是抢到票后,还需要创建订单,付款和发货