Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

41
library/net/netutil/BUILD Normal file
View File

@@ -0,0 +1,41 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/netutil/breaker:all-srcs",
],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"backoff.go",
"listen.go",
],
importpath = "go-common/library/net/netutil",
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["listen_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)

View File

@@ -0,0 +1,72 @@
package netutil
import (
"math/rand"
"time"
)
// DefaultBackoffConfig uses values specified for backoff in common.
var DefaultBackoffConfig = BackoffConfig{
MaxDelay: 120 * time.Second,
BaseDelay: 1.0 * time.Second,
Factor: 1.6,
Jitter: 0.2,
}
// Backoff defines the methodology for backing off after a call failure.
type Backoff interface {
// Backoff returns the amount of time to wait before the next retry given
// the number of consecutive failures.
Backoff(retries int) time.Duration
}
// BackoffConfig defines the parameters for the default backoff strategy.
type BackoffConfig struct {
// MaxDelay is the upper bound of backoff delay.
MaxDelay time.Duration
// baseDelay is the amount of time to wait before retrying after the first
// failure.
BaseDelay time.Duration
// factor is applied to the backoff after each retry.
Factor float64
// jitter provides a range to randomize backoff delays.
Jitter float64
}
/*
// NOTE TODO avoid use unexcept config.
func (bc *BackoffConfig) Fix() {
md := bc.MaxDelay
*bc = DefaultBackoffConfig
if md > 0 {
bc.MaxDelay = md
}
}
*/
// Backoff returns the amount of time to wait before the next retry given
// the number of consecutive failures.
func (bc *BackoffConfig) Backoff(retries int) time.Duration {
if retries == 0 {
return bc.BaseDelay
}
backoff, max := float64(bc.BaseDelay), float64(bc.MaxDelay)
for backoff < max && retries > 0 {
backoff *= bc.Factor
retries--
}
if backoff > max {
backoff = max
}
// Randomize backoff delays so that if a cluster of requests start at
// the same time, they won't operate in lockstep.
backoff *= 1 + bc.Jitter*(rand.Float64()*2-1)
if backoff < 0 {
return 0
}
return time.Duration(backoff)
}

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)
}

View File

@@ -0,0 +1,82 @@
package netutil
import (
"net"
"sync"
"sync/atomic"
)
var (
// ErrLimitListener listen limit error.
ErrLimitListener = &LimitListenerError{}
)
// LimitListenerError limit max connections of listener.
type LimitListenerError struct{}
// Temporary is the error temporary.
func (l *LimitListenerError) Temporary() bool { return true }
// Timeout is the error a timeout.
func (l *LimitListenerError) Timeout() bool { return true }
// Error return error message of error.
func (l *LimitListenerError) Error() string { return "LimitListener: limit" }
// LimitListener returns a Listener that accepts at most n simultaneous
// connections from the provided Listener.
func LimitListener(l net.Listener, n int32) net.Listener {
return &limitListener{l, 0, n, make(chan struct{}, n)}
}
type limitListener struct {
net.Listener
cur int32
max int32
sem chan struct{}
}
func (l *limitListener) acquire() (ok bool) {
ok = true
if cur := atomic.AddInt32(&l.cur, 1); cur > l.max {
select {
case l.sem <- struct{}{}:
default:
ok = false
}
}
return
}
func (l *limitListener) release() {
if cur := atomic.AddInt32(&l.cur, -1); cur >= l.max {
<-l.sem
}
}
func (l *limitListener) Accept() (net.Conn, error) {
ok := l.acquire()
c, err := l.Listener.Accept()
if err != nil {
l.release()
return nil, err
}
if !ok {
l.release()
c.Close()
return nil, ErrLimitListener
}
return &limitListenerConn{Conn: c, release: l.release}, nil
}
type limitListenerConn struct {
net.Conn
once sync.Once
release func()
}
func (l *limitListenerConn) Close() error {
err := l.Conn.Close()
l.once.Do(l.release)
return err
}

View File

@@ -0,0 +1,16 @@
package netutil
import "testing"
func TestListen(t *testing.T) {
l := limitListener{cur: 0, max: 10, sem: make(chan struct{}, 10)}
ok := l.acquire()
t.Logf("LimitListener: acquire status: %t", ok)
if l.cur != 1 {
t.Errorf("LimitListener: expceted cur=1 but got %d", l.cur)
}
l.release()
if l.cur != 0 {
t.Errorf("LimitListener: expceted cur=0 but got %d", l.cur)
}
}