Golang实现基于DAG图编排的插件系统

2,730 阅读2分钟

import (
	"fmt"
	"strconv"
	"sync"
	"time"
)

var Context = map[int]interface{}{}

type DependOnParams struct {
	Times int
	Channel chan struct{}
}
var Mutex sync.Mutex
//var MoreDependOn = map[int]DependOnParams{}
//var Group sync.WaitGroup
var MoreDependOn = sync.Map{}

//有向图
type Graph struct {
	vertex []Plugin         //顶点
	list   map[int][]Plugin //连接表边
}

type Plugin struct {
	RelayOn map[int]interface{}
	Name    string
	Index   int
	DependOn *DependOnParams
}

func (p Plugin)Do() interface{}{
	fmt.Println(p)
	return p.Index + 100000
}

//添加边
func (g *Graph) addVertex(t Plugin, s Plugin) {
	g.list[t.Index] = Push(g.list[t.Index], s)
}

//取出切片第一个
func Pop(list []Plugin) (Plugin, []Plugin) {
	if len(list) > 0 {
		a := list[0]
		b := list[1:]
		return a, b
	} else {
		return Plugin{}, list
	}
}

//推入切片
func Push(list []Plugin, value Plugin) []Plugin {
	result := append(list, value)
	return result
}

//添加边
func (g *Graph) KhanSort() {
	var inDegree = make(map[int]int)
	var queue []Plugin
	for i := 0; i < len(g.vertex); i++ {
		for _, m := range g.list[g.vertex[i].Index] {
			inDegree[m.Index]++
		}
	}
	for i := 0; i < len(g.vertex); i++ {
		if inDegree[g.vertex[i].Index] == 0 {
			queue = Push(queue, g.vertex[i])
		}
	}
	for len(queue) > 0 {
		var now Plugin
		now, queue = Pop(queue)


		// 模拟插件运行,并将结果进行缓存
		// 同时去修改所有对该插件的依赖
		go func() {
			fmt.Println("---->" + strconv.Itoa(now.Do().(int)))
			Context[now.Index] = now.Do()

			for _,k:= range g.list[now.Index]{
				inDegree[k.Index]--
			}
		}()




		for _, k := range g.list[now.Index] {
			inDegree[k.Index]--

			// 对依赖进行注入
			// DependencyInjection(k,now)
			if inDegree[k.Index] == 0 {
				queue = Push(queue, k)
			}
		}
	}

	time.Sleep(2* time.Minute)
}

// 采用另一种方案:每一个插件开启一个线程,同时进行阻塞等待,
// 当所有的依赖都执行完之后才执行方法
func (g *Graph) KhanExecute() {
	for i := 1; i < len(g.vertex); i++ {
		for _, m := range g.list[g.vertex[i].Index] {
			m.DependOn.Times++
			//result,_ := MoreDependOn.Load(m.Index)
			//if result == nil {
			//	MoreDependOn.Store(m.Index,&DependOnParams{
			//		Times:   1,
			//		Channel: make(chan struct{}),
			//	})
			//}else{
			//	depend := result.(*DependOnParams)
			//	depend.Times++
			//	MoreDependOn.Store(m.Index,depend)
			//}

			// 进行依赖的注册
			m.RelayOn[g.vertex[i].Index] = 1
		}
	}

	for i := 1;i < len(g.vertex);i++ {
		i := i
		go func() {
			// 进行依赖的阻塞
			//params,_ := MoreDependOn.Load(g.vertex[i].Index)
			//if params == nil{
			//	params = &DependOnParams{
			//		Times:   0,
			//		Channel: nil,
			//	}
			//}
			//times := params.(*DependOnParams)
			for j := 0;j < g.vertex[i].DependOn.Times;j++ {
				select {
				case <- g.vertex[i].DependOn.Channel:
				case <- time.After(time.Minute * 40):fmt.Println("超时了")
				}
			}
			// 进行依赖的注入
			DependencyInjection(g.vertex[i])
			result := g.vertex[i].Do()

			// 进行结果的缓存
			Context[g.vertex[i].Index] = result

			// 通知依赖它的插件进行依赖的更新
			for k := 0;k < len(g.list[g.vertex[i].Index]);k++ {
				g.list[g.vertex[i].Index][k].DependOn.Channel <- struct{}{}
			}
		}()
	}

	time.Sleep(2* time.Minute)
}

