連載中...
Golang框架實戰-KisFlow流式計算框架(1)-概述
Golang框架實戰-KisFlow流式計算框架(2)-項目構建/基礎模塊-(上)
Golang框架實戰-KisFlow流式計算框架(3)-項目構建/基礎模塊-(下)
Golang框架實戰-KisFlow流式計算框架(4)-數據流
Golang框架實戰-KisFlow流式計算框架(5)-Function調度
4.1 Router
現在,將KisFlow提供對外Function開放注冊能力,首先我們要定義一些注冊函數原型,和管理這些Function的Router映射關系類型。
創建kis-flow/kis/router.go
,定義原型如下:
kis-flow/kis/router.go
package kis
import "context"
// FaaS Function as a Service
type FaaS func(context.Context, Flow) error
// funcRouter
// key: Function Name
// value: Function 回調自定義業務
type funcRouter map[string]FaaS
// flowRouter
// key: Flow Name
// value: Flow
type flowRouter map[string]Flow
FaaS
:是開發者給KisFlow注冊的Function回調業務函數原型,需要傳遞兩個參數,Context和Flow,Context主要承載業務的上線文環境,Flow主要承載KisFlow的上下文環境,我們可以通過Flow獲取當前Function的配置信息,當前Function的數據信息,已經Flow上其他節點的Function相關信息等。
funcRouter
: 管理FunctionName和FaaS業務回調的映射關系Map,是一個私有類型,不對外提供引用。需要注意的是,funcRouter的key是FunctionName,因為FunctionId是生成的隨機Id,開發者在注冊路由的時候,并無法預判和可讀,所以關聯的業務回調是與FunctionName做的映射關系。
flowRouter
:管理FlowName和Flow實例的映射關系Map,是一個私有類型,不對外提供引用。flowRouter
依然是FlowName的映射關系。
4.2 KisPool
KisFlow提供一個用來管理全部全局映射關系的類KisPool
,KisPool包含Router,且提供對Router的管理能力。
4.2.1 KisPool的定義
創建 kis-flow/kis/pool.go
文件,來創建kis_pool
模塊。
kis-flow/kis/pool.go
package kis
import (
"context"
"errors"
"fmt"
"kis-flow/log"
"sync"
)
var _poolOnce sync.Once
// kisPool 用于管理全部的Function和Flow配置的池子
type kisPool struct {
fnRouter funcRouter // 全部的Function管理路由
fnLock sync.RWMutex // fnRouter 鎖
flowRouter flowRouter // 全部的flow對象
flowLock sync.RWMutex // flowRouter 鎖
}
// 單例
var _pool *kisPool
// Pool 單例構造
func Pool() *kisPool {
_poolOnce.Do(func() {
//創建kisPool對象
_pool = new(kisPool)
// fnRouter初始化
_pool.fnRouter = make(funcRouter)
// flowRouter初始化
_pool.flowRouter = make(flowRouter)
})
return _pool
}
kis_pool
采用單例模式,Pool()
方法為獲取當前的單例,有關fnRouter
和 flowRouter
在生命周期只會初始化一次,通過sync.Once
來控制。
4.2.2 注冊及獲取Flow
KisPool可以提供添加和獲取Flow信息的接口,如下:
kis-flow/kis/pool.go
func (pool *kisPool) AddFlow(name string, flow Flow) {
pool.flowLock.Lock()
defer pool.flowLock.Unlock()
if _, ok := pool.flowRouter[name]; !ok {
pool.flowRouter[name] = flow
} else {
errString := fmt.Sprintf("Pool AddFlow Repeat FlowName=%s\n", name)
panic(errString)
}
log.Logger().InfoF("Add FlowRouter FlowName=%s\n", name)
}
func (pool *kisPool) GetFlow(name string) Flow {
pool.flowLock.RLock()
defer pool.flowLock.RUnlock()
if flow, ok := pool.flowRouter[name]; ok {
return flow
} else {
return nil
}
}
AddFlow會根據相同的FlowName進行做重復校驗,相同的Flow無法注冊多次。
4.2.3 注冊及調度Function
KisPool提供注冊Funciton回調和調度Funciton方法, 如下。
kis-flow/kis/pool.go
// FaaS 注冊 Function 計算業務邏輯, 通過Function Name 索引及注冊
func (pool *kisPool) FaaS(fnName string, f FaaS) {
pool.fnLock.Lock()
defer pool.fnLock.Unlock()
if _, ok := pool.fnRouter[fnName]; !ok {
pool.fnRouter[fnName] = f
} else {
errString := fmt.Sprintf("KisPoll FaaS Repeat FuncName=%s", fnName)
panic(errString)
}
log.Logger().InfoF("Add KisPool FuncName=%s", fnName)
}
// CallFunction 調度 Function
func (pool *kisPool) CallFunction(ctx context.Context, fnName string, flow Flow) error {
if f, ok := pool.fnRouter[fnName]; ok {
return f(ctx, flow)
}
log.Logger().ErrorFX(ctx, "FuncName: %s Can not find in KisPool, Not Added.\n", fnName)
return errors.New("FuncName: " + fnName + " Can not find in NsPool, Not Added.")
}
在CallFunction()
中,需要傳遞參數Flow,作為數據流調度的上下文環境。 開發者在自定義FaaS中可以通過Flow來獲取一些Funciton信息,所以這里需要給Flow補充幾個獲取配置信息的接口,之后如果再需要,再繼續補充,具體如下:
kis-flow/kis/flow.go
type Flow interface {
// Run 調度Flow,依次調度Flow中的Function并且執行
Run(ctx context.Context) error
// Link 將Flow中的Function按照配置文件中的配置進行連接
Link(fConf *config.KisFuncConfig, fParams config.FParam) error
// CommitRow 提交Flow數據到即將執行的Function層
CommitRow(row interface{}) error
// Input 得到flow當前執行Function的輸入源數據
Input() common.KisRowArr
// ++++++++++++++++++++++++++++++++++
// GetName 得到Flow的名稱
GetName() string
// GetThisFunction 得到當前正在執行的Function
GetThisFunction() Function
// GetThisFuncConf 得到當前正在執行的Function的配置
GetThisFuncConf() *config.KisFuncConfig
}
kis-flow/flow/kis_flow.go
func (flow *KisFlow) GetName() string {
return flow.Name
}
func (flow *KisFlow) GetThisFunction() kis.Function {
return flow.ThisFunction
}
func (flow *KisFlow) GetThisFuncConf() *config.KisFuncConfig {
return flow.ThisFunction.GetConfig()
}
4.3 KisFunction引用KisPool調度
現在,我們就可以在KisFunctionX中的Call()
來通過Pool進行調度了,依次修改每個Function的Call()方法。
kis-flow/function/kis_function_c.go
package function
import (
"context"
"kis-flow/kis"
"kis-flow/log"
)
type KisFunctionC struct {
BaseFunction
}
func (f *KisFunctionC) Call(ctx context.Context, flow kis.Flow) error {
log.Logger().InfoF("KisFunctionC, flow = %+v\n", flow)
// 通過KisPool 路由到具體的執行計算Function中
if err := kis.Pool().CallFunction(ctx, f.Config.FName, flow); err != nil {
log.Logger().ErrorFX(ctx, "Function Called Error err = %s\n", err)
return err
}
return nil
}
kis-flow/function/kis_function_e.go
package function
import (
"context"
"kis-flow/kis"
"kis-flow/log"
)
type KisFunctionE struct {
BaseFunction
}
func (f *KisFunctionE) Call(ctx context.Context, flow kis.Flow) error {
log.Logger().InfoF("KisFunctionE, flow = %+v\n", flow)
// 通過KisPool 路由到具體的執行計算Function中
if err := kis.Pool().CallFunction(ctx, f.Config.FName, flow); err != nil {
log.Logger().ErrorFX(ctx, "Function Called Error err = %s\n", err)
return err
}
return nil
}
kis-flow/function/kis_function_l.go
package function
import (
"context"
"kis-flow/kis"
"kis-flow/log"
)
type KisFunctionL struct {
BaseFunction
}
func (f *KisFunctionL) Call(ctx context.Context, flow kis.Flow) error {
log.Logger().InfoF("KisFunctionL, flow = %+v\n", flow)
// 通過KisPool 路由到具體的執行計算Function中
if err := kis.Pool().CallFunction(ctx, f.Config.FName, flow); err != nil {
log.Logger().ErrorFX(ctx, "Function Called Error err = %s\n", err)
return err
}
return nil
}
kis-flow/function/kis_function_s.go
package function
import (
"context"
"kis-flow/kis"
"kis-flow/log"
)
type KisFunctionS struct {
BaseFunction
}
func (f *KisFunctionS) Call(ctx context.Context, flow kis.Flow) error {
log.Logger().InfoF("KisFunctionS, flow = %+v\n", flow)
// 通過KisPool 路由到具體的執行計算Function中
if err := kis.Pool().CallFunction(ctx, f.Config.FName, flow); err != nil {
log.Logger().ErrorFX(ctx, "Function Called Error err = %s\n", err)
return err
}
return nil
}
kis-flow/function/kis_function_v.go
package function
import (
"context"
"kis-flow/kis"
"kis-flow/log"
)
type KisFunctionV struct {
BaseFunction
}
func (f *KisFunctionV) Call(ctx context.Context, flow kis.Flow) error {
log.Logger().InfoF("KisFunctionV, flow = %+v\n", flow)
// 通過KisPool 路由到具體的執行計算Function中
if err := kis.Pool().CallFunction(ctx, f.Config.FName, flow); err != nil {
log.Logger().ErrorFX(ctx, "Function Called Error err = %s\n", err)
return err
}
return nil
}
4.4 KisPool單元測試
接下來我們來針對KisPool進行單元測試。
4.4.1 自定義FaaS
kis-flow/test/kis_pool_test.go
package test
import (
"context"
"fmt"
"kis-flow/common"
"kis-flow/config"
"kis-flow/flow"
"kis-flow/kis"
"testing"
)
func funcName1Handler(ctx context.Context, flow kis.Flow) error {
fmt.Println("---> Call funcName1Handler ----")
for index, row := range flow.Input() {
// 打印數據
str := fmt.Sprintf("In FuncName = %s, FuncId = %s, row = %s", flow.GetThisFuncConf().FName, flow.GetThisFunction().GetId(), row)
fmt.Println(str)
// 計算結果數據
resultStr := fmt.Sprintf("data from funcName[%s], index = %d", flow.GetThisFuncConf().FName, index)
// 提交結果數據
_ = flow.CommitRow(resultStr)
}
return nil
}
func funcName2Handler(ctx context.Context, flow kis.Flow) error {
for _, row := range flow.Input() {
str := fmt.Sprintf("In FuncName = %s, FuncId = %s, row = %s", flow.GetThisFuncConf().FName, flow.GetThisFunction().GetId(), row)
fmt.Println(str)
}
return nil
}
4.4.2 注冊FaaS及啟動Flow
kis-flow/test/kis_pool_test.go
func TestNewKisPool(t *testing.T) {
ctx := context.Background()
// 0. 注冊Function
kis.Pool().FaaS("funcName1", funcName1Handler)
kis.Pool().FaaS("funcName2", funcName2Handler)
// 1. 創建2個KisFunction配置實例
source1 := config.KisSource{
Name: "公眾號抖音商城戶訂單數據",
Must: []string{"order_id", "user_id"},
}
source2 := config.KisSource{
Name: "用戶訂單錯誤率",
Must: []string{"order_id", "user_id"},
}
myFuncConfig1 := config.NewFuncConfig("funcName1", common.C, &source1, nil)
if myFuncConfig1 == nil {
panic("myFuncConfig1 is nil")
}
myFuncConfig2 := config.NewFuncConfig("funcName2", common.E, &source2, nil)
if myFuncConfig2 == nil {
panic("myFuncConfig2 is nil")
}
// 2. 創建一個 KisFlow 配置實例
myFlowConfig1 := config.NewFlowConfig("flowName1", common.FlowEnable)
// 3. 創建一個KisFlow對象
flow1 := flow.NewKisFlow(myFlowConfig1)
// 4. 拼接Functioin 到 Flow 上
if err := flow1.Link(myFuncConfig1, nil); err != nil {
panic(err)
}
if err := flow1.Link(myFuncConfig2, nil); err != nil {
panic(err)
}
// 5. 提交原始數據
_ = flow1.CommitRow("This is Data1 from Test")
_ = flow1.CommitRow("This is Data2 from Test")
_ = flow1.CommitRow("This is Data3 from Test")
// 6. 執行flow1
if err := flow1.Run(ctx); err != nil {
panic(err)
}
}
cd到kis-flow/test/
下執行命令:
go test -test.v -test.paniconexit0 -test.run TestNewKisPool
結果如下:
=== RUN TestNewKisPool
Add KisPool FuncName=funcName1
Add KisPool FuncName=funcName2
context.Background
====> After CommitSrcData, flow_name = flowName1, flow_id = flow-1fdae2bfac684f1d8edf89d9000208c0
All Level Data =
map[FunctionIdFirstVirtual:[This is Data1 from Test This is Data2 from Test This is Data3 from Test]]
KisFunctionC, flow = &{Id:flow-1fdae2bfac684f1d8edf89d9000208c0 Name:flowName1 Conf:0xc0000e27c0 Funcs:map[func-51527b72a4ee447fb0bd494bda9a84ad:0xc0000c0190 func-9cd2ab870b384794b312d2be10bb06fa:0xc0000c01e0] FlowHead:0xc0000c0190 FlowTail:0xc0000c01e0 flock:{w:{state:0 sema:0} writerSem:0 readerSem:0 readerCount:0 readerWait:0} ThisFunction:0xc0000c0190 ThisFunctionId:func-51527b72a4ee447fb0bd494bda9a84ad PrevFunctionId:FunctionIdFirstVirtual funcParams:map[func-51527b72a4ee447fb0bd494bda9a84ad:map[] func-9cd2ab870b384794b312d2be10bb06fa:map[]] fplock:{w:{state:0 sema:0} writerSem:0 readerSem:0 readerCount:0 readerWait:0} buffer:[] data:map[FunctionIdFirstVirtual:[This is Data1 from Test This is Data2 from Test This is Data3 from Test]] inPut:[This is Data1 from Test This is Data2 from Test This is Data3 from Test]}
---> Call funcName1Handler ----
In FuncName = funcName1, FuncId = func-51527b72a4ee447fb0bd494bda9a84ad, row = This is Data1 from Test
In FuncName = funcName1, FuncId = func-51527b72a4ee447fb0bd494bda9a84ad, row = This is Data2 from Test
In FuncName = funcName1, FuncId = func-51527b72a4ee447fb0bd494bda9a84ad, row = This is Data3 from Test
context.Background
====> After commitCurData, flow_name = flowName1, flow_id = flow-1fdae2bfac684f1d8edf89d9000208c0
All Level Data =
map[FunctionIdFirstVirtual:[This is Data1 from Test This is Data2 from Test This is Data3 from Test] func-51527b72a4ee447fb0bd494bda9a84ad:[data from funcName[funcName1], index = 0 data from funcName[funcName1], index = 1 data from funcName[funcName1], index = 2]]
KisFunctionE, flow = &{Id:flow-1fdae2bfac684f1d8edf89d9000208c0 Name:flowName1 Conf:0xc0000e27c0 Funcs:map[func-51527b72a4ee447fb0bd494bda9a84ad:0xc0000c0190 func-9cd2ab870b384794b312d2be10bb06fa:0xc0000c01e0] FlowHead:0xc0000c0190 FlowTail:0xc0000c01e0 flock:{w:{state:0 sema:0} writerSem:0 readerSem:0 readerCount:0 readerWait:0} ThisFunction:0xc0000c01e0 ThisFunctionId:func-9cd2ab870b384794b312d2be10bb06fa PrevFunctionId:func-51527b72a4ee447fb0bd494bda9a84ad funcParams:map[func-51527b72a4ee447fb0bd494bda9a84ad:map[] func-9cd2ab870b384794b312d2be10bb06fa:map[]] fplock:{w:{state:0 sema:0} writerSem:0 readerSem:0 readerCount:0 readerWait:0} buffer:[] data:map[FunctionIdFirstVirtual:[This is Data1 from Test This is Data2 from Test This is Data3 from Test] func-51527b72a4ee447fb0bd494bda9a84ad:[data from funcName[funcName1], index = 0 data from funcName[funcName1], index = 1 data from funcName[funcName1], index = 2]] inPut:[data from funcName[funcName1], index = 0 data from funcName[funcName1], index = 1 data from funcName[funcName1], index = 2]}
In FuncName = funcName2, FuncId = func-9cd2ab870b384794b312d2be10bb06fa, row = data from funcName[funcName1], index = 0
In FuncName = funcName2, FuncId = func-9cd2ab870b384794b312d2be10bb06fa, row = data from funcName[funcName1], index = 1
In FuncName = funcName2, FuncId = func-9cd2ab870b384794b312d2be10bb06fa, row = data from funcName[funcName1], index = 2
--- PASS: TestNewKisPool (0.00s)
PASS
ok kis-flow/test 0.520s
經過日志的詳細校驗,結果是符合我們預期的。
好了,現在Function的業務能力已經開放給開發者了,接下來我們來繼續完善KisFlow的能力。
4.5 【V0.3】源代碼
https://github.com/aceld/kis-flow/releases/tag/v0.3
作者:劉丹冰Aceld github: https://github.com/aceld
KisFlow開源項目地址:https://github.com/aceld/kis-flow
Golang框架實戰-KisFlow流式計算框架(1)-概述
Golang框架實戰-KisFlow流式計算框架(2)-項目構建/基礎模塊-(上)
Golang框架實戰-KisFlow流式計算框架(3)-項目構建/基礎模塊-(下)
Golang框架實戰-KisFlow流式計算框架(4)-數據流
Golang框架實戰-KisFlow流式計算框架(5)-Function調度