1、多协程顺序打印A、B、C
//(1)使用channel控制打印次数
func main() {
var wg sync.WaitGroup
wg.Add(3)
a := make(chan bool, 1)
b := make(chan bool, 1)
c := make(chan bool, 1)
flag := make(chan int, 1)
flag <- 0
a <- true
defer cancel()
go func() {
defer func() {
wg.Done()
}()
for {
select {
case <- a:
f := <- flag
if f > 10 {
wg.Done()
wg.Done()
return
} else {
flag <- f + 1
fmt.Println(f)
}
fmt.Println("A")
b <- true
}
}
}()
go func() {
defer func() {
wg.Done()
}()
for {
select {
case <- b:
f := <- flag
if f > 10 {
wg.Done()
wg.Done()
return
} else {
flag <- f + 1
fmt.Println(f)
}
fmt.Println("B")
c <- true
}
}
}()
go func() {
defer func() {
wg.Done()
}()
for {
select {
case <- c:
f := <- flag
if f > 10 {
wg.Done()
wg.Done()
return
} else {
flag <- f + 1
fmt.Println(f)
}
fmt.Println("C")
a <- true
}
}
}()
wg.Wait()
return
}
(2)使用atomic原子锁控制打印次数
func main() {
var wg sync.WaitGroup
wg.Add(3)
var count int32
a := make(chan bool, 1)
b := make(chan bool, 1)
c := make(chan bool, 1)
a <- true
go func() {
defer func() {
wg.Done()
}()
for count < 10 {
atomic.AddInt32(&count, 1)
<- a
fmt.Println("A")
b <- true
}
}()
go func() {
defer func() {
wg.Done()
}()
for count < 10 {
atomic.AddInt32(&count, 1)
<- b
fmt.Println("B")
c <- true
}
}()
go func() {
defer func() {
wg.Done()
}()
for count < 10 {
atomic.AddInt32(&count, 1)
<- c
fmt.Println("C")
a <- true
}
}()
wg.Wait()
return
}
(3)使用context控制打印时长
func main() {
var wg sync.WaitGroup
wg.Add(3)
a := make(chan bool, 1)
b := make(chan bool, 1)
c := make(chan bool, 1)
a <- true
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second * 1)
defer cancel()
go func() {
defer func() {
wg.Done()
}()
for {
select {
case <-ctx.Done():
return
default:
<-a
fmt.Println("A")
b <- true
}
}
}()
go func() {
defer func() {
wg.Done()
}()
for {
select {
case <-ctx.Done():
return
default:
<- b
fmt.Println("B")
c <- true
}
}
}()
go func() {
defer func() {
wg.Done()
}()
for {
select {
case <-ctx.Done():
return
default:
<- c
fmt.Println("C")
a <- true
}
}
}()
wg.Wait()
return
}
2. 接口限流的常见做法
(1)固定窗口: 在一定时间内设置处理次数上限,定时重置
package main
import (
"context"
"fmt"
"sync"
"time"
)
type FixedWindow struct {
size time.Duration // 窗口大小
maxRequests int // 最大请求数
requests int // 当前窗口内的请求数
lastReset int64 // 上次窗口重置时间(秒级时间戳)
mutex sync.Mutex // 重置锁
}
func NewFixedWindow(size time.Duration, maxRequests int) *FixedWindow {
return &FixedWindow{
size: size,
maxRequests: maxRequests,
lastReset: time.Now().Unix(),
}
}
func(window *FixedWindow) AllowRequest(ctx context.Context, now time.Time) bool {
window.mutex.Lock()
defer window.mutex.Unlock()
for{
select {
case <- ctx.Done():
return false
default:
// 时间间隔是否满足重置条件
if now.Unix() - window.lastReset >= int64(window.size.Seconds()) {
window.requests = 0
window.lastReset = now.Unix()
}
// 窗口内请求数是否超过最大值
if window.requests >= window.maxRequests {
return false
}
window.requests++
return true
}
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second * 2)
defer cancel()
window := NewFixedWindow(time.Second * 1,5)
for {
select {
case <-ctx.Done():
return
default:
now := time.Now()
format := now.Format("15:04:05.0000000")
if window.AllowRequest(ctx, now) {
fmt.Println(format + " 请求已执行")
} else {
fmt.Println(format + " 请求被限流,无法执行")
}
time.Sleep(100 * time.Millisecond)
}
}
}
(2)滑动窗口:设定单位时间内请求数上限,清除不在窗口内的请求
package main
import (
"context"
"fmt"
"sync"
"time"
)
type slidingWindow struct {
size time.Duration // 窗口大小
maxRequests int // 最大请求数
requests []time.Time // 当前窗口内的请求时间
mutex sync.Mutex // 请求锁
}
func NewSlidingWindow(windowSize time.Duration, maxRequests int) *slidingWindow {
return &slidingWindow{
size: windowSize,
maxRequests: maxRequests,
requests: make([]time.Time, 0),
}
}
func (window *slidingWindow) AllowRequest(ctx context.Context, now time.Time) bool {
window.mutex.Lock()
defer window.mutex.Unlock()
for{
select {
case <- ctx.Done():
return false
default:
// 移除不在窗口时间内的请求
for len(window.requests) > 0 && now.Sub(window.requests[0]) > window.size {
window.requests = window.requests[1:]
}
// 窗口内请求数是否超过最大值
if len(window.requests) >= window.maxRequests {
return false
}
window.requests = append(window.requests, now)
return true
}
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 3 * time.Second)
defer cancel()
window := NewSlidingWindow(1 * time.Second, 4) // 每秒最多允许4个请求
for{
select {
case <- ctx.Done():
return
default:
now := time.Now()
format := now.Format("15:04:05.0000000")
if window.AllowRequest(ctx, now) {
fmt.Println(format + " 请求已执行")
} else {
fmt.Println(format + " 请求被限流,无法执行")
}
time.Sleep(100 * time.Millisecond)
}
}
}
(3)漏桶:漏桶内容量未满即可处理请求
package main
import (
"context"
"fmt"
"time"
)
type LeakyBucket struct {
rate float64 // 漏桶速率,个/秒
capacity int // 漏桶容量
standing int // 桶内水量
lastLeakyTime int64 // 上次漏水的时间戳
}
func NewLeakyBucket(rate float64, cap int) *LeakyBucket {
return &LeakyBucket{
rate: rate,
capacity: cap,
standing: 0,
lastLeakyTime: time.Now().Unix(),
}
}
// AllowRequest 检查是否处理本次请求,是就返回true,否则返回false
func (bucket *LeakyBucket) AllowRequest(ctx context.Context, now time.Time) bool {
for {
select {
case <-ctx.Done():
return false
default:
// 漏水,根据时间间隔计算漏掉的水量
time := now.Unix() - bucket.lastLeakyTime
amount := int(float64(time) * bucket.rate)
fmt.Printf("amount: %d\n", amount)
if amount > 0 {
// 更新桶内水量
bucket.standing -= amount
if bucket.standing < 0 {
bucket.standing = 0
}
}
// 判断当前存量是否超过容量
if bucket.standing >= bucket.capacity {
return false
}
bucket.standing++
bucket.lastLeakyTime = now.Unix()
return true
}
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second*2)
defer cancel()
bucket := NewLeakyBucket(4, 5)
for {
select {
case <-ctx.Done():
return
default:
now := time.Now()
format := now.Format("15:04:05.0000000")
if bucket.AllowRequest(ctx, now) {
fmt.Println(format + " 请求已执行")
} else {
fmt.Println(format + " 请求被限流,无法执行")
}
time.Sleep(100 * time.Millisecond)
}
}
}
(4)令牌桶:以固定速率向桶内补充令牌,请求到来时根据桶内令牌判断是否能处理
package main
import (
"context"
"fmt"
"sync"
"time"
)
type TokenBucket struct {
rate float64 // 令牌添加到桶中的速率,个/秒
capacity int // 令牌桶容量
standing int // 令牌桶内令牌数量
lastDripTime int64 // 上次更新令牌数量的时间
mutex sync.Mutex // 互斥锁
}
func NewTokenBucket(rate float64, cap int) *TokenBucket {
return &TokenBucket{
rate: rate,
capacity: cap,
standing: 0,
lastDripTime: time.Now().Unix(),
}
}
// AllowRequest 检查是否可以从桶中移除一个令牌。如果可以,它移除一个令牌并返回 true。如果不可以,它返回 false。
func (bucket *TokenBucket) AllowRequest(ctx context.Context, now time.Time) bool {
bucket.mutex.Lock()
defer bucket.mutex.Unlock()
for {
select {
case <-ctx.Done():
return false
default:
// 计算从上次更新令牌数量到现在应该补充的令牌数量
leakyNum := int(float64(now.Unix()-bucket.lastDripTime) * bucket.rate)
if leakyNum > 0 {
bucket.lastDripTime = now.Unix()
bucket.standing += leakyNum
if bucket.standing > bucket.capacity {
bucket.standing = bucket.capacity
}
}
// 判断当前是否有可以消耗的令牌
if bucket.standing > 0 {
bucket.standing--
return true
}
return false
}
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second*2)
defer cancel()
bucket := NewTokenBucket(4, 5)
for {
select {
case <-ctx.Done():
return
default:
now := time.Now()
format := now.Format("15:04:05.0000000")
if bucket.AllowRequest(ctx, now) {
fmt.Println(format + " 请求已执行")
} else {
fmt.Println(format + " 请求被限流,无法执行")
}
time.Sleep(100 * time.Millisecond)
}
}
}
四种算法的对比
算法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
固定窗口 | - 简单直观,易于实现 - 适用于稳定的流量控制 - 易于实现速率控制 | .- 无法应对短时间内的突发流量 - 流量不均匀时可能导致突发流量 | - 稳定的流量控制,不需要保证请求均匀分布的场景 |
滑动窗口 | - 平滑处理突发流量 - 颗粒度更小,可以提供更精确的限流控制 | .- 实现相对复杂 - 需要维护滑动窗口的状态 - 存在较高的内存消耗 | - 需要平滑处理突发流量的场景 |
漏桶 | - 平滑处理突发流量 - 可以固定输出速率,有效防止过载 -对突发流量的处理不够灵活 | .- 对突发流量的处理不够灵活 - 无法应对流量波动的情况 | - 需要固定输出速率的场景 -避免流量的突然增大对系统的冲击的场景 |
令牌桶 | - 平滑处理突发流量 - 可以动态调整限流规则 - 能适应不同时间段的流量变化 | .- 实现相对复杂 - 需要维护令牌桶的状态 | - 需要动态调整限流规则的场景 - 需要平滑处理突发流量的场景 |
todo
// 1.将容量设置为带缓冲的通道类型,起一个协程做动态补充
package main
import (
"context"
"fmt"
"sync"
"time"
)
type bucket struct {
rate float64
standing chan int
capacity int
}
func NewBucket(rate float64, cap int) *bucket {
return &bucket{
rate: rate,
capacity: cap,
standing: make(chan int, cap),
}
}
func (b *bucket) replenish(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
default:
if len(b.standing) >= b.capacity {
return
}
time.Sleep(time.Second / time.Duration(b.rate))
b.standing <- 1
fmt.Printf("当前容量:%d\n", len(b.standing))
}
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second*2)
defer cancel()
b := NewBucket(2, 6)
var wg sync.WaitGroup
wg.Add(1)
go b.replenish(ctx, &wg)
wg.Wait()
}
3. 截取切片
func subSlice[S ~[]E, E any](s S, skip, limit int64) S {
if skip == 0 && limit == 0 {
return s
}
start := skip
if start < 0 {
start = 0
}
if limit < 0 {
limit = 0
}
end := start + limit
count := int64(len(s))
if start >= count {
return nil
}
if end == 0 || end > count {
end = count
}
return s[start:end]
}