原题是这篇博客
https://blog.csdn.net/qq_28163175/article/details/75287877#commentBox
一个关于并发和锁的golang面试题, 题目为在一个高并发的web服务器中,要限制IP的频繁访问,每个IP三分钟之内只能访问一次。
考虑到高并发场景,不免遇到来访ip数量爆表的情况,为了合理应用计算机资源, 不能每时每刻,都去计算那些IP已过期, 因此想法是做一个以最近一个将要过期的IP距现在还剩多少时间的Duraction作为清理任务的定时时间
核心实现
// An highlighted block
import (
"fmt"
"sync"
"time"
)
type IpItem struct {
Ip string
LifeSpan time.Duration // 生命周期
CreateOn time.Time // 创建时间
}
type IpTable struct {
sync.RWMutex
CleanerDuraction time.Duration // 触发定时清理器的时间
Cleaner *time.Timer // 定时清理器
Items map[string]IpItem // ips
}
func NewIpTable() *IpTable {
return &IpTable{
Items: make(map[string]IpItem),
}
}
func (this *IpTable) Visit(ip string) bool {
this.Lock()
item := IpItem{
Ip: ip,
LifeSpan: 3 * time.Minute,
CreateOn: time.Now(),
}
if _, ok := this.Items[ip]; ok {
this.Unlock()
fmt.Printf("%s is limited\n", ip)
return true
}
this.Items[ip] = item
cleannerDuraction := this.CleanerDuraction
this.Unlock()
fmt.Printf("add %s to table\n", ip)
if cleannerDuraction == 0 {
this.cleanerCheck()
}
return false
}
func (this *IpTable) cleanerCheck() {
this.Lock()
defer this.Unlock()
fmt.Printf("start timer cleaner Duraction after %.2f s\n", this.CleanerDuraction.Seconds())
if this.Cleaner != nil {
this.Cleaner.Stop()
}
// 遍历当前限制的ip, 遇到过期的将其删掉
// 其余的则从中找到最近一个将要过期的IP并且将它还有多少时间过期作为下一次清理任务的定时时间
now := time.Now()
smallestDuracton := 0 * time.Second
for ip, item := range this.Items {
lifeSpan := item.LifeSpan
createOn := item.CreateOn
if now.Sub(createOn) >= lifeSpan {
fmt.Println("delete ip", ip)
delete(this.Items, ip)
} else {
if smallestDuracton == 0 || lifeSpan-now.Sub(createOn) < smallestDuracton {
smallestDuracton = lifeSpan - now.Sub(createOn)
}
}
}
this.CleanerDuraction = smallestDuracton
// 将最近一个将要过期的IP距离现在的时间作为启动清理任务的定时时间
if this.CleanerDuraction > 0 {
fn := func() {
go this.cleanerCheck()
}
this.Cleaner = time.AfterFunc(this.CleanerDuraction, fn)
}
}
测试代码
// An highlighted block
func TestIpTable(t *testing.T) {
var (
success int64
wg sync.WaitGroup
)
table := NewIpTable()
for j := 0; j < 1000; j++ {
for i := 0; i < 100; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
ip := fmt.Sprintf("192.168.1.%d", index)
if !table.Visit(ip) {
atomic.AddInt64(&success, 1)
}
}(i)
}
}
wg.Wait()
fmt.Println("end and success is", success)
}