GoLang之动态保活Worker工作池设计

GoLang之动态保活Worker工作池设计

一、我们如何知道一个Goroutine已经死亡?

实际上,Go语言并没有给我们暴露如何知道一个Goroutine是否存在的接口,如果要证明一个Go是否存在,可以在子Goroutine的业务中,定期写向一个keep live的Channel,然后主Goroutine来发现当前子Go的状态。Go语言在对于Go和Go之间没有像进程和线程一样有强烈的父子、兄弟等关系,每个Go实际上对于调度器都是一个独立的,平等的执行流程。

PS: 如果你是监控子线程、子进程的死亡状态,就没有这么简单了,这里也要感谢go的调度器给我们提供的方便,我们既然用Go,就要基于Go的调度器来实现该模式。

那么,我们如何做到一个Goroutine已经死亡了呢?

子Goroutine

可以通过给一个被监控的Goroutine添加一个defer ,然后recover() 捕获到当前Goroutine的异常状态,最后给主Goroutine发送一个死亡信号,通过Channel

主Goroutine

主Goroutine上,从这个Channel读取内容,当读到内容时,就重启这个子Goroutine,当然主Goroutine需要记录子GoroutineID,这样也就可以针对性的启动了。

二、代码实现

我们这里以一个工作池的场景来对上述方式进行实现。

WorkerManager作为主Goroutine, worker作为子Goroutine

WorkerManager

type WorkerManager struct {
   //用来监控Worker是否已经死亡的缓冲Channel
   workerChan chan *worker
   // 一共要监控的worker数量
   nWorkers int
}

//创建一个WorkerManager对象
func NewWorkerManager(nworkers int) *WorkerManager {
   return &WorkerManager{
      nWorkers:nworkers,
      workerChan: make(chan *worker, nworkers),
   }
}

//启动worker池,并为每个Worker分配一个ID,让每个Worker进行工作
func (wm *WorkerManager)StartWorkerPool() {
   //开启一定数量的Worker
   for i := 0; i < wm.nWorkers; i++ {
      i := i
      wk := &worker{id: i}
      go wk.work(wm.workerChan)
   }

  //启动保活监控
   wm.KeepLiveWorkers()
}

//保活监控workers
func (wm *WorkerManager) KeepLiveWorkers() {
   //如果有worker已经死亡 workChan会得到具体死亡的worker然后 打出异常,然后重启
   for wk := range wm.workerChan {
      // log the error
      fmt.Printf("Worker %d stopped with err: [%v] \n", wk.id, wk.err)
      // reset err
      wk.err = nil
      // 当前这个wk已经死亡了,需要重新启动他的业务
      go wk.work(wm.workerChan)
   }
}

worker

type worker struct {
   id  int
   err error
}

func (wk *worker) work(workerChan chan<- *worker) (err error) {
   // 任何Goroutine只要异常退出或者正常退出 都会调用defer 函数,所以在defer中想WorkerManager的WorkChan发送通知
   defer func() {
      //捕获异常信息,防止panic直接退出
      if r := recover(); r != nil {
         if err, ok := r.(error); ok {
            wk.err = err
         } else {
            wk.err = fmt.Errorf("Panic happened with [%v]", r)
         }
      } else {
         wk.err = err
      }
 
     //通知 主 Goroutine,当前子Goroutine已经死亡
      workerChan <- wk
   }()

   // do something
   fmt.Println("Start Worker...ID = ", wk.id)

   // 每个worker睡眠一定时间之后,panic退出或者 Goexit()退出
   for i := 0; i < 5; i++ {
      time.Sleep(time.Second*1)
   }

   panic("worker panic..")
   //runtime.Goexit()

   return err
}

三、测试

main

func main() {
   wm := NewWorkerManager(10)

   wm.StartWorkerPool()
}

结果:

$ go run workmanager.go 
Start Worker...ID =  2
Start Worker...ID =  1
Start Worker...ID =  3
Start Worker...ID =  4
Start Worker...ID =  7
Start Worker...ID =  6
Start Worker...ID =  8
Start Worker...ID =  9
Start Worker...ID =  5
Start Worker...ID =  0
Worker 9 stopped with err: [Panic happened with [worker panic..]] 
Worker 1 stopped with err: [Panic happened with [worker panic..]] 
Worker 0 stopped with err: [Panic happened with [worker panic..]] 
Start Worker...ID =  9
Start Worker...ID =  1
Worker 2 stopped with err: [Panic happened with [worker panic..]] 
Worker 5 stopped with err: [Panic happened with [worker panic..]] 
Worker 4 stopped with err: [Panic happened with [worker panic..]] 
Start Worker...ID =  0
Start Worker...ID =  2
Start Worker...ID =  4
Start Worker...ID =  5
Worker 7 stopped with err: [Panic happened with [worker panic..]] 
Worker 8 stopped with err: [Panic happened with [worker panic..]] 
Worker 6 stopped with err: [Panic happened with [worker panic..]] 
Worker 3 stopped with err: [Panic happened with [worker panic..]] 
Start Worker...ID =  3
Start Worker...ID =  6
Start Worker...ID =  8
Start Worker...ID =  7
...
...

我们会发现,无论子Goroutine是因为 panic()异常退出,还是Goexit()退出,都会被主Goroutine监听到并且重启。主要我们就能够起到保活的功能了. 当然如果线程死亡?进程死亡?我们如何保证? 大家不用担心,我们用Go开发实际上是基于Go的调度器来开发的,进程、线程级别的死亡,会导致调度器死亡,那么我们的全部基础框架都将会塌陷。那么就要看线程、进程如何保活啦,不在我们Go开发的范畴之内了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值