时间:2021-05-23
可以选择普通模式和LUA脚本模式,建议选择普通模式,实际上不需要控制的那么精确。
package Middlewaresimport ( "github.com/gin-gonic/gin" "strconv" "time" "voteapi/pkg/app/response" "voteapi/pkg/gredis" "voteapi/pkg/util")const IP_LIMIT_NUM_KEY = "ipLimit:ipLimitNum"const IP_BLACK_LIST_KEY = "ipLimit:ipBlackList"var prefix = "{gateway}"var delaySeconds int64 = 60 // 观察时间跨度,秒var maxAttempts int64 = 10000 // 限制请求数var blackSeconds int64 = 0 // 封禁时长,秒,0-不封禁func GateWayPlus() gin.HandlerFunc { return func(c *gin.Context) { path := c.FullPath() clientIp := c.ClientIP() // redis配置集群时必须 param := make(map[string]string) param["path"] = path param["clientIp"] = clientIp if !main(param) { c.Abort() response.JsonResponseError(c, "当前IP请求过于频繁,暂时被封禁~") } }}func main(param map[string]string) bool { // 预知的IP黑名单 var blackList []string if util.InStringArray(param["clientIp"], blackList) { return false } // 预知的IP白名单 var whiteList []string if util.InStringArray(param["clientIp"], whiteList) { return false } blackKey := prefix + ":" + IP_BLACK_LIST_KEY limitKey := prefix + ":" + IP_LIMIT_NUM_KEY curr := time.Now().Unix() item := util.Md5(param["path"] + "|" + param["clientIp"]) return normal(blackKey, limitKey, item, curr)}// 普通模式func normal(blackKey string, limitKey string, item string, time int64) (res bool) { if blackSeconds > 0 { timeout, _ := gredis.RawCommand("HGET", blackKey, item) if timeout != nil { to, _ := strconv.Atoi(string(timeout.([]uint8))) if int64(to) > time { // 未解封 return false } // 已解封,移除黑名单 gredis.RawCommand("HDEL", blackKey, item) } } l, _ := gredis.RawCommand("HGET", limitKey, item) if l != nil { last, _ := strconv.Atoi(string(l.([]uint8))) if int64(last) >= maxAttempts { return false } } num, _ := gredis.RawCommand("HINCRBY", limitKey, item, 1) if ttl, _ := gredis.TTLKey(limitKey); ttl == int64(-1) { gredis.Expire(limitKey, int64(delaySeconds)) } if num.(int64) >= maxAttempts && blackSeconds > 0 { // 加入黑名单 gredis.RawCommand("HSET", blackKey, item, time+blackSeconds) // 删除记录 gredis.RawCommand("HDEL", limitKey, item) } return true}// LUA脚本模式// 支持redis集群部署func luaScript(blackKey string, limitKey string, item string, time int64) (res bool) { script := `local blackSeconds = tonumber(ARGV[5])if(blackSeconds > 0)then local timeout = redis.call('hget', KEYS[1], ARGV[1]) if(timeout ~= false) then if(tonumber(timeout) > tonumber(ARGV[2])) then return false end redis.call('hdel', KEYS[1], ARGV[1]) endendlocal last = redis.call('hget', KEYS[2], ARGV[1])if(last ~= false and tonumber(last) >= tonumber(ARGV[3]))then return falseendlocal num = redis.call('hincrby', KEYS[2], ARGV[1], 1)local ttl = redis.call('ttl', KEYS[2])if(ttl == -1)then redis.call('expire', KEYS[2], ARGV[4])endif(tonumber(num) >= tonumber(ARGV[3]) and blackSeconds > 0)then redis.call('hset', KEYS[1], ARGV[1], ARGV[2] + ARGV[5]) redis.call('hdel', KEYS[2], ARGV[1])endreturn true` result, err := gredis.RawCommand("EVAL", script, 2, blackKey, limitKey, item, time, maxAttempts, delaySeconds, blackSeconds) if err != nil { return false } if result == int64(1) { return true } else { return false }}补充:golang实现限制每秒多少次的限频操作
一些函数的执行可能会限制频率,比如某个api接口要求每秒最大请求30次。下面记录了自己写的限频和官方的限频
代码
// 加锁限频,输出次数大概率小于最大值func ExecLimit(lastExecTime *time.Time, l *sync.RWMutex ,maxTimes int, perDuration time.Duration, f func()) { l.Lock() defer l.Unlock() // per times cost time(s) SecondsPerTimes := float64(perDuration) / float64(time.Second) / float64(maxTimes) now := time.Now() interval := now.Sub(*lastExecTime).Seconds() if interval < SecondsPerTimes { time.Sleep(time.Duration(int64((SecondsPerTimes-interval)*1000000000)) * time.Nanosecond) } f() *lastExecTime = time.Now()}// 官方的,需要引用 "golang.org/x/time/rate"// 基本上可以达到满值,比自己写的更优func ExecLimit2(l *rate.Limiter, f func()) { go func() { l.Wait(context.Background()) f() }()}使用
func TestExecLimit(t *testing.T) { runtime.GOMAXPROCS(runtime.NumCPU()) go func() { var lastExecTime time.Time var l sync.RWMutex for { ExecLimit(&lastExecTime, &l, 10, time.Second, func() { fmt.Println("do") }) } }() select { case <-time.After(1 * time.Second): fmt.Println("1秒到时") }}func TestExecLimit2(t *testing.T) { runtime.GOMAXPROCS(runtime.NumCPU()) l := rate.NewLimiter(1, 30) go func() { for { ExecLimit2(l, func() { fmt.Println("do") }) } }() select { case <-time.After(1 * time.Second): fmt.Println("1秒到时") }}输出:
一秒内输出了<=10次 "do"
上述使用,定义在某个服务节点的全局变量lastExecTime仅仅会对该服务的函数f()操作限频,如果在负载均衡后,多个相同服务的节点,对第三方的接口累计限频,比如三个服务共同拉取第三方接口,合计限频为30次/s.
则,必须将lastExecTime的获取,从redis等共享中间件中获取,而不应该从任何一个单点服务获取。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
一、各类黑白名单的简介首先说明一下:UA白名单-->初始化阶段User-Agent白名单UA黑名单-->初始化阶段User-Agent黑名单IP白名单-->所有
这个是一个检测ip是否非法的php函数,适应于白名单,黑名单功能开发,主要场景应用于:api来源限制,访问限制等.复制代码代码如下:/***安全IP检测,支持I
要知道,淘宝也是有黑白名单的。众所周知,黑名单就是那些被拉黑了的客户,通常都是因为这些人有一些劣迹。那么,淘宝直播白名单是什么意思呢?白名单的概念与“黑名单”相
微信公众号需要配置IP白名单,该怎么修改配置的ip白名单呢?下面我们就来看看详细的教程。1、打开微信公众平台,输入账号密码登录后台。2、点击【基本配置】。3、找
一、背景我们在日常维护网站中,经常会遇到这样一个需求,为了封禁某些爬虫或者恶意用户对服务器的请求,我们需要建立一个动态的IP黑名单。对于黑名单之内的IP,拒绝提