如果controller方法中启动了一个go协程, 如何保证该协程和方法的生命周期一致?

在Go语言中,如果在控制器(controller)的方法中启动了一个goroutine,而你希望这个goroutine的生命周期与调用它的方法保持一致,确保在方法结束时goroutine也能正确地结束或清理资源,可以采用以下几种方法:

  1. 使用Context(推荐)
    Go的标准库提供了context包,它允许通过传递一个context来控制goroutine的生命周期。当外部环境(如HTTP请求)结束时,可以取消context,使得goroutine能够感知并退出。

    func myControllerMethod(w http.ResponseWriter, r *http.Request) {
        ctx, cancel := context.WithCancel(r.Context())
        defer cancel()
    
        go func() {
            select {
            case <-ctx.Done():
                // 当外部context被取消时,此处的goroutine会停止运行
                return
            default:
                // 这里是协程执行的逻辑
                doSomething(ctx)
            }
        }()
    
        // ... controller method 的其他逻辑 ...
    }
    
    func doSomething(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                return
            default:
                // 业务逻辑处理
                // ...
            }
        }
    }
  2. 使用sync.WaitGroup
    使用sync.WaitGroup可以确保在goroutine完成工作后,方法才能结束。

    import (
        "sync"
    )
    
    func myControllerMethod(w http.ResponseWriter, r *http.Request) {
        var wg sync.WaitGroup
        wg.Add(1)
    
        go func() {
            defer wg.Done()
    
            // 协程执行的逻辑
            doSomething()
    
            // 如果有必要,还可以在这里执行清理操作
        }()
    
        // 在方法结束前等待所有goroutine完成
        wg.Wait()
    
        // ... controller method 的其他逻辑 ...
    }
    
    func doSomething() {
        // 业务逻辑处理
        // ...
    }
  3. 使用channel关闭信号
    通过channel传递一个关闭信号,让goroutine监听这个信号并适时退出。

    func myControllerMethod(w http.ResponseWriter, r *http.Request) {
        stopCh := make(chan struct{})
        defer close(stopCh)
    
        go func() {
            for {
                select {
                case <-stopCh:
                    // 收到关闭信号时退出goroutine
                    return
                default:
                    // 协程执行的逻辑
                    doSomething()
                }
            }
        }()
    
        // ... controller method 的其他逻辑 ...
    }
    
    func doSomething() {
        // 业务逻辑处理
        // ...
    }

综合来看,使用context是最符合现代Go实践的方式,因为它不仅能够控制goroutine的生命周期,还能够携带请求级的信息,并在取消时释放资源。

分库分表? 水平分表, 垂直分表

分库分表是数据库优化的一种手段,目的是为了应对大规模数据量和高并发场景下的性能瓶颈。随着数据量的增长,单个数据库或表可能因数据量过大、索引过大、查询速度慢等原因导致性能下降,这时就需要对数据库进行拆分。

垂直分库/垂直分表
垂直拆分(Vertical Partitioning)是将一个大表按照列进行分割,即将一张表中不同特性的字段分为不同的表,或者将不同业务逻辑相关的表分配到不同的数据库中。这样做的目的是将数据量较大的列或访问频率不同的列分开,减少单表宽度,优化查询性能和存储效率。

例如,假设有一张用户表,包含用户基本信息(如ID、姓名、性别)、订单信息(如订单ID、购买物品)和社交信息(如好友列表、动态)。垂直分表后,可以将用户基本信息、订单信息、社交信息分别存入不同的表,甚至不同的数据库中。

水平分库/水平分表
水平拆分(Horizontal Partitioning)是将一个大表按照行进行分割,将表中的数据按照某种规则(如哈希、范围、列表等)分散到多个表或多个数据库中。每个分表包含原表的部分行,但保留相同的列结构。

例如,一个用户表,按照用户ID的范围或者用户ID的哈希值进行拆分,不同的用户数据分布在不同的表中。这样可以将数据分散到多个物理存储单元上,有效缓解单表数据量过大带来的性能问题,同时也便于做分布式集群扩展。

总结来说,垂直分库分表关注的是数据的维度,旨在减少单表宽度和优化存储结构;水平分库分表关注的是数据的规模,旨在分摊大量数据的访问压力,适应分布式计算和存储环境。

最后编辑: kuteng  文档更新时间: 2024-04-02 09:53   作者:kuteng