ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

演唱会票务预订系统(Concert Ticket Booking System)低层设计:Go 并发实现与防超卖方案全解析

演唱会票务预订系统(Concert Ticket Booking System)低层设计:Go 并发实现与防超卖方案全解析 示例工程【免费下载链接】awesome-low-level-designLearn Low Level Design (LLD) and prepare for interviews using free resources.项目地址https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design点击查看免费下载本篇技术指南以awesome-low-level-design仓库中 Concert Ticket Booking SystemGo 实现 为骨架完整拆解演唱会票务预订系统的需求分析、类设计与枚举建模并结合 Go 源码深入讲解Singleton 单例、Mutex 并发控制、座位抢占与失败回滚等核心机制。读完本文你将掌握如何用 Go 设计一个能应对并发订票请求、避免座位重复售卖的 LLDLow Level Design解决方案并能在面试与实战中复现同样的设计思路。系统概述与需求分析演唱会票务预订系统是一个经典的低层设计LLD考题核心难点不在于实体多而在于并发场景下的座位一致性。仓库内 问题描述 与 Go 实现文档 共同定义了以下八条核心需求系统允许用户查看可购演唱会及其座位布局用户可按艺人artist、场馆venue、日期时间date time等条件搜索演唱会用户可为指定演唱会选择座位并购买门票系统必须处理并发订票请求避免座位重复预订double-booking系统应保证所有用户获得公平的订票机会系统应安全处理支付流程系统应生成预订确认并通过邮件或短信发送给用户系统应为售罄演唱会提供等待列表waiting list功能。需要说明的是在仓库的 Go 实现中需求 15 有完整代码支撑需求 6 以 mock 形式占位processPayment为空实现需求 7 在booking.go中以TODO注释标记待实现需求 8等待列表暂未实现。这是本文档与源码中的真实状态下文会逐一指出。核心类设计与枚举建模原文档定义了 6 个类与 3 个枚举Go 实现将其分别落到了独立的源文件中。下面逐一对照讲解。Concert 演唱会实体Concert代表一场演唱会事件属性包含 ID、艺人、场馆、日期时间以及座位列表。在 Go 中对应 concert.gotype Concert struct { ID string Artist string Venue string DateTime time.Time Seats []*Seat }构造函数NewConcert(id, artist, venue string, dateTime time.Time, seats []*Seat)一次性注入所有字段。注意Seats使用[]*Seat指针切片目的是让后续Booking与Seat之间共享同一份状态——座位被预订后演唱会视图与预订记录都能感知到状态变化这是后续并发一致性设计的基础。Seat 座位与 SeatType / SeatStatus 枚举Seat表示演唱会中的一个座位属性包括 ID、座位号、座位类型、价格和状态并提供book预订与release释放方法。Go 实现见 seat.gotype Seat struct { ID string SeatNumber string Type SeatType Price float64 status SeatStatus mu sync.Mutex }这里有两个值得注意的设计点status字段是小写未导出外部只能通过GetStatus()读取无法直接篡改体现了 Go 语言的封装encapsulation惯例每个座位自带一把sync.Mutex这是并发安全的关键——同一座位在任何时刻只会被一个 goroutine 修改状态。枚举定义集中在 types.gotype SeatType int type SeatStatus int type BookingStatus int const ( SeatTypeRegular SeatType iota // 常规座 SeatTypePremium // 优选座 SeatTypeVIP // VIP 座 ) const ( StatusAvailable SeatStatus iota // 可预订 StatusBooked // 已预订 StatusReserved // 已预留 ) const ( BookingStatusPending BookingStatus iota // 待处理支付前 BookingStatusConfirmed // 已确认 BookingStatusCancelled // 已取消 )SeatType常规regular、优选premium、VIP 三种档位SeatStatus可预订available、已预订booked、已预留reserved。reserved状态在需求中对应“锁座/排队占位”语义当前 Go 实现中尚未使用该状态BookingStatus待处理pending、已确认confirmed、已取消cancelled完整支撑订单生命周期。座位定价规则utils.go座位类型与价格如何在真实场景中落地utils.go 中的GenerateSeats提供了一个可运行的样例规则——按座位编号分段定价func GenerateSeats(numberOfSeats int) []*Seat { seats : make([]*Seat, 0, numberOfSeats) for i : 1; i numberOfSeats; i { seatNumber : fmt.Sprintf(S%d, i) var seatType SeatType var price float64 switch { case i 10: // 前 10 个座位 seatType SeatTypeVIP price 100.0 case i 30: // 第 1130 个座位 seatType SeatTypePremium price 75.0 default: // 其余座位 seatType SeatTypeRegular price 50.0 } seats append(seats, NewSeat(seatNumber, seatNumber, seatType, price)) } return seats }规则一目了然前 10 个座位为 VIP100 元、第 1130 个为优选75 元、其余为常规50 元。这虽然是一个演示级定价策略但它完整演示了“座位类型 → 价格映射”在生产系统中如何被数据化建模你完全可以将这段逻辑替换为读数据库票价表。Booking 预订与 BookingStatusBooking表示用户对某场演唱会及若干座位的预订包含 ID、用户、演唱会、座位列表、总价和状态提供confirm确认与cancel取消方法。Go 实现见 booking.gotype Booking struct { ID string User *User Concert *Concert Seats []*Seat TotalPrice float64 Status BookingStatus } func NewBooking(id string, user *User, concert *Concert, seats []*Seat) *Booking { totalPrice : calculateTotalPrice(seats) return Booking{ ID: id, User: user, Concert: concert, Seats: seats, TotalPrice: totalPrice, Status: BookingStatusPending, } }calculateTotalPrice对座位价格求和得到TotalPrice新订单初始状态为BookingStatusPending待支付。状态机流转体现在两个方法上func (b *Booking) ConfirmBooking() { if b.Status BookingStatusPending { b.Status BookingStatusConfirmed // TODO: Send booking confirmation to user } } func (b *Booking) CancelBooking() { if b.Status BookingStatusConfirmed { b.Status BookingStatusCancelled for _, seat : range b.Seats { seat.Release() // 释放所有关联座位 } // TODO: Send cancellation notification to user } }两个关键点ConfirmBooking仅在 pending 状态下生效杜绝了已取消/已确认订单被二次确认CancelBooking会遍历并释放该订单的所有座位这是“取消订单 → 座位回到可售池”的闭环实现。取消后的座位可以被下一个用户重新预订demo 中正是靠这一步来演示座位的复用。TODO注释表明向用户发送确认/取消通知的通道邮件/SMS在需求 7 中已定义但当前实现留待扩展。User 用户User表示系统用户属性为 ID、姓名、邮箱。见 user.gotype User struct { ID string Name string Email string } func NewUser(id, name, email string) *User { return User{ID: id, Name: name, Email: email} }简单实体通过Email字段为需求 7 的邮件通知预留数据基础。SeatNotAvailableError 自定义错误SeatNotAvailableException是用于处理“座位不可预订”场景的自定义异常。Go 中通过实现error接口完成见 errors.gotype SeatNotAvailableError struct { message string } func NewSeatNotAvailableError(message string) *SeatNotAvailableError { return SeatNotAvailableError{message: message} } func (e *SeatNotAvailableError) Error() string { return e.message }该错误类型被 seat.go 的Seat.Book()与 concert_booking_system.go 的BookTickets复用作为“座位已售/已锁”的统一错误信号便于调用方做类型断言区分业务错误。ConcertTicketBookingSystem单例 并发控制的系统核心ConcertTicketBookingSystem是整个系统的中枢组件遵循Singleton 模式保证全局唯一实例管理演唱会与预订并提供添加演唱会、搜索演唱会、订票、取消预订等方法。Singleton 单例实现sync.OnceGo 实现见 concert_booking_system.gotype ConcertTicketBookingSystem struct { concerts map[string]*Concert bookings map[string]*Booking mu sync.Mutex } var ( instance *ConcertTicketBookingSystem once sync.Once ) func GetBookingSystem() *ConcertTicketBookingSystem { once.Do(func() { instance ConcertTicketBookingSystem{ concerts: make(map[string]*Concert), bookings: make(map[string]*Booking), } }) return instance }这是 Go 中线程安全单例的标准写法sync.Once保证初始化函数只执行一次无论多少 goroutine 并发调用GetBookingSystem()都会拿到同一个实例。相比加锁判空的懒加载写法sync.Once更简洁且天然并发安全。同时系统内部的concerts与bookings使用map sync.Mutex保护所有读写方法都先加锁再操作保证并发安全。添加与搜索演唱会func (bs *ConcertTicketBookingSystem) AddConcert(concert *Concert) { bs.mu.Lock() defer bs.mu.Unlock() bs.concerts[concert.ID] concert } func (bs *ConcertTicketBookingSystem) GetConcert(concertID string) *Concert { bs.mu.Lock() defer bs.mu.Unlock() return bs.concerts[concertID] } func (bs *ConcertTicketBookingSystem) SearchConcerts(artist, venue string, dateTime time.Time) []*Concert { bs.mu.Lock() defer bs.mu.Unlock() var results []*Concert for _, concert : range bs.concerts { if concert.Artist artist concert.Venue venue concert.DateTime.Equal(dateTime) { results append(results, concert) } } return results }AddConcert以演唱会 ID 为 key 存入 mapGetConcert按 ID 精确取回SearchConcerts实现需求 2 的多条件组合搜索艺人、场馆、日期时间三个条件同时命中才返回。注意time.Time的比较使用了Equal方法Go 中对time.Time不可靠。BookTickets防超卖的核心流程BookTickets是系统的重头戏完整实现了需求 3 与需求 4选择座位、购买门票、并发防重func (bs *ConcertTicketBookingSystem) BookTickets(user *User, concert *Concert, seats []*Seat) (*Booking, error) { bs.mu.Lock() defer bs.mu.Unlock() // 1. 校验所有座位当前是否可售 for _, seat : range seats { if seat.GetStatus() ! StatusAvailable { return nil, NewSeatNotAvailableError(fmt.Sprintf(Seat %s is not available, seat.SeatNumber)) } } // 2. 逐个预订座位任一失败则回滚已预订的座位 for _, seat : range seats { if err : seat.Book(); err ! nil { // 回滚释放本次已成功预订的前序座位 for _, s : range seats { if s seat { break } s.Release() } return nil, err } } // 3. 生成预订 ID基于纳秒时间戳避免冲突 bookingID : fmt.Sprintf(BKG-%d, time.Now().UnixNano()) booking : NewBooking(bookingID, user, concert, seats) // 4. 支付当前为 mock 实现 bs.processPayment(booking) // 5. 确认预订并入库 booking.ConfirmBooking() bs.bookings[bookingID] booking fmt.Printf(Booking %s - %d seats booked\n, booking.ID, len(booking.Seats)) return booking, nil }这个方法体现了“先校验 → 再抢占 → 失败回滚 → 确认入库”的经典事务式流程层层分析全量预校验先遍历所有目标座位确认StatusAvailable任一座位不可用立即返回SeatNotAvailableError不做任何写入逐个抢占 原子性回滚真正写状态时逐个调用seat.Book()若中途某个座位预订失败则释放本次已成功预订的前序座位保证一批座位要么全部预订成功、要么全部释放避免出现“订了 3 个座位只成功 2 个”的脏状态——这正是需求 4“避免 double-booking”在单请求粒度上的体现预订 ID 生成使用纳秒时间戳time.Now().UnixNano()保证同一批次内 ID 唯一支付占位processPayment当前为空实现mock对应需求 6 的支付处理留待接入真实支付网关确认并入库状态置为BookingStatusConfirmed后写入bookingsmap。并发防超卖的锁粒度设计需求 4 的“并发请求避免重复预订”在本实现中是双层锁协作完成的系统级锁bs.muBookTickets全程持有ConcertTicketBookingSystem.mu保证同一时刻只有一个订票请求能进入座位抢占流程——这是粗粒度的互斥屏障从源头杜绝两个用户同时订同一批座位的竞态窗口座位级锁seat.museat.go 的Book/Release/GetStatus各自持有Seat.mu保证座位状态读写的原子性即使未来去掉系统级锁、改为细粒度锁座预留场景单个座位的状态切换依然是安全的。Seat.Book()的实现是防重的最小闭环func (s *Seat) Book() error { s.mu.Lock() defer s.mu.Unlock() if s.status ! StatusAvailable { return NewSeatNotAvailableError(Seat is already booked or reserved) } s.status StatusBooked return nil }先锁 → 再判断状态 → 只有available才能翻转为booked否则返回业务错误。这个“加锁-校验-更新”的三角关系正是面试中回答“如何防止座位超卖”时最核心的代码级论据。CancelBooking取消与座位回收func (bs *ConcertTicketBookingSystem) CancelBooking(bookingID string) { bs.mu.Lock() defer bs.mu.Unlock() if booking, exists : bs.bookings[bookingID]; exists { booking.CancelBooking() delete(bs.bookings, bookingID) fmt.Printf(Booking %s cancelled\n, bookingID) } }取消流程同样加锁保护从bookings取出订单 → 调用booking.CancelBooking()内部释放全部座位→ 从 map 删除。释放后的座位回到StatusAvailable可被后续用户重新预订。端到端流程走查从建演唱会到取消重购demo 文件 中的Run()函数把上述所有组件串成了一条完整的用户旅程非常适合面试表述与本地验证func Run() { bookingSystem : GetBookingSystem() // 创建两场演唱会C001 有 100 个座位C002 有 50 个座位 concert1Seats : GenerateSeats(100) concert1 : NewConcert(C001, Artist 1, Venue 1, time.Now().Add(30*24*time.Hour), concert1Seats) bookingSystem.AddConcert(concert1) concert2Seats : GenerateSeats(50) concert2 : NewConcert(C002, Artist 2, Venue 2, time.Now().Add(60*24*time.Hour), concert2Seats) bookingSystem.AddConcert(concert2) // 创建用户 user1 : NewUser(U001, John Doe, johnexample.com) user2 : NewUser(U002, Jane Smith, janeexample.com) // 按艺人场馆时间搜索 searchResults : bookingSystem.SearchConcerts(Artist 1, Venue 1, time.Now().Add(30*24*time.Hour)) fmt.Println(Search Results:) for _, concert : range searchResults { fmt.Printf(Concert: %s at %s\n, concert.Artist, concert.Venue) } // user1 预订 C001 前 3 个座位 selectedSeats1 : concert1.Seats[:3] booking1, err : bookingSystem.BookTickets(user1, concert1, selectedSeats1) if err ! nil { fmt.Printf(Booking error: %v\n, err) } // user2 预订 C002 前 2 个座位 selectedSeats2 : concert2.Seats[:2] booking2, err : bookingSystem.BookTickets(user2, concert2, selectedSeats2) if err ! nil { fmt.Printf(Booking error: %v\n, err) } if booking2 ! nil { fmt.Printf(Booking Successful\n) } // user1 取消 C001 的预订 → 座位释放 if booking1 ! nil { bookingSystem.CancelBooking(booking1.ID) } // user2 随后预订 C001 第 4、5 个座位注意前 3 个座位已被 user1 预订过又释放此处选的是后续座位 selectedSeats3 : concert1.Seats[3:5] booking3, err : bookingSystem.BookTickets(user2, concert1, selectedSeats3) if err ! nil { fmt.Printf(Booking error: %v\n, err) } if booking3 ! nil { fmt.Printf(Booking Successful\n) } }整个流程覆盖了需求的完整链路创建演唱会 → 生成座位与定价 → 创建用户 → 条件搜索 → 多座位批量预订 → 取消预订 → 再次预订。demo 刻意设计了两处有代表性的操作booking1成功后立即被取消随后booking3预订了concert1.Seats[3:5]第 4、5 个座位避开已被释放的前 3 个座位演示了座位区间的可操作性多次BookTickets与CancelBooking交错出现可直观验证“预订-释放-再预订”的状态流转与错误处理分支。运行方式与依赖环境该实现位于 Go 模块 solutions/golang模块名github.com/ashishps1/awesome-low-level-design/solutions/golangGo 版本1.23.2。运行方式如下在仓库根目录进入 Go 解决方案目录并运行整个模块的入口 main.go入口中已预置concertbookingsystem.Run()调用默认被注释取消注释即可然后执行cd solutions/golang go run main.go若希望独立运行本系统的 demo也可以将concertticketbookingsystem目录下的源码复制为独立main包后直接执行go run .需要注意当前 demo 的Run()定义于包concertbookingsystem内直接以包方式调用concertbookingsystem.Run()比独立可执行更贴合仓库现有的多项目组织结构。该实现无第三方依赖仅依赖 Go 标准库sync、time、fmtgo.mod已锁定 Go 1.23.2 以上版本即可编译运行。设计亮点总结与待扩展点值得在面试中强调的设计亮点双层锁并发模型系统级sync.Mutex保证订票流程整体串行座位级sync.Mutex保证单座位状态切换原子性二者结合从架构层面回答“如何避免 double-booking”批量操作的原子性回滚BookTickets中任一座位失败即释放前序座位杜绝半成功状态是“预订事务”的代码级落地状态机约束Booking的Confirm/Cancel均校验当前状态防止非法状态迁移座位状态由available → booked单向流转并由错误类型兜底Singleton 与并发安全sync.Once单例 map 全量加锁全局状态一致且线程安全封装的 Go 惯例座位status字段私有化仅暴露GetStatus()避免外部绕过锁直接改状态。原需求中尚未实现、可继续扩展的点对照原文档需求列表以下几点在当前 Go 实现中仍是开放扩展项代码中均有明确标注可作后续迭代方向需求 6 安全支付processPayment 目前为空 mock可接入真实支付网关并在支付失败时回滚座位需求 7 邮件/SMS 确认booking.go 中ConfirmBooking与CancelBooking的发送通知逻辑均为TODO需求 8 等待列表SeatStatus中已预留StatusReserved枚举值但等待列表队列逻辑尚未实现可在此基础上扩展“锁座 排队补位”需求 5 公平性当前实现通过“先到先得 全量加锁”保证公平若引入预留reserved状态则需进一步设计锁座超时释放策略。与其他实现横向对照本题在仓库中有多语言实现可供对照阅读Java 实现位于 solutions/java/src/concertticketbookingsystem/、Python 实现位于 solutions/python/concertticketbookingsystem/、C 实现位于 solutions/cpp/concertticketbookingsystem/、C# 实现位于 solutions/csharp/concertticketbookingsystem/。对照阅读时重点观察各语言如何表达“并发锁”与“状态枚举”可以加深对 LLD 通用建模思路的理解。结语演唱会票务预订系统是低层设计面试的高频题其精髓在于在简单实体模型之上用并发控制解决真实的业务一致性难题。本文基于awesome-low-level-design仓库的 Go 实现从需求、类图、枚举、单例、锁与回滚、端到端 demo 六个层面完成了完整拆解。你可以直接运行 demo 验证整套流程也可以在TODO标注处继续扩展支付、通知与等待列表把这道题打磨成面试中最有把握的代表作。赞分享示例工程【免费下载链接】awesome-low-level-designLearn Low Level Design (LLD) and prepare for interviews using free resources.项目地址https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design点击查看免费下载相关推荐演唱会票务预订系统Concert Ticket Booking SystemC 实现从需求分析到源码级设计详解演唱会票务预订系统Concert Ticket Booking SystemC 实现从需求分析到源码级设计详解 本篇技术指南基于 awesome lo示例工程电影票预订系统Movie Ticket Booking SystemC 低层设计实战类建模、座位锁定与并发安全电影票预订系统Movie Ticket Booking SystemC 低层设计实战类建模、座位锁定与并发安全 本文基于 awesome low le示例工程Movie Ticket Booking SystemBookMyShow 式低层设计全解析需求拆解、类图建模与并发座位预订实战Movie Ticket Booking SystemBookMyShow 式低层设计全解析需求拆解、类图建模与并发座位预订实战 本文以开源仓库 awes示例工程上一篇[1.2.0] - 2025-10-28下一篇Librosa深度解析Python音频信号处理与音乐分析实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表