基于 govaluate 的监控系统中,如何设计灵活可扩展的自定义表达式函数体系
背景(Situation)
在现代监控系统中,用户往往需要通过灵活的表达式语言来定义告警规则、指标计算和复杂条件判断。govaluate
是 Go 语言中一个强大的表达式求值库,支持自定义函数扩展,满足复杂业务需求。
然而,随着监控场景和产品线的多样化,单一的表达式函数集合难以满足所有业务需求:
- 不同产品或模块需要注册不同的自定义函数集合
- 业务上下文(如实例、任务、策略ID)会影响函数的行为
- 需要动态加载和扩展函数,避免代码耦合和重复
如果直接在代码中硬编码所有函数,维护成本高,扩展困难,且难以支持多产品多策略。
目标(Task)
设计一个灵活、可扩展的自定义表达式函数注册体系,满足以下需求:
- 支持多产品、多策略的函数注册和调用
- 允许根据业务上下文动态创建函数实例
- 统一管理函数注册,方便维护和扩展
- 利用 govaluate 执行表达式时,能调用对应的自定义函数
- 设计清晰,易于理解和使用
解决方案(Action)
1. 设计接口和结构体
定义统一接口 GoValuateServiceInterface
,包含注册函数方法 RegistryFunc
,不同产品实现该接口,注册各自的函数集合。
type GoValuateServiceInterface interface {
RegistryFunc(funcMap map[string]govaluate.ExpressionFunction) map[string]govaluate.ExpressionFunction
}
定义结构体 GoValuateService
,携带业务上下文(实例、任务、策略ID等),函数实现依赖上下文。
2. 设计全局注册表和注册函数
使用全局 map GoValuateServiceMap
,key 为产品名,value 为服务构造函数。通过注册函数 RegistryGoValuateServiceMap
注册不同产品的构造函数。
var GoValuateServiceMap map[string]func(*GoValuateService) GoValuateServiceInterface
func RegistryGoValuateServiceMap(product string, service func(*GoValuateService) GoValuateServiceInterface) {
if GoValuateServiceMap == nil {
GoValuateServiceMap = make(map[string]func(*GoValuateService) GoValuateServiceInterface)
}
GoValuateServiceMap[product] = service
}
3. 实现服务构造函数和函数注册
实现构造函数 NewGoValuateService
,返回实现了接口的服务实例。
在 RegistryFunc
中注册自定义函数,如 len
、isStringHasPrefix
、toFloat
等。
4. 运行时动态加载函数
在业务代码中,根据产品名从注册表获取构造函数,创建服务实例,调用 RegistryFunc
注册函数到 funcMap
。
使用 govaluate 创建表达式,传入自定义函数映射,执行表达式。
完整示例代码
package main
import (
"fmt"
"github.com/Knetic/govaluate"
"strings"
)
// --------------------
// 1. 定义接口和结构体
// --------------------
type GoValuateServiceInterface interface {
RegistryFunc(funcMap map[string]govaluate.ExpressionFunction) map[string]govaluate.ExpressionFunction
}
type GoValuateService struct {
// 业务上下文示例字段
Ins string
Task string
StrategyID string
ConditionID string
}
// --------------------
// 2. 实现自定义函数
// --------------------
func (g *GoValuateService) GetValueLen(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("len expects exactly 1 argument")
}
switch v := args[0].(type) {
case string:
return float64(len(v)), nil
case []interface{}:
return float64(len(v)), nil
default:
return nil, fmt.Errorf("unsupported type for len")
}
}
func (g *GoValuateService) IsStringHasPrefix(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("isStringHasPrefix expects exactly 2 arguments")
}
str, ok1 := args[0].(string)
prefix, ok2 := args[1].(string)
if !ok1 || !ok2 {
return nil, fmt.Errorf("arguments must be strings")
}
return strings.HasPrefix(str, prefix), nil
}
func (g *GoValuateService) ToFloat(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("toFloat expects exactly 1 argument")
}
switch v := args[0].(type) {
case float64:
return v, nil
case int:
return float64(v), nil
case string:
var f float64
_, err := fmt.Sscanf(v, "%f", &f)
if err != nil {
return nil, err
}
return f, nil
default:
return nil, fmt.Errorf("unsupported type for toFloat")
}
}
// --------------------
// 3. 实现 RegistryFunc 注册函数
// --------------------
func (g *GoValuateService) RegistryFunc(funcMap map[string]govaluate.ExpressionFunction) map[string]govaluate.ExpressionFunction {
funcMap["len"] = g.GetValueLen
funcMap["isStringHasPrefix"] = g.IsStringHasPrefix
funcMap["toFloat"] = g.ToFloat
return funcMap
}
// --------------------
// 4. 定义全局注册表和注册函数
// --------------------
var GoValuateServiceMap map[string]func(*GoValuateService) GoValuateServiceInterface
func RegistryGoValuateServiceMap(product string, service func(*GoValuateService) GoValuateServiceInterface) {
if GoValuateServiceMap == nil {
GoValuateServiceMap = make(map[string]func(*GoValuateService) GoValuateServiceInterface)
}
GoValuateServiceMap[product] = service
}
// --------------------
// 5. 实现服务构造函数
// --------------------
func NewGoValuateService(goValuateService *GoValuateService) GoValuateServiceInterface {
return goValuateService
}
// --------------------
// 6. main 演示流程
// --------------------
func main() {
// 6.1 注册服务构造函数
RegistryGoValuateServiceMap("default", NewGoValuateService)
// 6.2 创建上下文数据
goValuateService := &GoValuateService{
Ins: "instance1",
Task: "taskA",
StrategyID: "strategy123",
ConditionID: "condition456",
}
// 6.3 初始化函数映射
funcMap := make(map[string]govaluate.ExpressionFunction)
// 6.4 遍历注册表,调用 RegistryFunc 注册所有函数
for _, serviceConstructor := range GoValuateServiceMap {
serviceInstance := serviceConstructor(goValuateService)
funcMap = serviceInstance.RegistryFunc(funcMap)
}
// 6.5 定义表达式,调用自定义函数
expressionStr := "len('hello') > 3 && isStringHasPrefix('golang', 'go') && toFloat('3.14') > 3"
expression, err := govaluate.NewEvaluableExpressionWithFunctions(expressionStr, funcMap)
if err != nil {
panic(err)
}
// 6.6 计算表达式
result, err := expression.Evaluate(nil)
if err != nil {
panic(err)
}
fmt.Printf("表达式结果: %v\n", result) // 期望输出: true
}
结果(Result)
通过该设计,监控系统实现了:
- 灵活扩展:新增产品只需注册对应构造函数和函数集合,无需修改核心代码
- 解耦清晰:函数注册和业务逻辑分离,代码结构清晰,易于维护
- 动态上下文支持:函数实现可访问业务上下文,支持复杂业务需求
- 统一管理:全局注册表集中管理所有产品的函数构造,方便查找和调用
- 高复用性:公共函数可复用,不同产品可定制专属函数,满足多样化需求
设计模式分析
- 工厂模式:通过注册表和构造函数动态创建服务实例
- 策略模式:不同产品实现不同函数注册策略,接口统一调用
- 单例模式(变体):全局注册表唯一管理构造函数
- 依赖注入:构造函数注入业务上下文,函数实现依赖上下文数据
总结
在复杂的监控系统中,表达式函数的灵活扩展和管理是关键。通过结合工厂、策略等设计模式,利用注册表集中管理构造函数,实现了高内聚低耦合的自定义函数体系。
该方案不仅提升了代码的可维护性和扩展性,也为业务快速迭代和多产品支持提供了坚实基础。