应用场景
电商系统中,用户下单是一个核心业务流程。这个过程涉及到用户选择商品、填写订单信息、生成订单、进行库存检查、生成订单号、计算总价、记录订单日志等多个步骤。如果这些步骤是串行执行的,可能会导致用户等待时间过长,降低用户体验。因此,我们可以使用多线程来并行处理这些步骤,提高系统性能。
单线程
单线程下单过程的设计是按照一定的顺序依次执行每个步骤,每个步骤都依赖于前一个步骤的结果。 这种设计方式通常适用于低并发情况下或者系统规模较小的情况,因为在高并发场景下,单线程可能会导致用户等待时间过长,降低了系统的响应性能。
多线程
客户发起下单请求: 当客户发起下单请求时,系统接收到请求并为其创建一个新的线程,这个线程将负责处理该订单的所有任务。
并行执行步骤: 创建的线程会并行执行下单流程的各个步骤,这些步骤包括但不限于:
- 商品信息查询:一个线程负责查询商品的详细信息,例如商品名称和价格。
- 库存检查:另一个线程负责检查商品库存是否足够。
- 生成订单号:一个线程负责生成唯一的订单标识符。
- 计算总价:一个线程负责计算订单的总价。
- 记录订单日志:最后一个线程负责将订单信息记录到系统的日志中。
这些线程可以同时运行,互不干扰。每个线程独立执行自己的任务。
等待任务完成: 当所有并行执行的线程完成各自的任务后,系统会等待这些线程的结束。
返回响应: 一旦所有步骤都完成,系统将合并所有线程的结果,并向客户返回订单确认或失败的响应,包括订单号和总价等信息。
多线程设计的优势在于能够充分利用多核处理器和系统资源,同时提高系统的并发处理能力。这对于处理大量并发下单请求时尤为有益,因为每个订单可以在不等待前一个订单完成的情况下同时进行处理。这提高了系统的响应速度和吞吐量。
代码逻辑
单线程
这是一个单线程版本的示例,所有的订单处理步骤都在同一个线程中执行,每个步骤都会导致阻塞,从而增加了总的处理时间。
go
func getProductInfo(productID int) (string, float64) {
// 模拟商品信息查询
}
func checkInventory(productID int, quantity int) bool {
// 模拟库存检查
}
func generateOrderID(customerID int) string {
// 模拟订单号生成
}
func calculateTotalPrice(productPrice float64, quantity int) float64 {
// 模拟总价计算
}
func logOrder(orderID string, totalPrice float64) {
// 模拟订单日志记录
}
func placeOrder(customerID int, productID int, quantity int) {
// 商品信息查询,这里调用以上全部的微服务
}
func main() {
customerID := 123
productID := 456
quantity := 2
// 用户下订单
placeOrder(customerID, productID, quantity)
}
多线程
这是一个多线程版本的示例,通过使用Goroutines和sync.WaitGroup
,各个订单处理步骤可以并行执行,从而提高了系统的性能和响应速度。
go
func getProductInfo(productID int) (string, float64) {
// 模拟商品信息查询
}
func checkInventory(productID int, quantity int) bool {
// 模拟库存检查
}
func generateOrderID(customerID int) string {
// 模拟订单号生成
}
func calculateTotalPrice(productPrice float64, quantity int) float64 {
// 模拟总价计算
}
func logOrder(orderID string, totalPrice float64) {
// 模拟订单日志记录
}
func placeOrder(customerID int, productID int, quantity int) {
var wg sync.WaitGroup
// 商品信息查询
var productInfo string
var productPrice float64
wg.Add(1)
go func() {
defer wg.Done()
productInfo, productPrice = getProductInfo(productID)
}()
// 库存检查
var hasInventory bool
wg.Add(1)
go func() {
defer wg.Done()
hasInventory = checkInventory(productID, quantity)
}()
wg.Wait() // 等待商品信息查询和库存检查完成
if !hasInventory {
fmt.Println("库存不足,无法下单")
return
}
// 生成订单号
orderID := generateOrderID(customerID)
// 计算总价
totalPrice := calculateTotalPrice(productPrice, quantity)
// 记录订单日志
wg.Add(1)
go func() {
defer wg.Done()
logOrder(orderID, totalPrice)
}()
wg.Wait() // 等待订单日志记录完成
fmt.Printf("订单已生成:%s\n", orderID)
fmt.Printf("总价: $%.2f\n", totalPrice)
}
func main() {
customerID := 123
productID := 456
quantity := 2
// 用户下订单
placeOrder(customerID, productID, quantity)
}
批量处理接口策略
入参: goods id、number(数量)
进行SQL操作: update ...(单个SQL)
入参: list (goods id、number(数量))
进行SQL操作: 批量化
后端优化成批量处理接口 (入参: ist(ison)-- 返回参数也是list(ison))
原来接口调用100次,现在调用1次 !!!
需要加入一些技术:
1.缓冲机制: MQ 、队列来接收单个请求
- 定时任务 (Go语言的标准库中包含了
time
包,您可以使用它来创建定时任务)
MQ最好要用消息中间件:
消息中间件(Message Queue)如Kafka和RocketMQ是专门用于处理消息传递和异步通信的系统。它们具有高可靠性、持久性、伸缩性和高吞吐量等特点,适用于解决分布式系统中的通信和数据传输问题。
详细解释:
应用宕机了,MQ中有消息: 使用消息中间件时,消息通常是持久化的,这意味着即使应用宕机,消息不会丢失。一旦应用重新启动,它可以继续消费尚未处理的消息,确保数据不丢失。
如果MQ宕机:
- 主从节点: Kafka和RocketMQ等消息中间件通常支持主从架构,包括主节点和多个从节点。主节点负责接收消息,而从节点负责备份数据。如果主节点宕机,可以立即切换到一个从节点,确保消息的持续传递。
- RocketMQ多主多从节点: RocketMQ支持多主多从的高可用架构。这意味着即使某个主节点宕机,其他主节点仍然可以继续服务。
使用消息中间件有助于确保消息的可靠性和持久性,以及系统的高可用性。这些特性使得消息中间件在构建分布式系统和处理异步通信时非常有用。
RocketMQ原理和使用可以参考我的博客(非常详细!!! )GO微服务中使用RocketMQ消息队列系统实现分布式事务