使用 golang + redis 实现一个简单的秒杀功能
安装 redigogo get github.com/gomodule/redigo/redisredigo 文档:https://pkg.go.dev/github.com/gomodule/redigo/redis
1. 创建 redis 连接池 (我这里是在 global 包下新建的一个 redis.go 文件,只有一行代码如下)
var RedisPool *redis.Pool紧接着,在 main.go 文件里初始化 redis 连接池
func init() { global.RedisPool = &redis.Pool{ MaxIdle: 10, // 最初的连接数量 MaxActive: 0, // 最大连接数量 0表示不限制 IdleTimeout: 300 * time.Second, // 连接关闭时间单位秒 (超时不使用自动关闭) Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "127.0.0.1:6379") }, } }
2. 实现模拟秒杀功能
var lock sync.Mutex var listName = "seckill" func main() { createQueue() }
// 模拟抢购 func createQueue() { var wg sync.WaitGroup rdb := global.RedisPool.Get() defer rdb.Close() // 设定20人抢购(用于随机抢购) userIDs := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} count := len(userIDs) var ch = make(chan int, count) f := func(id int, done func()) { defer func() { done() }() lock.Lock() defer lock.Unlock() // 判断人数是否少于10 lLen, _ := redis.Int64(rdb.Do("LLEN", listName)) if lLen < 10 { // 写入队列,字符串使用 @ 符合连接用户id和当前时间 rdb.Do("RPUSH", listName, fmt.Sprintf("%d@%v", id, time.Now())) fmt.Println(id, "抢购成功") } else { fmt.Println("抢购活动已结束") } ch <- id } wg.Add(count) for _, v := range userIDs { go f(v, wg.Done) } for i := 0; i < count; i++ { <-ch } close(ch) wg.Wait() }
运行以上代码,结果:
20 抢购成功
10 抢购成功
1 抢购成功
2 抢购成功
3 抢购成功
4 抢购成功
5 抢购成功
6 抢购成功
7 抢购成功
8 抢购成功
抢购活动已结束
抢购活动已结束
抢购活动已结束
抢购活动已结束
抢购活动已结束
抢购活动已结束
抢购活动已结束
抢购活动已结束
抢购活动已结束
抢购活动已结束
查看队列数据:
127.0.0.1:6379> lrange seckill 0 100
1) "20@2021-10-12 09:49:08.162533478 +0800 CST m=+0.001920843"
2) "10@2021-10-12 09:49:08.163083661 +0800 CST m=+0.002471032"
3) "1@2021-10-12 09:49:08.163379093 +0800 CST m=+0.002766464"
4) "2@2021-10-12 09:49:08.163807431 +0800 CST m=+0.003194802"
5) "3@2021-10-12 09:49:08.16409304 +0800 CST m=+0.003480411"
6) "4@2021-10-12 09:49:08.164380002 +0800 CST m=+0.003767371"
7) "5@2021-10-12 09:49:08.16480322 +0800 CST m=+0.004190580"
8) "6@2021-10-12 09:49:08.165220687 +0800 CST m=+0.004608047"
9) "7@2021-10-12 09:49:08.165518322 +0800 CST m=+0.004905684"
10) "8@2021-10-12 09:49:08.166351454 +0800 CST m=+0.005738847"
3. 保存到数据库 新建一个 main.go 文件,创建 redis 链接池和初始化 mysql
func init() { global.RedisPool = &redis.Pool{ MaxIdle: 10, // 最初的连接数量 MaxActive: 0, // 最大连接数量 0表示不限制 IdleTimeout: 300 * time.Second, // 连接关闭时间单位秒 (超时不使用自动关闭) Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "127.0.0.1:6379") }, } mysql.Init("root:123456@tcp(127.0.0.1:3306)/test001?charset=utf8&loc=Asia%2FShanghai") }
读取队列数据,保存到数据表 代码实现了,监控队列是否有数据,如果有数据则马上保存到数据表,如果读取插入数据失败,则把移出并获取列表的元素重新写入到队列里
func main() { rdb := global.RedisPool.Get() defer rdb.Close() for { total, err := redis.Int64(rdb.Do("LLEN", listName)) if err != nil { fmt.Println(err) return } if total == 0 { time.Sleep(1 * time.Second) fmt.Println("total:", total) continue } // LPOP:在队列最左侧取出一个值(移除并返回列表的第一个元素) str, err := redis.String(rdb.Do("LPOP", listName)) if err != nil { fmt.Println(err) return } // 分隔字符串 l := strings.Split(str, "@") // 保存到数据库中 query := "insert into seckill_list (user_id, time_stamp) values(?, ?)" _, err = mysql.Insert(query, l[0], l[1]) if err != nil { fmt.Println("mysql.Insert err:", err) // 数据库插入失败时回滚机制 rdb.Do("RPUSH", listName, str) } else { fmt.Println(fmt.Sprintf("id:%v保存成功", l[0])) } } }
运行结果(如果有数据就马上读取并保存到数据表):
id:20保存成功
id:10保存成功
id:1保存成功
id:2保存成功
id:3保存成功
id:4保存成功
id:5保存成功
id:6保存成功
id:7保存成功
id:8保存成功
total: 0
total: 0
total: 0
total: 0
id:1保存成功
id:20保存成功
id:2保存成功
id:3保存成功
id:4保存成功
id:5保存成功
id:6保存成功
id:7保存成功
id:8保存成功
id:9保存成功
total: 0
total: 0
total: 0
total: 0
保存到数据表的结果:
以上是本次简单秒杀功能的实现,如果有其他方式欢迎留言或给出建议,谢谢(#^.^#)