func DependencyInjection(plugin Plugin) {
	for index,_ := range plugin.RelayOn {
		plugin.RelayOn[index] = Context[index]
	}
}

// 下一步的目标就是改成多线程的
// 在执行的时候进行多线程
func main() {
	g := NewGraph(9)
	for i := 1;i < 9;i++ {
		if i != 5{
			g.vertex[i] = Plugin{Index: i, RelayOn: map[int]interface{}{},DependOn: &DependOnParams{Times: 0,Channel: make(chan struct{})}}
		}else {
			g.vertex[i] = Plugin{Index: 0, RelayOn: map[int]interface{}{},DependOn: &DependOnParams{Times: 0,Channel: make(chan struct{})}}
		}
	}

	g.addVertex(g.vertex[2], g.vertex[1])
	g.addVertex(g.vertex[3], g.vertex[1])
	g.addVertex(g.vertex[7], g.vertex[1])
	g.addVertex(g.vertex[4], g.vertex[2])
	g.addVertex(g.vertex[5], g.vertex[2])
	g.addVertex(g.vertex[8], g.vertex[7])
	g.KhanExecute()
}

//创建图
func NewGraph(v int) *Graph {
	g := new(Graph)
	g.vertex = make([]Plugin,v)
	g.list = map[int][]Plugin{}
	i := 0
	for i < v {
		g.list[i] = make([]Plugin, 0)
		i++
	}
	return g
}

本质就是进行拓扑排序,但是拓扑排序之后会将所有的入度为0的先放入队列,然后遍历这个队列,同时对会清理这个定点所有的依赖点的度;然后判断所依赖的点的入度是否为0,是的话就加入队列,直到队列为空。

插件调度的实质就是,在扫描完依赖关系之后,为每一个插件开启一个协程,该协程会根据他的依赖开启通道阻塞。在运行完成之后,首先将运行的结果缓存在上下文中,然后给依赖他的协程的通道中传入channel。

第二部分

在进行DAG的模拟实现之后,需要将这种技术运用于工作流的编排之中。首先根据业务需求对插件的操作进行抽象,插件的动作抽象包含:

  1. 依赖的个数

  2. 依赖通道(信号量)

  3. 依赖的数据

  4. 以来的数据类型

  5. 插件的定义

	Do(ctx *static.OptionalParams, relyOnParams ...interface{}) (interface{}, error)
	GetValue() chan *Value
	GetIndex() int
	On() *RelyOn
	GetResultType() string
	SetHandlerInfo(info *HandlerInfo)
	GetHandlerInfo() *HandlerInfo
	SetHandlerSystem(system *HandlerSystem)
	GetHandlerSystem() *HandlerSystem
}

type DoValue func(ctx *static.OptionalParams, relyOnParams ...interface{}) (interface{}, error)

type HandlerSystem struct {
	Executable bool
	Token      string
	System     func(value DoValue) DoValue
	Recover    func()
	logrus.Logger
}

type HandlerInfo struct {
	ID    int
	Rely  *RelyOn
	Value chan *Value
	Type  string
}

type RelyOn struct {
	Times        int
	Channel      chan struct{}
	RelyOnParams map[int]*Value
}

type Value struct {
	Type   string
	Result interface{}
	Err    chan error
}

扫描图

	scheduler := graph.TasksScheduler{
		PointTask:      make([]graph.Handler, 0, 0),
		AdjustTask:     make(map[int][]graph.Handler),
		HandlerContext: make(map[int]*graph.Value),
	}

	if a.Recall != nil {
		for i := 0; i < len(a.Recall); i++ {
			scheduler.PointTask = append(scheduler.PointTask, a.Recall[i])
		}
	}

	if a.ReSorter != nil {
		for i := 0; i < len(a.ReSorter); i++ {
			scheduler.PointTask = append(scheduler.PointTask, a.ReSorter[i])
		}
	}

	if a.Filter != nil {
		for i := 0; i < len(a.Filter); i++ {
			scheduler.PointTask = append(scheduler.PointTask, a.Filter[i])
		}
	}

	if a.Sorter != nil {
		for i := 0; i < len(a.Sorter); i++ {
			scheduler.PointTask = append(scheduler.PointTask, a.Sorter[i])
		}
	}

	if a.Extra != nil {
		for i := 0; i < len(a.Extra); i++ {
			scheduler.PointTask = append(scheduler.PointTask, a.Extra[i])
		}
	}

	for index, slice := range a.Config {
		RelyOns := make([]graph.Handler, 0, 0)
		for k := 0; k < len(slice); k++ {
			for j := 0; j < len(scheduler.PointTask); j++ {
				if slice[k] == scheduler.PointTask[j].GetIndex() {
					RelyOns = append(RelyOns, scheduler.PointTask[j])
				}
			}
		}

		scheduler.AdjustTask[index] = append(scheduler.AdjustTask[index], RelyOns...)
	}

	return &scheduler
}

