-
Notifications
You must be signed in to change notification settings - Fork 448
动态数据源扩展设计
这篇文章主要描述 sentinel-golang 的动态数据源扩展的设计。目前 sentinel 内部的限流、熔断等策略都是基于规则来实现的,提供动态数据源扩展的目的,就是希望将规则或则其余的 properties 的更新操作通过一些配置中心中间件(比如 etcd,conful,nacos-go 等等)来实现动态更新,数据流大概就是:Sentinel board 或则是其余的 Config center dashboard --> 配置中心中间件 --> sentinel, 从而实现 properties 的动态更新。
动态数据源扩展的整体架构和 Java 版本是一样的,可以参考下图所示架构图:

整体的设计分为两大块:property的抽象和DataSource数据源的抽象。
- Property的抽象作为DataSource的下游,负责具体property的处理,将最新的property更新到下游的组件(比如流控规则、限流规则、配置模块等)。
- DataSource的抽象主要是负责建立与配置中心中间件建立长连接,基于类似于Watcher的监听机制监听具体property的变化,并将property变化通过Property的抽象处理。
Property的抽象通过接口 PropertyHandler 接口来定义,PropertyHandler的职责边界是:处理输入的property字节,并将最新的property更新到下游相关联的核心组件。
type PropertyHandler interface {
// check whether the current src is consistent with last update property
isPropertyConsistent(src interface{}) bool
// handle the current property
Handle(src []byte) error
}PropertyHandler 的定义非常简单,只包含两个函数:
-
isPropertyConsistent函数用于判断当前更新的 property 是否和上次更新的一样,如果一样就不做更新,类似于一个缓存过滤机制。 -
Handle函数则负责具体的 property 处理逻辑,包括将字节数组转换成实际的 property ,并将该 property 更新到下游相关联的核心组件。
Sentinel 提供了 PropertyHandler 的一个默认的实现:DefaultPropertyHandler。
type DefaultPropertyHandler struct {
lastUpdateProperty interface{}
converter PropertyConverter
updater PropertyUpdater
}这里的一个DefaultPropertyHandler实例将用于处理一个 property type。 属性lastUpdateProperty 用于缓存上次更新的property,用于过滤无效property。 此外还包含两个函数属性, 下面先看定义:
// PropertyConverter func is to converter source message bytes to the specific property.
// the first return value: is the real property;
// the second return value: return nil if succeed to convert src, if not return the detailed error when convert src.
// if src is nil or len(src)==0, the return value is (nil,nil)
type PropertyConverter func(src []byte) (interface{}, error)
// PropertyUpdater func is to update the specific properties to downstream.
// return nil if succeed to update, if not, return the error.
type PropertyUpdater func(data interface{}) errorPropertyConverter:会将property的字节数组反序列化成具体的property类型。 具体的反序列化协议依据用户的实现,提供比较高的灵活性,Sentinel默认会提供一个json反序列化的实现,用户也可以根据自己的需求,自己实现自己的反序列化方式。
PropertyUpdater:会将具体的property类型数据,更新到相关联的下游的核心组件。比如 []FlowRule 类型的property会被更新到 flow module的flow manager里面。这里sentinel框架会提供所有支持的动态 property 的update函数的实现,用户对这个理论上来说是不感知的。
这里需要强调的是,每种动态property类型的PropertyConverter和PropertyUpdater是成对出现在DefaultPropertyHandler中的。
DefaultPropertyHandler 具体的 Handle 函数的实现可以参考源码:
func (h *DefaultPropertyHandler) Handle(src []byte) error {
defer func() {
if err := recover(); err != nil && logger != nil {
logger.Panicf("Unexpected panic: %+v", errors.Errorf("%+v", err))
}
}()
// convert to target property
realProperty, err := h.converter(src)
if err != nil {
return err
}
isConsistent := h.isPropertyConsistent(realProperty)
if isConsistent {
return nil
}
return h.updater(realProperty)
}整体的workflow如下:
- 调用converter将 []bytes 转成具体的property;
- 通过缓存过滤无效property;
- 更新实际的property到相关联的下游组件。
数据源的抽象通过接口 Datasource 来定义,一个Datasource 可能会有多个下游的property handler来处理。Datasource的职责就是负责建立与配置中心中间件建立长连接,基于类似于Watcher的监听机制监听具体property的变化,并将property变化通过下游的property handler来处理。下面是接口的定义:
// The generic interface to describe the datasource
// Each DataSource instance listen in one property type.
type DataSource interface {
// Add specified property handler in current datasource
AddPropertyHandler(h PropertyHandler)
// Remove specified property handler in current datasource
RemovePropertyHandler(h PropertyHandler)
// Read original data from the data source.
// return source bytes if succeed to read, if not, return error when reading
ReadSource() ([]byte, error)
// Initialize the datasource and load initial rules
// start listener to listen on dynamic source
// return error if initialize failed;
// once initialized, listener should recover all panic and error.
Initialize() error
// Close the data source.
io.Closer
}这里主要有两个函数需要介绍下:
- ReadSource:基于watcher机制,从watcher读取最新的property数据。
- Initialize:这里需要创建property的watcher,并通过一个单独的goroutine来监听watcher上的事件并处理相应的事件。
这里基于 refreshable file 这个case来说明一些实际场景的use case。 refreshable file的code正在review中,可以参考PR:https://github.com/alibaba/sentinel-golang/pull/86/files
Suppose现在property是flow rules, 下游也只有flow component。test code如下:
func TestNewFileDataSource_FlowRule(t *testing.T) {
ds := FileDataSourceStarter("../../../tests/testdata/extension/refreshable_file/FlowRule.json", plugin.NewFlowRulesHandler(plugin.FlowRulesJsonConverter))
time.Sleep(5 * time.Second)
ds.Close()
}这个主要是联动的场景:在集群模式下,某一个property是维护一个集群中namespace set, namespace Set这个集合的变更,更新下游namespace的每个property的DataSource。
TODO
-
文档
-
Documents (EN)