Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"breaker_test.go",
"sre_breaker_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/time:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"breaker.go",
"sre_breaker.go",
],
importpath = "go-common/library/net/netutil/breaker",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/stat/summary:go_default_library",
"//library/time:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["example_test.go"],
tags = ["automanaged"],
deps = [
"//library/net/netutil/breaker:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,17 @@
#### breaker
### v1.1.0
> 1.remove hystrix and use summary.Summary instead of counter.Counter
##### v1.0.3
> 1.修复配置了conf时fix k=2
##### v1.0.2
> 1.Go支持上报普罗米修斯
##### v1.0.1
> 1. 增加Go方法
##### v1.0.0
> 1. 提供默认熔断配置,热更新配置功能
> 2. 内敛普罗米修斯上报熔断状态功能

View File

@@ -0,0 +1,6 @@
# Author
zhapuyu
liangkai
# Reviewer
maojian

View File

@@ -0,0 +1,9 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- liangkai
- zhapuyu
reviewers:
- liangkai
- maojian
- zhapuyu

View File

@@ -0,0 +1,27 @@
#### breaker
##### 项目简介
> 提供熔断器功能供各种client如rpc、http、msyql等进行熔断
> 提供Go方法供业务在breaker熔断前后进行回调处理
##### 编译环境
> 1. 请只用golang v1.8.x以上版本编译执行。
##### 依赖包
> 1. 无
##### 配置说明
> 1. NewGroup(name string,c *Config)当c==nil时则采用默认配置
> 2. 可通过breaker.Init(c *Config)替换默认配置
> 3. 可通过group.Reload(c *Config)进行配置更新
> 4. 默认配置如下所示:
_conf = &Config{
Window: xtime.Duration(3 * time.Second),
Sleep: xtime.Duration(100 * time.Millisecond),
Bucket: 10,
Ratio: 0.5,
Request: 100,
}
##### 测试
> 1. 执行当前目录下所有测试文件,测试所有功能

View File

@@ -0,0 +1,176 @@
package breaker
import (
"sync"
"time"
xtime "go-common/library/time"
)
// Config broker config.
type Config struct {
SwitchOff bool // breaker switch,default off.
// Hystrix
Ratio float32
Sleep xtime.Duration
// Google
K float64
Window xtime.Duration
Bucket int
Request int64
}
func (conf *Config) fix() {
if conf.K == 0 {
conf.K = 1.5
}
if conf.Request == 0 {
conf.Request = 100
}
if conf.Ratio == 0 {
conf.Ratio = 0.5
}
if conf.Sleep == 0 {
conf.Sleep = xtime.Duration(500 * time.Millisecond)
}
if conf.Bucket == 0 {
conf.Bucket = 10
}
if conf.Window == 0 {
conf.Window = xtime.Duration(3 * time.Second)
}
}
// Breaker is a CircuitBreaker pattern.
// FIXME on int32 atomic.LoadInt32(&b.on) == _switchOn
type Breaker interface {
Allow() error
MarkSuccess()
MarkFailed()
}
// Group represents a class of CircuitBreaker and forms a namespace in which
// units of CircuitBreaker.
type Group struct {
mu sync.RWMutex
brks map[string]Breaker
conf *Config
}
const (
// StateOpen when circuit breaker open, request not allowed, after sleep
// some duration, allow one single request for testing the health, if ok
// then state reset to closed, if not continue the step.
StateOpen int32 = iota
// StateClosed when circuit breaker closed, request allowed, the breaker
// calc the succeed ratio, if request num greater request setting and
// ratio lower than the setting ratio, then reset state to open.
StateClosed
// StateHalfopen when circuit breaker open, after slepp some duration, allow
// one request, but not state closed.
StateHalfopen
//_switchOn int32 = iota
// _switchOff
)
var (
_mu sync.RWMutex
_conf = &Config{
Window: xtime.Duration(3 * time.Second),
Bucket: 10,
Request: 100,
Sleep: xtime.Duration(500 * time.Millisecond),
Ratio: 0.5,
// Percentage of failures must be lower than 33.33%
K: 1.5,
// Pattern: "",
}
_group = NewGroup(_conf)
)
// Init init global breaker config, also can reload config after first time call.
func Init(conf *Config) {
if conf == nil {
return
}
_mu.Lock()
_conf = conf
_mu.Unlock()
}
// Go runs your function while tracking the breaker state of default group.
func Go(name string, run, fallback func() error) error {
breaker := _group.Get(name)
if err := breaker.Allow(); err != nil {
return fallback()
}
return run()
}
// newBreaker new a breaker.
func newBreaker(c *Config) (b Breaker) {
// factory
return newSRE(c)
}
// NewGroup new a breaker group container, if conf nil use default conf.
func NewGroup(conf *Config) *Group {
if conf == nil {
_mu.RLock()
conf = _conf
_mu.RUnlock()
} else {
conf.fix()
}
return &Group{
conf: conf,
brks: make(map[string]Breaker),
}
}
// Get get a breaker by a specified key, if breaker not exists then make a new one.
func (g *Group) Get(key string) Breaker {
g.mu.RLock()
brk, ok := g.brks[key]
conf := g.conf
g.mu.RUnlock()
if ok {
return brk
}
// NOTE here may new multi breaker for rarely case, let gc drop it.
brk = newBreaker(conf)
g.mu.Lock()
if _, ok = g.brks[key]; !ok {
g.brks[key] = brk
}
g.mu.Unlock()
return brk
}
// Reload reload the group by specified config, this may let all inner breaker
// reset to a new one.
func (g *Group) Reload(conf *Config) {
if conf == nil {
return
}
conf.fix()
g.mu.Lock()
g.conf = conf
g.brks = make(map[string]Breaker, len(g.brks))
g.mu.Unlock()
}
// Go runs your function while tracking the breaker state of group.
func (g *Group) Go(name string, run, fallback func() error) error {
breaker := g.Get(name)
if err := breaker.Allow(); err != nil {
return fallback()
}
return run()
}

View File

@@ -0,0 +1,100 @@
package breaker
import (
"errors"
"testing"
"time"
xtime "go-common/library/time"
)
func TestGroup(t *testing.T) {
g1 := NewGroup(nil)
g2 := NewGroup(_conf)
if g1.conf != g2.conf {
t.FailNow()
}
brk := g2.Get("key")
brk1 := g2.Get("key1")
if brk == brk1 {
t.FailNow()
}
brk2 := g2.Get("key")
if brk != brk2 {
t.FailNow()
}
g := NewGroup(_conf)
c := &Config{
Window: xtime.Duration(1 * time.Second),
Sleep: xtime.Duration(100 * time.Millisecond),
Bucket: 10,
Ratio: 0.5,
Request: 100,
SwitchOff: !_conf.SwitchOff,
}
g.Reload(c)
if g.conf.SwitchOff == _conf.SwitchOff {
t.FailNow()
}
}
func TestInit(t *testing.T) {
switchOff := _conf.SwitchOff
c := &Config{
Window: xtime.Duration(3 * time.Second),
Sleep: xtime.Duration(100 * time.Millisecond),
Bucket: 10,
Ratio: 0.5,
Request: 100,
SwitchOff: !switchOff,
}
Init(c)
if _conf.SwitchOff == switchOff {
t.FailNow()
}
}
func TestGo(t *testing.T) {
if err := Go("test_run", func() error {
t.Log("breaker allow,callback run()")
return nil
}, func() error {
t.Log("breaker not allow,callback fallback()")
return errors.New("breaker not allow")
}); err != nil {
t.Error(err)
}
_group.Reload(&Config{
Window: xtime.Duration(3 * time.Second),
Sleep: xtime.Duration(100 * time.Millisecond),
Bucket: 10,
Ratio: 0.5,
Request: 100,
SwitchOff: true,
})
if err := Go("test_fallback", func() error {
t.Log("breaker allow,callback run()")
return nil
}, func() error {
t.Log("breaker not allow,callback fallback()")
return nil
}); err != nil {
t.Error(err)
}
}
func markSuccess(b Breaker, count int) {
for i := 0; i < count; i++ {
b.MarkSuccess()
}
}
func markFailed(b Breaker, count int) {
for i := 0; i < count; i++ {
b.MarkFailed()
}
}

View File

@@ -0,0 +1,61 @@
package breaker_test
import (
"fmt"
"time"
"go-common/library/net/netutil/breaker"
xtime "go-common/library/time"
)
// ExampleGroup show group usage.
func ExampleGroup() {
c := &breaker.Config{
Window: xtime.Duration(3 * time.Second),
Sleep: xtime.Duration(100 * time.Millisecond),
Bucket: 10,
Ratio: 0.5,
Request: 100,
}
// init default config
breaker.Init(c)
// new group
g := breaker.NewGroup(c)
// reload group config
c.Bucket = 100
c.Request = 200
g.Reload(c)
// get breaker by key
g.Get("key")
}
// ExampleBreaker show breaker usage.
func ExampleBreaker() {
// new group,use default breaker config
g := breaker.NewGroup(nil)
brk := g.Get("key")
// mark request success
brk.MarkSuccess()
// mark request failed
brk.MarkFailed()
// check if breaker allow or not
if brk.Allow() == nil {
fmt.Println("breaker allow")
} else {
fmt.Println("breaker not allow")
}
}
// ExampleGo this example create a default group and show function callback
// according to the state of breaker.
func ExampleGo() {
run := func() error {
return nil
}
fallback := func() error {
return fmt.Errorf("unknown error")
}
if err := breaker.Go("example_go", run, fallback); err != nil {
fmt.Println(err)
}
}

View File

@@ -0,0 +1,71 @@
package breaker
import (
"math"
"math/rand"
"sync/atomic"
"time"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/stat/summary"
)
// sreBreaker is a sre CircuitBreaker pattern.
type sreBreaker struct {
stat summary.Summary
k float64
request int64
state int32
r *rand.Rand
}
func newSRE(c *Config) Breaker {
return &sreBreaker{
stat: summary.New(time.Duration(c.Window), c.Bucket),
r: rand.New(rand.NewSource(time.Now().UnixNano())),
request: c.Request,
k: c.K,
state: StateClosed,
}
}
func (b *sreBreaker) Allow() error {
success, total := b.stat.Value()
k := b.k * float64(success)
if log.V(5) {
log.Info("breaker: request: %d, succee: %d, fail: %d", total, success, total-success)
}
// check overflow requests = K * success
if total < b.request || float64(total) < k {
if atomic.LoadInt32(&b.state) == StateOpen {
atomic.CompareAndSwapInt32(&b.state, StateOpen, StateClosed)
}
return nil
}
if atomic.LoadInt32(&b.state) == StateClosed {
atomic.CompareAndSwapInt32(&b.state, StateClosed, StateOpen)
}
dr := math.Max(0, (float64(total)-k)/float64(total+1))
rr := b.r.Float64()
if log.V(5) {
log.Info("breaker: drop ratio: %f, real rand: %f, drop: %v", dr, rr, dr > rr)
}
if dr <= rr {
return nil
}
return ecode.ServiceUnavailable
}
func (b *sreBreaker) MarkSuccess() {
b.stat.Add(1)
}
func (b *sreBreaker) MarkFailed() {
// NOTE: when client reject requets locally, continue add counter let the
// drop ratio higher.
b.stat.Add(0)
}

View File

@@ -0,0 +1,59 @@
package breaker
import (
"testing"
"time"
xtime "go-common/library/time"
"github.com/stretchr/testify/assert"
)
func getSRE() Breaker {
return NewGroup(&Config{
Window: xtime.Duration(1 * time.Second),
Bucket: 10,
Request: 100,
K: 2,
}).Get("")
}
func testSREClose(t *testing.T, b Breaker) {
markSuccess(b, 80)
assert.Equal(t, b.Allow(), nil)
markSuccess(b, 120)
assert.Equal(t, b.Allow(), nil)
}
func testSREOpen(t *testing.T, b Breaker) {
markSuccess(b, 100)
assert.Equal(t, b.Allow(), nil)
markFailed(b, 10000000)
assert.NotEqual(t, b.Allow(), nil)
}
func testSREHalfOpen(t *testing.T, b Breaker) {
// failback
assert.Equal(t, b.Allow(), nil)
t.Run("allow single failed", func(t *testing.T) {
markFailed(b, 10000000)
assert.NotEqual(t, b.Allow(), nil)
})
time.Sleep(2 * time.Second)
t.Run("allow single succeed", func(t *testing.T) {
assert.Equal(t, b.Allow(), nil)
markSuccess(b, 10000000)
assert.Equal(t, b.Allow(), nil)
})
}
func TestSRE(t *testing.T) {
b := getSRE()
testSREClose(t, b)
b = getSRE()
testSREOpen(t, b)
b = getSRE()
testSREHalfOpen(t, b)
}