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

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

有问必答

image.png

摘要

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

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

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

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

1. 引言

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

2. 架构设计

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

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

3. 实现步骤

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

3.1 初始化库存

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

var stock = 100 // 商品库存
var mu sync.Mutex

3.2 处理秒杀请求

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

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 并发控制和等待

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

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才引入的

func tryAcquireLock() bool {
    return mu.TryLock()
}
func releaseLock() {
    mu.Unlock()
}

4. 完整代码

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. 运行结果

image.png

6. 总结

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

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

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

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

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

相关文章
|
2月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
275 86
|
4月前
|
分布式计算 算法 安全
Go语言泛型-泛型约束与实践
Go语言中的泛型约束用于限制类型参数的范围,提升类型安全性。通过接口定义约束,可实现对数值类型、排序与比较等操作的支持。开发者既可使用标准库提供的预定义约束,如constraints.Ordered和constraints.Comparable,也可自定义约束以满足特定需求。泛型广泛应用于通用数据结构(如栈、队列)、算法实现(如排序、查找)及构建高效可复用的工具库,使代码更简洁灵活。
|
5月前
|
设计模式 人工智能 Go
go 依赖注入实践
依赖注入(DI)是一种软件设计模式,旨在降低代码耦合度,提高代码可测试性和可复用性。其核心思想是将依赖项从外部传入使用对象,而非由其内部创建。通过 DI,模块间关系更清晰,便于维护和扩展。常见实现包括方法注入和接口注入,适用于如 Go 等支持函数式编程和接口抽象的语言。
131 8
|
5月前
|
开发框架 JSON 中间件
Go语言Web开发框架实践:路由、中间件、参数校验
Gin框架以其极简风格、强大路由管理、灵活中间件机制及参数绑定校验系统著称。本文详解其核心功能:1) 路由管理,支持分组与路径参数;2) 中间件机制,实现全局与局部控制;3) 参数绑定,涵盖多种来源;4) 结构体绑定与字段校验,确保数据合法性;5) 自定义校验器扩展功能;6) 统一错误处理提升用户体验。Gin以清晰模块化、流程可控及自动化校验等优势,成为开发者的优选工具。
|
5月前
|
开发框架 安全 前端开发
Go Web开发框架实践:模板渲染与静态资源服务
Gin 是一个功能强大的 Go Web 框架,不仅适用于构建 API 服务,还支持 HTML 模板渲染和静态资源托管。它可以帮助开发者快速搭建中小型网站,并提供灵活的模板语法、自定义函数、静态文件映射等功能,同时兼容 Go 的 html/template 引擎,具备高效且安全的页面渲染能力。
|
5月前
|
开发框架 JSON 中间件
Go语言Web开发框架实践:使用 Gin 快速构建 Web 服务
Gin 是一个高效、轻量级的 Go 语言 Web 框架,支持中间件机制,非常适合开发 RESTful API。本文从安装到进阶技巧全面解析 Gin 的使用:快速入门示例(Hello Gin)、定义 RESTful 用户服务(增删改查接口实现),以及推荐实践如参数校验、中间件和路由分组等。通过对比标准库 `net/http`,Gin 提供更简洁灵活的开发体验。此外,还推荐了 GORM、Viper、Zap 等配合使用的工具库,助力高效开发。
|
5月前
|
数据采集 安全 Go
Go 语言并发编程基础:Goroutine 的创建与调度
Go 语言的 Goroutine 是轻量级线程,由 runtime 管理,具有启动快、占用小、支持高并发的特点。本章介绍 Goroutine 的基本概念、创建方式(如使用 `go` 关键字或匿名函数)、M:N 调度模型及其工作流程,并探讨其在高并发场景中的应用,帮助理解其高效并发的优势。
|
6月前
|
设计模式 缓存 算法
Go如何进行高质量编程与性能调优实践
本文介绍了Go语言高质量编程与性能调优的实践方法。高质量编程包括良好的编码习惯(如清晰注释、命名规范)、代码风格与设计(如MVC模式)、简洁明了的代码原则,以及单元测试与代码重构的重要性。性能调优方面,涵盖算法优化、数据结构选择、I/O优化、内存管理、并行与并发处理优化及代码层面的改进。通过这些方法,可有效提升代码质量和系统性能。
146 13
|
5月前
|
Go 开发者
Go 并发编程基础:无缓冲与有缓冲通道
本章深入探讨Go语言中通道(Channel)的两种类型:无缓冲通道与有缓冲通道。无缓冲通道要求发送和接收必须同步配对,适用于精确同步和信号通知;有缓冲通道通过内部队列实现异步通信,适合高吞吐量和生产者-消费者模型。文章通过示例对比两者的行为差异,并分析死锁风险及使用原则,帮助开发者根据场景选择合适的通道类型以实现高效并发编程。
|
4月前
|
Linux Go 开发者
Go语言泛型-泛型约束与实践
《Go语言实战指南》介绍了如何使用Go进行交叉编译,即在一个操作系统上编译出适用于不同系统和架构的二进制文件。通过设置GOOS和GOARCH环境变量,开发者可轻松构建跨平台程序,无需在每个平台上单独编译。Go从1.5版本起原生支持此功能,极大提升了多平台部署效率。