golang接口IP限流,IP黑名单,IP白名单的实例
作者:raoxiaoya 时间:2024-04-25 15:18:14
增加中间件
可以选择普通模式和LUA脚本模式,建议选择普通模式,实际上不需要控制的那么精确。
package Middlewares
import (
"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])
end
end
local last = redis.call('hget', KEYS[2], ARGV[1])
if(last ~= false and tonumber(last) >= tonumber(ARGV[3]))
then
return false
end
local 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])
end
if(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])
end
return 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等共享中间件中获取,而不应该从任何一个单点服务获取。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持asp之家。如有错误或未考虑完全的地方,望不吝赐教。
来源:https://blog.csdn.net/raoxiaoya/article/details/108997674
标签:golang,IP,限流
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
做设计还是做产品
2009-06-11 13:01:00
ASP如何跳出本次进入下一次循环
2008-10-23 13:46:00
Go操作redis与redigo的示例解析
2024-04-28 09:12:38
Vue 实现拨打电话操作
2024-05-09 15:09:53
浅谈Mybatis+mysql 存储Date类型的坑
2024-01-17 19:13:14
golang中如何保证精度的方法
2024-04-26 17:23:22
ASP.NET(AJAX+JSON)实现对象调用
2023-07-19 12:29:33
Python+uiautomator2实现手机锁屏解锁功能
2021-05-26 23:04:12
![](https://img.aspxhome.com/file/2023/4/95154_0s.jpg)
python数据可视化matplotlib绘制折线图示例
2023-05-20 23:01:56
![](https://img.aspxhome.com/file/2023/9/103159_0s.png)
Python使用Mediapipe对图像进行手部地标检测
2021-04-23 06:18:52
![](https://img.aspxhome.com/file/2023/5/114745_0s.jpg)
正确使用字体和颜色 让网页内容更易阅读
2007-09-13 18:45:00
![](https://img.aspxhome.com/file/UploadPic/20079/13/2007913185327997s.gif)
利用Python实现简单的Excel统计函数
2021-09-27 09:21:09
![](https://img.aspxhome.com/file/2023/2/110072_0s.png)
python第三方库visdom的使用入门教程
2021-12-08 22:32:51
![](https://img.aspxhome.com/file/2023/9/82719_0s.png)
Git的基本操作流程及工作区版本库暂存区的关系
2022-03-10 04:52:42
![](https://img.aspxhome.com/file/2023/0/131880_0s.jpg)
python异常处理、自定义异常、断言原理与用法分析
2023-06-12 02:36:08
![](https://img.aspxhome.com/file/2023/4/115034_0s.png)
SQL函数substr使用简介
2024-01-27 11:12:02
Python Pillow(PIL)库的用法详解
2022-01-31 13:43:26
![](https://img.aspxhome.com/file/2023/8/81458_0s.jpg)
十一个案例带你吃透Python函数参数
2021-07-02 08:11:12
Python进程的通信Queue、Pipe实例分析
2021-11-22 13:50:44
![](https://img.aspxhome.com/file/2023/8/65078_0s.png)
探究MySQL中索引和提交频率对InnoDB表写入速度的影响
2024-01-26 08:03:22
![](https://img.aspxhome.com/file/2023/0/124840_0s.png)