Skip to content

Commit

Permalink
add feature gate introduce
Browse files Browse the repository at this point in the history
  • Loading branch information
nejisama committed Mar 13, 2020
1 parent 6d101f7 commit 9e43e80
Show file tree
Hide file tree
Showing 2 changed files with 248 additions and 2 deletions.
4 changes: 2 additions & 2 deletions content/zh/docs/contribute/creating-pages/index.md
Expand Up @@ -4,7 +4,7 @@ linkTitle: "创建页面"
date: 2020-02-11
weight: 2
description: >
本页介绍了如何创建 MOSn 文档页面。
本页介绍了如何创建 MOSN 文档页面。
---

## 开始之前
Expand Down Expand Up @@ -72,4 +72,4 @@ author: "作者信息"
- 使用英文单词命名
- 不同的单词间使用连字符连接
- 不得出现其他标点符号
- 名称尽量简短
- 名称尽量简短
246 changes: 246 additions & 0 deletions content/zh/docs/dev/featuregate-introduce/index.md
@@ -0,0 +1,246 @@
---
title: "FeatureGate介绍"
linkTitle: "FeatureGate介绍"
date: 2020-03-13
weight: 2
description: >
本页介绍了MOSN的FeatureGate。
---

## Featuregate 介绍

在MOSN中,存在一些功能是需要在启动时决定是否开启的,为了满足这种需求,MOSN推出了feature gate(功能开关)的能力。

feature gate描述了一组MOSN中需要开启/关闭的feature状态,每个feature都有自己默认的状态,每个mosn版本支持的feature、feature默认的版本都有所不同;feature gate的描述用一个字符串表示,按照${feature}=${bool}的方式,用逗号进行分割:

```
// 通用模版
./mosn start -c ${config path} -f ${feature gates description}
// 示例
./mosn start -c mosn_config.json -f "auto_config=true,XdsMtlsEnable=true"
```

feature gate 不仅仅是提供了一种功能切换的能力,同时也提供了一种可扩展的开发机制,基于MOSN进行二次开发时,可以使用feature gate做到如下的功能:
- 功能切换的能力,可以控制某个feature的开启/关闭
- feature之间的依赖关系管理,包括feature之间的启动顺序依赖、开启/关闭状态的依赖等
- 举例说明,基于mosn实现两个feature,分别为A和B,需要在A初始化完成以后,B会使用A初始化的结果进行初始化,这就是B依赖A,当feature A处于Disable状态时,B显然也会处于Disable或者需要作出对应的“降级”; feature gate框架提供了一种简单的方式,可以更加专注于feature的开发,而不用去管理对应的启动与依赖

基于featuregate的框架,在mosn进行不同feature的二次开发,是featuregate框架最主要的目的。

## 基于Featuregate进行开发

### featuregate 实现

首先,我们来看一下,featuregate框架提供了哪些接口
```Go
// 返回一个Feature当前的状态,true表示enable,false表示disable
func Enabled(key Feature) bool
// “订阅”一个Feature,并且返回其订阅完成以后的状态。
// 当订阅的Feature初始化完成以后,会返回其是否Enable。
// 如果订阅的Feature是Disable的,会直接返回false;如果在订阅的timeout期间,Feature依然没有
// 初始化完成,那么会返回订阅超时的错误,如果timeout小于等于0,则没有订阅超时
func Subscribe(key Feature, timeout time.Duration) (bool, error)
// 设置feature gates的状态,value为一个完整的feature gates描述
func Set(value string) error
// 设置feature gates的状态,其中map的key为feature的key,value是期望设置的feature状态
func SetFromMap(m map[string]bool) error
// 新注册一个feature到feature gate中
func AddFeatureSpec(key Feature, spec FeatureSpec) error
// 设置一个feature的状态
func SetFeatureState(key Feature, enable bool) error
// 开启初始化feature
func StartInit()
// 等待所有的feature初始化结束
func WaitInitFinsh() error
```

这其中,StartInit 和 WaitInitFinsh 是由MOSN框架进行调用,基于MOSN进行二次开发时无须关注和调用;通常情况下,Set和SetFromMap也无须关注。所有的上述接口,都是由框架下默认的一个不可导出的全局FeatureGate对象暴露,在没有极为特殊需求的场景下(如编写单元测试),不需要额外生成FeatureGate对象,使用默认的即可。

接下来,我们看一下FeatureGate的实现
```Go
type knownFeatureSpec struct {
FeatureSpec
once sync.Once
channel chan struct{}
}
type Feature string
type FeatureGate struct {
// lock guards writes to known, enabled, and reads/writes of closed
lock sync.Mutex
known map[Feature]*knownFeatureSpec
// inited is set to true when StartInit is called.
inited bool
wg sync.WaitGroup
once sync.Once
}
```

