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

32
library/rate/BUILD Normal file
View File

@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["rate.go"],
importpath = "go-common/library/rate",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/rate/limit:all-srcs",
"//library/rate/vegas:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

51
library/rate/limit/BUILD Normal file
View File

@@ -0,0 +1,51 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["limit_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//library/rate:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["limit.go"],
importpath = "go-common/library/rate/limit",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/container/queue/aqm:go_default_library",
"//library/log:go_default_library",
"//library/rate:go_default_library",
"//library/rate/vegas:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/rate/limit/bench/stress/cmd:all-srcs",
"//library/rate/limit/bench/stress/conf:all-srcs",
"//library/rate/limit/bench/stress/dao:all-srcs",
"//library/rate/limit/bench/stress/http:all-srcs",
"//library/rate/limit/bench/stress/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,2 @@
# v1.0.0
1.

View File

@@ -0,0 +1,6 @@
# Owner
lintanghui
# Author
lintanghui
# Reviewer

View File

@@ -0,0 +1,6 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- lintanghui
reviewers:
- lintanghui

View File

@@ -0,0 +1,14 @@
# stress
# 项目简介
1.框架压测专用
# 编译环境
# 依赖包
# 编译执行

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["stress-test.toml"],
importpath = "go-common/library/rate/limit/bench/stress/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//library/net/trace:go_default_library",
"//library/rate/limit/bench/stress/conf:go_default_library",
"//library/rate/limit/bench/stress/http: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,60 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"go-common/library/log"
"go-common/library/net/trace"
"go-common/library/rate/limit/bench/stress/conf"
"go-common/library/rate/limit/bench/stress/http"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
// init log
log.Init(conf.Conf.Log)
defer log.Close()
log.Info("stress start")
// init trace
trace.Init(conf.Conf.Tracer)
defer trace.Close()
// ecode init
// ecode.Init(conf.Conf.Ecode)
// service init
http.Init(conf.Conf)
// init pprof conf.Conf.Perf
go func() {
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
fmt.Println("go sig!!!!!!!!")
log.Info("stress get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("stress exit")
os.Exit(0)
return
case syscall.SIGHUP:
os.Exit(0)
default:
os.Exit(0)
return
}
}
}()
ch := make(chan struct{})
<-ch
}

View File

