在Golang中實現Actor模型的原始碼 - Gaurav

banq發表於2022-02-25

Actor模型是一種這樣的程式設計結構,它對大量獨立作業進行建模,以任何順序進行處理,無需鎖同步。如Java中Play!框架。

在本文中,我將描述如何在 golang 中實現一個原始的 Actor 模型。我們將利用 golang 提供的工具進行併發處理——goroutine、通道和等待組。

Actor 有一個任務佇列和一個監聽任務佇列並執行任務的 goroutine。

這裡 A 是一個阻塞任務佇列並繼續執行佇列中的任務的 goroutine。Actor 的介面如下所示:

type Actor interface {
   AddTask(task Task)
   Start()
   Stop()
}

task在actor中執行。它是具有Execute 方法的給定介面的實現。任何可以通過呼叫 Execute 來執行的東西。

task是我們需要做的工作的業務實現。

 

actor系統介面:

type ActorSystem interface {
   Run()
   SubmitTask(task Task) 
   Shutdown(shutdownWG *sync.WaitGroup)
}

task使用SubmitTask方法提交給ActorSystem。一個任務分配器將每個任務task分配給一個行為體。每個行動者也有一個小佇列,在其中緩衝任務task並逐一執行。

 

ActorSystem

type ActorSystem struct {
    name           string
    assigner        entities.Actor
    wg             *sync.WaitGroup
    tracker        *tracker.Tracker
}

func (system *ActorSystem) Run() {
    log.Debug("actor system %s started \n", system.name)
  // start the assigner in seprate go routine
    go system.assigner.Start()
}

func (system *ActorSystem) SubmitTask(task entities.Task) error {
  // adding submitted task to assigner
    return system.assigner.AddTask(task)
}

func (system *ActorSystem) Shutdown(wg *sync.WaitGroup) {
    defer wg.Done()
    system.assigner.Stop()
    system.wg.Wait()
    system.tracker.Shutdown()
    log.Debug("actor system: %s shutdown completed ", system.name)
}

當ActorSystem啟動時,它啟動一個taskAssigner actor。通過在這個actor上呼叫 AddTask 方法,將每個傳入Task系統的資料新增到 taskAssigner actor。

task使用SubmitTask方法提交給ActorSystem。我們通過呼叫AddTask方法將每個收到的任務放到taskAssigner中。

在Shutdown 時,它關閉任務通道,阻止任何新進入的任務,等待所有收到的任務被分配給行為者。然後,它對每個actor呼叫 "停止",並等待它們完成。

 

Task Assigner

我們把每個傳入的任務放在一個通道tasks中,taskAssigner和Task在一個Actor的內部佇列中。

type AssignerActor struct {
    name     string
    closeSig chan bool
    tasks    chan entities.Task
    assignerIndex int
    tracker *tracker.Tracker
    scalar  *autoScalar
    *TaskActorPool
    *Config
}

func (assigner *AssignerActor) AddTask(task entities.Task) error{
    if len(assigner.tasks) >= assignerQueueSize {
        assigner.tracker.GetTrackerChan() <- tracker.CreateCounterTrack(tracker.Task, tracker.Rejected)
        return errors.New("task queue is full")
    }
    // task getting added to assigner actor channel 
    assigner.tasks <- task
    assigner.tracker.GetTrackerChan() <- tracker.CreateCounterTrack(tracker.Task, tracker.Submitted)
    return nil
}

taskAssigner內部處理任務通道,並通過對其呼叫AddTask,將任務路由到池中的一個任務執行者。

func (assigner *AssignerActor) Start() {
    poolStarted := make(chan bool)
    assigner.scalar = GetAutoScaler(assigner, poolStarted)
    <- poolStarted
        // will loop forever till tasks channel is closed
    for task := range assigner.tasks {
        for {
            assigner.poolLock.Lock()
            assigner.assignerIndex = assigner.assignerIndex % len(assigner.pool)
            actor := assigner.pool[assigner.assignerIndex]
            assigner.assignerIndex += 1
            assigner.poolLock.Unlock()
            // assigning task to a task actor from pool
            err := actor.AddTask(task)
            if err == nil {
                break
            }
        }
    }
    assigner.closeSig <- true
}

autoScalar持續關注任務中的專案數量,並增加或減少任務actor池的大小。

// auto scalar is part of task assigner actor
// It scales task actor pool based on queue len size
type autoScalar struct {
    *AssignerActor
    lastActorId int
    closingSig  chan bool
    closedSig   chan bool
}

func(scalar *autoScalar) run(poolStarted chan bool) {
    log.Debug("running auto scalar with min actor")
        // provision starting actors
    scalar.provisionActors(scalar.Config.MinActor)
    // waiting for scalar to start task actors
    poolStarted <- true
    completed := false
        // loops till it gets a closing signal from task assigner
    for !completed {
        select {
        case <- scalar.closingSig:
            completed = true
        case <-time.After(100 * time.Millisecond):
            if scalar.QueueSize() > scalar.UpscaleQueueSize && len(scalar.pool) < scalar.MaxActor {
                scalar.provisionActors(1)

            } else if scalar.QueueSize() < scalar.DownscaleQueueSize && len(scalar.pool) > scalar.MinActor {
                scalar.deprovisionActors(1)
            }
        }
    }
  // when it comes out, it closes all task actors in pool
    scalar.deprovisionActors(len(scalar.pool))
    log.Debug("scalar exited")
    scalar.closedSig <- true
}

 

Task Actor

它也是一個Actor,它的工作是執行任務,這些任務被新增到它的通道任務中,類似於分配者assigner的Actor。

type TaskActor struct {
id int
closeSig chan bool
wg *sync.WaitGroup
tasks chan entities.Task
tracker *tracker.Tracker
}
// add task only if channel has space
func (a *TaskActor) AddTask(task entities.Task) error {
if len(a.tasks) >= taskQueueSize {
return errors.New("filled queue")
}
// task added to channel
a.tasks <- task
return nil
}
func (a *TaskActor) Start() {
defer a.wg.Done()
a.wg.Add(1)
log.Debug("starting actor :%d", a.id)
// forever loop on tasks channel till channel is closed
for task := range a.tasks{
task.Execute()
a.tracker.GetTrackerChan() <- tracker.CreateCounterTrack(tracker.Task, tracker.Completed)
}
log.Debug("stopped actor :%d", a.id)
a.closeSig <- true
}
func (a *TaskActor) Stop() {
// closing task channel
close (a.tasks)
<- a.closeSig
}
 
 

原始碼:Github

 

相關文章