「有问必答」秒杀系统 Go并发编程实践!

有问必答

摘要

本文将介绍如何使用Go语言的并发原语来构建一个简单的高并发秒杀系统。

我们将使用Go语言的原生库和一些常见的技术手段,包括互斥锁、通道、计数器等,来解决并发访问和数据一致性的问题。

本文只是一个简单的示例,重点是Go语言并发原语在业务场景中的应用。

在实际应用中,还需要考虑数据库事务、分布式锁、限流等问题。我之前也写过一篇文章,附在了文末。

1. 引言

秒杀系统是一种高并发场景下的特殊应用,需要处理大量的并发请求和保证数据的一致性。本文将介绍如何使用Go语言的并发原语来构建一个高并发的秒杀系统,以满足用户的需求并保证系统的稳定性。

2. 架构设计

我们的秒杀系统将采用经典的客户端-服务器架构。客户端发送秒杀请求,服务器处理请求并更新库存。为了保证系统的高并发性能,我们将使用以下技术和原语:

  • 互斥锁(sync.Mutex):用于保护共享资源的并发访问。
  • 通道(channel):用于协程间的通讯。
  • 计数器(sync.WaitGroup):用于等待所有请求完成。

3. 实现步骤

下面是我们实现秒杀系统的关键步骤:

3.1 初始化库存

在系统启动时,我们需要初始化商品的库存。

go 复制代码
var stock = 100 // 商品库存
var mu sync.Mutex

3.2 处理秒杀请求

当客户端发送秒杀请求时,服务器需要处理请求并更新库存。

go 复制代码
func handleRequest(user int) {
    defer wg.Done()
    if tryAcquireLock() {
        if stock > 0 {
            // 执行秒杀逻辑
            stock--
            fmt.Printf("用户%d秒杀成功,剩余库存:%d\n", user, stock)
        } else {
            fmt.Printf("用户%d秒杀失败,库存不足\n", user)
        }
        releaseLock()
    } else {
        fmt.Printf("用户%d未获取到锁,秒杀失败\n", user)
    }
}

3.3 并发控制和等待

为了控制并发请求的数量,我们使用计数器和通道来限制并发度。

go 复制代码
var wg sync.WaitGroup

func main() {
    for i := 1; i <= 1000; i++ {
        wg.Add(1)
        go handleRequest(i)
    }
    wg.Wait()
}

3.4 互斥锁和并发安全

为了保证并发访问的安全性,我们使用互斥锁来保护共享资源的访问。

注意:TryLock()是go1.18才引入的

go 复制代码
func tryAcquireLock() bool {
    return mu.TryLock()
}

func releaseLock() {
    mu.Unlock()
}

4. 完整代码

go 复制代码
package main

import (
 "fmt"
 "sync"
)

//后面开启了1000个goroutine,所以这里channel的缓冲区设置成了1000
var ch = make(chan bool, 1000)

type Product struct {
 sync.Mutex
 stock int64 // 商品库存
}

func main() {
 p := Product{stock: 1000}
 for i := 1; i <= 1000; i++ {
  go p.handleRequest(i)
 }
 <-ch
}

func (p *Product) handleRequest(user int) {
 if p.tryAcquireLock() {
  if p.stock > 0 {
   // 执行秒杀逻辑
   p.stock--
   fmt.Printf("用户%d秒杀成功,剩余库存:%d\n", user, p.stock)
  } else {
   fmt.Printf("用户%d秒杀失败,库存不足\n", user)
  }
  //这里是不可以使用defer的,因为可能会加锁失败,unlock一个不存在的锁
  p.releaseLock()
 } else {
  fmt.Printf("用户%d未获取到锁,秒杀失败\n", user)
 }
}

func (p *Product) tryAcquireLock() bool {
//p.TryLock() 方法用于尝试获取锁,如果成功获取到锁,则相当于执行了 Lock() 操作,即加锁成功。 
 return p.TryLock()
}

func (p *Product) releaseLock() {
 p.Unlock()
 ch <- true
}

解析代码

var ch = make(chan bool, 1000):后面开启了1000个goroutine,所以这里channel的缓冲区设置成了1000

p.releaseLock():这里是不可以使用defer的,因为可能会加锁失败,unlock一个不存在的锁

p.TryLock():方法用于尝试获取锁,如果成功获取到锁,则相当于执行了 Lock() 操作,即加锁成功。

5. 运行结果

6. 总结

通过使用Go语言的并发原语,我们成功地构建了一个高并发的秒杀系统。

使用互斥锁和计数器等原语,我们实现了并发控制、数据一致性和并发安全。这些原语帮助我们解决了高并发场景下的并发访问问题,并保证了系统的稳定性和性能。

本文只是一个简单的示例,实际的秒杀系统可能涉及更多的业务逻辑和并发控制。

在实际应用中,还需要考虑数据库事务、分布式锁、限流等问题。因此,建议根据实际需求和场景进行更详细的设计和实现。

我之前也有写万字长文总结过,感兴趣的朋友欢迎查看:万字详解:秒杀系统设计

一起学习

欢迎大家关注我的账号,你的支持,是我更文的最大动力!

也欢迎关注我的公众号: 程序员升职加薪之旅,领取更多Go学习和面试资料。

微信号:wangzhongyang1993

相关推荐
bobz9659 分钟前
ovs patch port 对比 veth pair
后端
Asthenia041219 分钟前
Java受检异常与非受检异常分析
后端
uhakadotcom33 分钟前
快速开始使用 n8n
后端·面试·github
JavaGuide40 分钟前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9651 小时前
qemu 网络使用基础
后端
Asthenia04121 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04121 小时前
Spring 启动流程:比喻表达
后端
Asthenia04122 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua2 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫
致心2 小时前
记一次debian安装mariadb(带有迁移数据)
后端