@@ -0,0 +1,121 @@
# This is a TOML document. Boom
version = "1.0.0"
user = "nobody"
pid = "/tmp/stress.pid"
dir = "./"
perf = "0.0.0.0:6420"
trace = false
debug = false
[log]
vLevel = 1
[tracer]
proto = "udp"
addr = "172.16.33.46:5140"
tag = "mypro"
[bm]
[bm.inner]
addr = "0.0.0.0:9001"
timeout = "1s"
[bm.local]
addr = "0.0.0.0:9002"
timeout = "1s"
[identify]
whiteAccessKey = ""
whiteMid = 0
[identify.app]
key = "6a29f8ed87407c11"
secret = "d3c5a85f5b895a03735b5d20a273bc57"
[identify.memcache]
name = "go-business/identify"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 5
idle = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[identify.host]
auth = "http://passport.bilibili.com"
secret = "http://open.bilibili.com"
[identify.httpClient]
key = "f022126a8a365e20"
secret = "b7b86838145d634b487e67b811b8fab2"
dial = "30ms"
timeout = "100ms"
keepAlive = "60s"
[identify.httpClient.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[identify.httpClient.url]
"http://passport.bilibili.co/intranet/auth/tokenInfo" = {timeout = "100ms"}
"http://passport.bilibili.co/intranet/auth/cookieInfo" = {timeout = "100ms"}
"http://open.bilibili.co/api/getsecret" = {timeout = "500ms"}
[ecode]
domain = "uat-api.bilibili.co"
all = "1h"
diff = "5m"
[ecode.clientconfig]
key = "c1a1cb2d89c33794"
secret = "dda47eeca111e03e6845017505baea13"
dial = "2000ms"
timeout = "2s"
keepAlive = "10s"
timer = 128
[ecode.clientconfig.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[mysql]
addr = "127.0.0.1:3306"
dsn = "test:test@tcp(127.0.0.1:3306)/test?timeout=200ms&readTimeout=200ms&writeTimeout=200ms&parseTime=true&loc=Local&charset=utf8,utf8mb4"
readDSN = ["test:test@tcp(127.0.0.2:3306)/test? timeout=200ms&readTimeout=200ms&writeTimeout=200ms&parseTime=true&loc=Local&charset=utf8,utf8mb4","test:test@tcp(127.0.0.3:3306)/test?timeout=200ms&readTimeout=200ms&writeTimeout=200ms&parseTime=true&loc=Local&charset=utf8,utf8mb4"]
active = 20
idle = 10
idleTimeout ="4h"
queryTimeout = "100ms"
execTimeout = "100ms"
tranTimeout = "200ms"
[mysql.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[redis]
name = "stress"
proto = "tcp"
addr = ""
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1m"
[memcache]
name = "stress"
proto = "tcp"
addr = ""
idle = 5
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "24h"

View File

@@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/library/rate/limit/bench/stress/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/trace:go_default_library",
"//vendor/github.com/BurntSushi/toml: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,91 @@
package conf
import (
"flag"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/database/sql"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/trace"
"github.com/BurntSushi/toml"
)
// global var
var (
confPath string
// Conf config
Conf = &Config{}
)
// Config config set
type Config struct {
// elk
Log *log.Config
// http
BM *HTTPServers
// tracer
Tracer *trace.Config
// redis
Redis *redis.Config
// memcache
Memcache *memcache.Config
// MySQL
MySQL *sql.Config
// ecode
Ecode *ecode.Config
}
// HTTPServers Http Servers
type HTTPServers struct {
Outer *bm.ServerConfig
Inner *bm.ServerConfig
Local *bm.ServerConfig
}
func init() {
flag.StringVar(&confPath, "conf", "./stress-test.toml", "default config path")
}
// Init init conf
func Init() error {
if confPath != "" {
return local()
}
s := `# This is a TOML document. Boom
version = "1.0.0"
user = "nobody"
pid = "/tmp/stress.pid"
dir = "./"
perf = "0.0.0.0:6420"
trace = false
debug = false
[log]
#dir = "/data/log/stress"
#[log.agent]
# taskID = "000161"
# proto = "unixgram"
# addr = "/var/run/lancer/collector.sock"
# chan = 10240
[bm]
[bm.inner]
addr = "0.0.0.0:9001"
timeout = "1s"
[bm.local]
addr = "0.0.0.0:9002"
timeout = "1s"`
_, err := toml.Decode(s, &Conf)
return err
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}

View File

@@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["dao.go"],
importpath = "go-common/library/rate/limit/bench/stress/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/rate/limit/bench/stress/conf: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,48 @@
package dao
import (
"context"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
xsql "go-common/library/database/sql"
"go-common/library/rate/limit/bench/stress/conf"
)
// Dao dao
type Dao struct {
c *conf.Config
mc *memcache.Pool
redis *redis.Pool
db *xsql.DB
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
mc: memcache.NewPool(c.Memcache),
redis: redis.NewPool(c.Redis),
db: xsql.NewMySQL(c.MySQL),
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.mc.Close()
d.redis.Close()
d.db.Close()
}
// Ping dao ping
func (d *Dao) Ping(c context.Context) error {
return d.pingMC(c)
}
// pingMc ping
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
return
}

View File

@@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/library/rate/limit/bench/stress/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/rate:go_default_library",
"//library/rate/limit:go_default_library",
"//library/rate/limit/bench/stress/conf:go_default_library",
"//library/rate/limit/bench/stress/service:go_default_library",
"//library/rate/vegas: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,130 @@
package http
import (
"hash/crc32"
"math/rand"
"strconv"
"sync/atomic"
"time"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/rate"
"go-common/library/rate/limit"
"go-common/library/rate/limit/bench/stress/conf"
"go-common/library/rate/limit/bench/stress/service"
"go-common/library/rate/vegas"
)
var (
svc *service.Service
req int64
qps int64
)
// Init init
func Init(c *conf.Config) {
rand.Seed(time.Now().Unix())
initService(c)
// init router
engineInner := bm.DefaultServer(c.BM.Inner)
outerRouter(engineInner)
if err := engineInner.Start(); err != nil {
log.Error("xhttp.Serve error(%v)", err)
panic(err)
}
engineLocal := bm.DefaultServer(c.BM.Local)
localRouter(engineLocal)
if err := engineLocal.Start(); err != nil {
log.Error("xhttp.Serve error(%v)", err)
panic(err)
}
if log.V(1) {
go calcuQPS()
}
}
// initService init services.
func initService(c *conf.Config) {
// idfSvc = identify.New(c.Identify)
svc = service.New(c)
}
// outerRouter init outer router api path.
func outerRouter(e *bm.Engine) {
v := vegas.New()
go func() {
ticker := time.NewTicker(time.Second * 3)
defer ticker.Stop()
for {
<-ticker.C
m := v.Stat()
log.Info("vegas: limit(%d) inFlight(%d) minRtt(%v) rtt(%v)", m.Limit, m.InFlight, m.MinRTT, m.LastRTT)
}
}()
l := limit.New(nil)
//init api
e.GET("/monitor/ping", ping)
group := e.Group("/stress")
group.GET("/normal", aqmTest)
group.GET("/vegas", func(c *bm.Context) {
start := time.Now()
done, success := v.Acquire()
if !success {
done(time.Time{}, rate.Ignore)
c.AbortWithStatus(509)
return
}
defer done(start, rate.Success)
c.Next()
}, aqmTest)
group.GET("/attack", func(c *bm.Context) {
done, err := l.Allow(c)
defer done(rate.Success)
if err != nil {
c.AbortWithStatus(509)
return
}
c.Next()
}, aqmTest)
}
func calcuQPS() {
var creq, breq int64
for {
time.Sleep(time.Second * 5)
creq = atomic.LoadInt64(&req)
delta := creq - breq
atomic.StoreInt64(&qps, delta/5)
breq = creq
log.Info("HTTP QPS:%d", atomic.LoadInt64(&qps))
}
}
func aqmTest(c *bm.Context) {
params := c.Request.Form
sleep, err := strconv.ParseInt(params.Get("sleep"), 10, 64)
if err == nil {
time.Sleep(time.Millisecond * time.Duration(sleep))
}
atomic.AddInt64(&req, 1)
for i := 0; i < 3000+rand.Intn(3000); i++ {
crc32.Checksum([]byte(`testasdwfwfsddsfgwddcscsc
http://git.bilibili.co/platform/go-common/merge_requests/new?merge_request%5Bsource_branch%5D=stress%2Fcodel`), crc32.IEEETable)
}
}
// ping check server ok.
func ping(c *bm.Context) {
}
// innerRouter init local router api path.
func localRouter(e *bm.Engine) {
//init api
e.GET("/monitor/ping", ping)
group := e.Group("/x/main/stress")
{
group.GET("", aqmTest)
}
}

View File

@@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["service.go"],
importpath = "go-common/library/rate/limit/bench/stress/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/rate/limit/bench/stress/conf: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,18 @@
package service
import (
"go-common/library/rate/limit/bench/stress/conf"
)
// Service struct
type Service struct {
c *conf.Config
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
}
return s
}

View File

@@ -0,0 +1,60 @@
package limit
import (
"context"
"time"
"go-common/library/container/queue/aqm"
"go-common/library/log"
"go-common/library/rate"
"go-common/library/rate/vegas"
)
var _ rate.Limiter = &Limiter{}
// New returns a new Limiter that allows events up to adaptive rtt.
func New(c *aqm.Config) *Limiter {
l := &Limiter{
rate: vegas.New(),
queue: aqm.New(c),
}
go func() {
ticker := time.NewTicker(time.Second * 1)
defer ticker.Stop()
for {
<-ticker.C
v := l.rate.Stat()
q := l.queue.Stat()
log.Info("rate/limit: limit(%d) inFlight(%d) minRtt(%v) rtt(%v) codel packets(%d)", v.Limit, v.InFlight, v.MinRTT, v.LastRTT, q.Packets)
}
}()
return l
}
// Limiter use tcp vegas + codel for adaptive limit.
type Limiter struct {
rate *vegas.Vegas
queue *aqm.Queue
}
// Allow immplemnet rate.Limiter.
// if error is returned,no need to call done()
func (l *Limiter) Allow(ctx context.Context) (func(rate.Op), error) {
var (
done func(time.Time, rate.Op)
err error
ok bool
)
if done, ok = l.rate.Acquire(); !ok {
// NOTE exceed max inflight, use queue
if err = l.queue.Push(ctx); err != nil {
done(time.Time{}, rate.Ignore)
return func(rate.Op) {}, err
}
}
start := time.Now()
return func(op rate.Op) {
done(start, op)
l.queue.Pop()
}, nil
}

View File

@@ -0,0 +1,59 @@
package limit
import (
"context"
"testing"
"time"
"go-common/library/rate"
)
func worker(qps int64, ch chan struct{}) {
for {
<-ch
time.Sleep(time.Duration(int64(time.Second) / qps))
}
}
func TestRateSuccess(t *testing.T) {
ch := make(chan struct{})
go worker(100, ch)
failed := producer(New(nil), 100, ch)
if failed > 0 {
t.Fatalf("Should be rejected 0 time,but (%d)", failed)
}
}
func TestRateFail(t *testing.T) {
ch := make(chan struct{})
go worker(100, ch)
failed := producer(New(nil), 200, ch)
if failed < 900 {
t.Fatalf("Should be rejected more than 900 times,but (%d)", failed)
}
}
func TestRateFailMuch(t *testing.T) {
ch := make(chan struct{})
go worker(10, ch)
failed := producer(New(nil), 200, ch)
if failed < 1600 {
t.Fatalf("Should be rejected more than 1600 times,but (%d)", failed)
}
}
func producer(l *Limiter, qps int64, ch chan struct{}) (failed int) {
for i := 0; i < int(qps)*10; i++ {
go func() {
done, err := l.Allow(context.Background())
defer done(rate.Success)
if err == nil {
ch <- struct{}{}
} else {
failed++
}
}()
time.Sleep(time.Duration(int64(time.Second) / qps))
}
return
}

22
library/rate/rate.go Normal file
View File

@@ -0,0 +1,22 @@
package rate
import (
"context"
)
// Op operations type.
type Op int
const (
// Success opertion type: success
Success Op = iota
// Ignore opertion type: ignore
Ignore
// Drop opertion type: drop
Drop
)
// Limiter limit interface.
type Limiter interface {
Allow(ctx context.Context) (func(Op), error)
}

42
library/rate/vegas/BUILD Normal file
View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["vegas_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//library/rate:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"sample.go",
"vegas.go",
],
importpath = "go-common/library/rate/vegas",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/rate: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,46 @@
package vegas
import (
"sync/atomic"
)
type sample struct {
count int64
maxInFlight int64
drop int64
// nanoseconds
totalRTT int64
}
func (s *sample) Add(rtt int64, inFlight int64, drop bool) {
if drop {
atomic.StoreInt64(&s.drop, 1)
}
for max := atomic.LoadInt64(&s.maxInFlight); max < inFlight; max = atomic.LoadInt64(&s.maxInFlight) {
if atomic.CompareAndSwapInt64(&s.maxInFlight, max, inFlight) {
break
}
}
atomic.AddInt64(&s.totalRTT, rtt)
atomic.AddInt64(&s.count, 1)
}
func (s *sample) RTT() int64 {
count := atomic.LoadInt64(&s.count)
if count == 0 {
return 0
}
return atomic.LoadInt64(&s.totalRTT) / count
}
func (s *sample) MaxInFlight() int64 {
return atomic.LoadInt64(&s.maxInFlight)
}
func (s *sample) Count() int64 {
return atomic.LoadInt64(&s.count)
}
func (s *sample) Drop() bool {
return atomic.LoadInt64(&s.drop) == 1
}

137
library/rate/vegas/vegas.go Normal file
View File

@@ -0,0 +1,137 @@
package vegas
import (
"math"
"math/rand"
"sync"
"sync/atomic"
"time"
"go-common/library/rate"
)
const (
_minWindowTime = int64(time.Millisecond * 500)
_maxWindowTime = int64(time.Millisecond * 2000)
_minLimit = 8
_maxLimit = 2048
)
// Stat is the Statistics of vegas.
type Stat struct {
Limit int64
InFlight int64
MinRTT time.Duration
LastRTT time.Duration
}
// New new a rate vegas.
func New() *Vegas {
v := &Vegas{
probes: 100,
limit: _minLimit,
}
v.sample.Store(&sample{})
return v
}
// Vegas tcp vegas.
type Vegas struct {
limit int64
inFlight int64
updateTime int64
minRTT int64
sample atomic.Value
mu sync.Mutex
probes int64
}
// Stat return the statistics of vegas.
func (v *Vegas) Stat() Stat {
return Stat{
Limit: atomic.LoadInt64(&v.limit),
InFlight: atomic.LoadInt64(&v.inFlight),
MinRTT: time.Duration(atomic.LoadInt64(&v.minRTT)),
LastRTT: time.Duration(v.sample.Load().(*sample).RTT()),
}
}
// Acquire No matter success or not,done() must be called at last.
func (v *Vegas) Acquire() (done func(time.Time, rate.Op), success bool) {
inFlight := atomic.AddInt64(&v.inFlight, 1)
if inFlight <= atomic.LoadInt64(&v.limit) {
success = true
}
return func(start time.Time, op rate.Op) {
atomic.AddInt64(&v.inFlight, -1)
if op == rate.Ignore {
return
}
end := time.Now().UnixNano()
rtt := end - start.UnixNano()
s := v.sample.Load().(*sample)
if op == rate.Drop {
s.Add(rtt, inFlight, true)
} else if op == rate.Success {
s.Add(rtt, inFlight, false)
}
if end > atomic.LoadInt64(&v.updateTime) && s.Count() >= 16 {
v.mu.Lock()
defer v.mu.Unlock()
if v.sample.Load().(*sample) != s {
return
}
v.sample.Store(&sample{})
lastRTT := s.RTT()
if lastRTT <= 0 {
return
}
updateTime := end + lastRTT*5
if lastRTT*5 < _minWindowTime {
updateTime = end + _minWindowTime
} else if lastRTT*5 > _maxWindowTime {
updateTime = end + _maxWindowTime
}
atomic.StoreInt64(&v.updateTime, updateTime)
limit := atomic.LoadInt64(&v.limit)
queue := float64(limit) * (1 - float64(v.minRTT)/float64(lastRTT))
v.probes--
if v.probes <= 0 {
maxFlight := s.MaxInFlight()
if maxFlight*2 < v.limit || maxFlight <= _minLimit {
v.probes = 3*limit + rand.Int63n(3*limit)
v.minRTT = lastRTT
}
}
if v.minRTT == 0 || lastRTT < v.minRTT {
v.minRTT = lastRTT
}
var newLimit float64
threshold := math.Sqrt(float64(limit)) / 2
if s.Drop() {
newLimit = float64(limit) - threshold
} else if s.MaxInFlight()*2 < v.limit {
return
} else {
if queue < threshold {
newLimit = float64(limit) + 6*threshold
} else if queue < 2*threshold {
newLimit = float64(limit) + 3*threshold
} else if queue < 3*threshold {
newLimit = float64(limit) + threshold
} else if queue > 6*threshold {
newLimit = float64(limit) - threshold
} else {
return
}
}
newLimit = math.Max(_minLimit, math.Min(_maxLimit, newLimit))
atomic.StoreInt64(&v.limit, int64(newLimit))
}
}, success
}

View File

@@ -0,0 +1,59 @@
package vegas
import (
"testing"
"time"
"go-common/library/rate"
)
func worker(qps int64, ch chan struct{}) {
for {
<-ch
time.Sleep(time.Duration(int64(time.Second) / qps))
}
}
func TestRateSuccess(t *testing.T) {
ch := make(chan struct{})
go worker(100, ch)
failed := producer(New(), 100, ch)
if failed > 0 {
t.Fatalf("Should be rejected 0 time,but (%d)", failed)
}
}
func TestRateFail(t *testing.T) {
ch := make(chan struct{})
go worker(100, ch)
failed := producer(New(), 200, ch)
if failed < 900 {
t.Fatalf("Should be rejected more than 900 times,but (%d)", failed)
}
}
func TestRateFailMuch(t *testing.T) {
ch := make(chan struct{})
go worker(10, ch)
failed := producer(New(), 200, ch)
if failed < 1600 {
t.Fatalf("Should be rejected more than 1600 times,but (%d)", failed)
}
}
func producer(v *Vegas, qps int64, ch chan struct{}) (failed int) {
for i := 0; i < int(qps)*10; i++ {
go func() {
start := time.Now()
done, success := v.Acquire()
defer done(start, rate.Success)
if success {
ch <- struct{}{}
} else {
failed++
}
}()
time.Sleep(time.Duration(int64(time.Second) / qps))
}
return
}