Skip to content

动态数据源扩展设计

louyuting edited this page Mar 13, 2020 · 10 revisions

动态数据源扩展

这篇文章主要描述 sentinel-golang 的动态数据源扩展的设计。目前 sentinel 内部的限流、熔断等策略都是基于规则来实现的,提供动态数据源扩展的目的,就是希望将规则或则其余的 properties 的更新操作通过一些配置中心中间件(比如 etcd,conful,nacos-go 等等)来实现动态更新,数据流大概就是:Sentinel board 或则是其余的 Config center dashboard --> 配置中心中间件 --> sentinel, 从而实现 properties 的动态更新。

Overall

动态数据源扩展的整体架构和 Java 版本是一样的,可以参考下图所示架构图:

整体的设计分为两大块:property的抽象和DataSource数据源的抽象。

  1. Property的抽象作为DataSource的下游,负责具体property的处理,将最新的property更新到下游的组件(比如流控规则、限流规则、配置模块等)。
  2. DataSource的抽象主要是负责建立与配置中心中间件建立长连接,基于类似于Watcher的监听机制监听具体property的变化,并将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 的定义非常简单,只包含两个函数:

  1. isPropertyConsistent 函数用于判断当前更新的 property 是否和上次更新的一样,如果一样就不做更新,类似于一个缓存过滤机制。
  2. 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{}) error

PropertyConverter:会将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如下:

  1. 调用converter将 []bytes 转成具体的property;
  2. 通过缓存过滤无效property;
  3. 更新实际的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
}

这里主要有两个函数需要介绍下:

  1. ReadSource:基于watcher机制,从watcher读取最新的property数据。
  2. Initialize:这里需要创建property的watcher,并通过一个单独的goroutine来监听watcher上的事件并处理相应的事件。

Use case

这里基于 refreshable file 这个case来说明一些实际场景的use case。 refreshable file的code正在review中,可以参考PR:https://github.com/alibaba/sentinel-golang/pull/86/files

一个动态property使用一个数据源,下游只有一个

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使用一个数据源,下游有多个

这个主要是联动的场景:在集群模式下,某一个property是维护一个集群中namespace set, namespace Set这个集合的变更,更新下游namespace的每个property的DataSource。

多个动态property使用一个数据源,下游只有一个

TODO

Clone this wiki locally