执行DAG

	multipleHandlerResult := make(map[int]chan interface{}, len(g.PointTask))
	var ss sync.WaitGroup
	ss.Add(len(g.PointTask))
	for i := 0; i < len(g.PointTask); i++ {
		for _, m := range g.AdjustTask[g.PointTask[i].GetIndex()] {
			m.On().Times++
			// 依赖值的初始化

			if m.On().RelyOnParams == nil {
				m.On().RelyOnParams = make(map[int]*Value)
			}
			m.On().RelyOnParams[g.PointTask[i].GetIndex()] = &Value{}
		}
	}

	for i := 0; i < len(g.PointTask); i++ {
		if g.PointTask[i].On().Times > 0 {
			g.PointTask[i].On().Channel = make(chan struct{}, g.PointTask[i].On().Times)
		}
	}

	for i := 0; i < len(g.PointTask); i++ {
		i := i
		go func() {

			defer func() {
				if rec := recover(); rec != nil {
					SendSemaphoreToRelayed(g.AdjustTask, g.PointTask[i].GetIndex())
					fmt.Println(string(debug.Stack()))
				}
				ss.Done()
			}()

			// 进行依赖的阻塞
			DependencyBlocking(g.PointTask[i])

			// 进行依赖的注入
			relyOnParams := DependencyInjection(g.PointTask[i], g.HandlerContext)
			result, _ := g.PointTask[i].Do(ctx, relyOnParams...)
			if result != nil {
				ctx.GoodsIDs = result.([]int64)
			}
			fmt.Println(result)
			fmt.Println(g.PointTask[i].GetIndex())
			value := &Value{
				Type:   g.PointTask[i].GetResultType(),
				Result: result,
			}
			multipleHandlerResult[g.PointTask[i].GetIndex()] = make(chan interface{}, 1)
			multipleHandlerResult[g.PointTask[i].GetIndex()] <- result

			// 进行结果的缓存
			CacheResultToContext(g.HandlerContext, value, g.PointTask[i].GetIndex())

			// 给依赖它的插件发送信号量
			SendSemaphoreToRelayed(g.AdjustTask, g.PointTask[i].GetIndex())
		}()
	}

	ss.Wait()
	return multipleHandlerResult
}

func DependencyInjection(plugin Handler, Context map[int]*Value) []interface{} {
	relyOnParams := make([]interface{}, 0, 0)
	for index, _ := range plugin.On().RelyOnParams {
		if checkRelyOnParams(plugin.On().RelyOnParams[index].Type, Context[index].Type) {
			relyOnParams = append(relyOnParams, Context[index])
		}
	}

	return relyOnParams
}

func SendSemaphoreToRelayed(adjust map[int][]Handler, curPluginIndex int) {
	for k := 0; k < len(adjust[curPluginIndex]); k++ {
		adjust[curPluginIndex][k].On().Channel <- struct{}{}
	}
}

func CacheResultToContext(ctx map[int]*Value, result *Value, index int) {
	ctx[index] = result
}

func DependencyBlocking(curPlugin Handler) {
	for j := 0; j < curPlugin.On().Times; j++ {
		select {
		case <-curPlugin.On().Channel:
		case <-time.After(40 * time.Second):
			fmt.Println("over the time:" + strconv.Itoa(curPlugin.GetIndex()))
		}
	}
	fmt.Println("finish:" + strconv.Itoa(curPlugin.GetIndex()))
}

func checkRelyOnParams(args1, args2 string) bool {
	return strings.EqualFold(args1, args2)
}