FeatureGate包含了一个map,用于记录所有被支持的Feature;一个inited状态标,表示FeatureGate是否已经完成了初始化; Once用于确保FeatureGate的初始化只执行一次,WaitGroup则用于同步Feature初始化的结果;一个Mutex用于并发保护。
按照FeatureGate的设计,不同的Feature是可以通过Add的方式新增,以及不同的Set方法改变状态的,而不同Feature的初始化Init函数都会统一执行,因此一旦执行完Init,则不再允许新增Feature、修改Feature状态;因此我们需要一个inited的标记来记录这个行为。
knownFeatureSpec是一个不可导出的结构体,用于对表示不同Feature的FeatureSpec封装,其中的Once和channel均是用于FeatureGate中订阅和初始化使用,在此不做详细说明。
下面,我们来看一下FeatureSpec的定义,这也是我们基于FeatureGate框架进行开发的核心数据结构。
```Go
type prerelease string
const (
// Values for PreRelease.
Alpha = prerelease("ALPHA")
Beta = prerelease("BETA")
GA = prerelease("")
)
type FeatureSpec interface {
// Default is the default enablement state for the feature
Default() bool
// LockToDefault indicates that the feature is locked to its default and cannot be changed
LockToDefault() bool
// SetState sets the enablement state for the feature
SetState(enable bool)
// State indicates the feature enablement
State() bool
// InitFunc used to init process when StartInit is invoked
InitFunc()
// PreRelease indicates the maturity level of the feature
PreRelease() prerelease
}
```
- prerelease 是不可导出的定义,有三个约定的导出变量可以使用,相当于传统语言的Enum类型,用于描述Feature的信息,没有明确的作用
- FeatureSpec可以自行实现,同时多数情况下可以用框架实现的BaseFeatureSpec,或者基于BaseFeatureSpec进行封装;如注释描述,通常情况下只需要额外封装实现一个InitFunc函数即可
```Go
// BaseFeatureSpec is a basic implementation of FeatureSpec.
// Usually, a feature spec just need an init func.
type BaseFeatureSpec struct {
// 默认状态
DefaultValue bool
// 是否可修改状态,如果为true,说明这个feature只能保持默认状态
// 一般情况下设置这个为true的时候,default也是true
// 这种feature 主要会用于做为其他feature的“基础依赖”
IsLockedDefault bool
PreReleaseValue prerelease
stateValue bool // stateValue shoule be setted by SetState
inited int32 // inited cannot be setted
}
```

### feature gate的使用

了解了feature gate的基本实现,就可以考虑使用feature gate进行基本的编程扩展了。下面会介绍几种feature gate的使用场景,以及如何编写feature

#### 1. 基本的“全局”开关
对于Feature切换最基本的使用场景,就是使用一个类似“全局变量”进行控制,通过IF条件判断执行不同的逻辑。使用FeatureGate框架实现这种能力,可以把控制Feature切换的参数全部统一到启动参数中
```Go
var featureName featuregate.Feature = "simple_feature"
func init() {
fs := &featuregate.BaseFeatureSpec{
DefaultValue: true
}
featuregate.AddFeatureSpec(featureName,fs)
}

func myfunc() {
if featuregate.Enable(featureName) {
dosth()
} else {
dosth2()
}
}
```

#### 2. 需要进行“初始化”操作
通过封装扩展InitFunc函数,让相关的初始化工作在mosn启动时统一完成,如果Feature处于Disable状态,那么InitFunc不会执行
```Go
var featureName featuregate.Feature = "init_feature"

type MyFeature struct {
*BaseFeatureSpec
}

func (f *MyFeature) InitFunc() {
doInit()
}
// 其他的类似 1.
```

#### 3. Feature之间存在依赖关系
这个功能是FeatureGate框架提供的最重要的能力,可以方便的解决下面的场景:
- 假设我们存在四个独立的组件(Feature),分别是A、B、C,D
- B和C的启动都依赖于A,即首先要A启动完成,然后B和C才能启动完成;D依赖于B,必须B启动完成,D才可以启动
- 如果A没有启动,B就不能启动,而C存在一种降级方案,依然可以继续工作
- 四个Feature在FeatureGate框架下可各自实现,如下
```Go
var FeatureA featuregate.Feature = "A"

func init() {
fs := &featuregate.BaseFeatureSpec{
DefaultValue: true
}
featuregate.AddFeatureSpec(FeatureA,fs)
}
```

```Go
var FeatureB featuregate.Feature = "B"

type FB struct {
*BaseFeatureSpec
}

func (f *FB) InitFunc() {
enabled, err := featuregate.Subscribe(FeatureA, 5 * time.Second)
if err != nil || !enabled {
f.SetState(false) // 如果FeatureA没有开启,则FeatureB也不开启
}
}
```

```Go
var FeatureC featuregate.Feature = "C"

type FC struct {
*BaseFeatureSpec
mode int32
}

func (f *FC) InitFunc() {
enabled, err := featuregate.Subscribe(FeatureA, 5 * time.Second)
if err != nil || !enabled {
f.mode = -1 // 降级模式
return
}
if enabled {
f.mode = 1 // 正常模式
}
}

func (f *FC) MyFunc() {
if f.mode == 1 {
dosth()
} else if f.mode == -1 {
dosth2()
}
}
```

```Go
type FeatureD featuregate.Feature = "D"

type FD struct {
*BaseFeatureSpec
}

func (f *FD) InitFunc() {
enabled, err := featuregate.Subscribe(FeatureB, 0) // 不超时,一定要等待B结束
if err != nil || !enabled {
return
}
f.Start()
}

func (f *FD) Start() {
}
```

### FAQ

#### 为什么不使用配置的方式,而要使用feature gate?

- 配置文件需要进行解析,feature gate更有利于扩展能力的实现
- 有的feature 需要判断的时机,比配置文件解析要早,甚至可能影响配置解析的逻辑

0 comments on commit 9e43e80

Please sign in to comment.