gin接口限制请求频率

参与的业务模块今天被安全的同学玩坏了。一直疯狂掉用接口,然后服务器就挂了。涉及的接口需要处理大量的数据,并且需要处理很多条件。

有限流和缓存两种解决方案。考虑到正常业务下数据的访问量并不高,而且筛选条件较多,所以选用限流的方式解决。(我让你测)

吃水不忘挖井人,这是参考的博客:SpringBoot限制接口访问频率 - 这些错误千万不能犯

设计

两种设计方案。相同点是均采用访问ip+访问path作为redis的key。不同点是第一种采用stirng类型,第二种采用zset类型。

string

适用场景:某一ip在t时间内访问一个接口1次。
收到接口请求时,首先判断一下该key是否存在。若存在则返回,不存在则将key添加至redis中,并设置过期时间t。
在这里插入图片描述

为什么上面要说访问这种适合访问1次呢,我们将请求次数作为value

现在规定在5s内只能访问两次。我们分别在第1秒和第5s之间访问了一次。在第一访问的时候就已设置了过期时间。那么在第五秒过后该key就会过期。紧接着我又在第6s和第7s访问了两次。正常情况第7s这次不应该访问成功,因为[3,7]之间已经访问了两次5,6!

那如果每次都延长该key过期时间呢?
还是在5s内只能访问两次。我们分别在第1秒和第2秒之间访问了一次,在第6秒的时候该key还没有过期,并且value为2,依旧会被拦截。

所以采用string适合某一ip在t时间内访问一个接口1次的这种情况

zset

将时间戳(s)作为zset的分数,可以很好解决上面的问题。假如还是规定在5s内只能访问两次。那么每次请求只需要统计(当前时间戳-5s,当前时间戳] 区域的分数的个数是否大于2,就可以很好的解决上面的问题。
在这里插入图片描述

如果不考虑缓存回收问题,这样简直完美。
这里采用过期时间+逻辑删除。在每次访问时,可以将ts前的数据删除。并且在新建一个zset时为其添加一个较长的过期时间。如果只采用逻辑删除的话,创建的zset不会被删除,会越积越多。所以需要配合过期时间来回收创建的zset。(回收时间周期比较长,出现上述string类型的问题的概率比较低)

代码实现

定义一个中间件,如果超过次数限制直接拦截

middlewares

适用方法和jwt验证一样,在路由中 r.Use(RequestLimit()) 即可


type Limit struct {
	Count    int64 `json:"count"`	// 次数
	Interval int64 `json:"interval"` // 时间间隔
}

var limit Limit

func RequestLimit() func(c *gin.Context) {
	rdb, _, _ := dao.NewRedis()
	pkg.ScanGlobalConfigValue(context.Background(), "ep_event", "request_limit_config", &limit) // 这里可以删掉,修改为limit的初始化参数
	return func(c *gin.Context) {

		// 获取ip
		clientIp := c.ClientIP()
		path := c.Request.URL.Path
		t := time.Now().Unix()
		key := pkg.RedisRequestLimit + clientIp + path
		has, _ := rdb.Exists(context.Background(), key).Result()
		count, _ := rdb.ZCount(context.Background(), key, fmt.Sprintf("%d", t-limit.Interval), "+inf").Result()
		if has == 0 { // 如果是第一次创建,最长时间不超过1小时
			rdb.Expire(context.Background(), key, 1*time.Hour)
		}
		if count >= limit.Count { // 超出次数,限制
			c.Abort()
			c.JSON(http.StatusBadRequest, model.CommonReply{Code: model.ReplyCodeErr, Msg: "请求过于频繁"})
			return
		} else {
			rdb.ZAdd(context.Background(), key, &redis.Z{Score: float64(t), Member: strconv.Itoa(int(t))})
			// 删除窗口外的数据
			go func() {
				memberToRemove, _ := rdb.ZRangeByScore(context.Background(), key, &redis.ZRangeBy{
					Max: strconv.Itoa(int(t - limit.Interval)),
					Min: "0",
				}).Result()
				if len(memberToRemove) > 0 {
					rdb.ZRem(context.Background(), key, memberToRemove)
				}
			}()
		}
	}
}

相关推荐

  1. .NET Redis限制接口请求频率 滑动窗口算法

    2024-05-25 18:32:50       28 阅读
  2. Nginx限制频繁刷新

    2024-05-25 18:32:50       64 阅读
  3. Django 自定义中间件(IP限制频率、用户权限)

    2024-05-25 18:32:50       46 阅读
  4. 限制API接口访问速率

    2024-05-25 18:32:50       55 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-25 18:32:50       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-25 18:32:50       106 阅读
  3. 在Django里面运行非项目文件

    2024-05-25 18:32:50       87 阅读
  4. Python语言-面向对象

    2024-05-25 18:32:50       96 阅读

热门阅读

  1. 如何开展自动化测试工作,减少线上bug

    2024-05-25 18:32:50       33 阅读
  2. 【6】PostgreSQL 循环

    2024-05-25 18:32:50       28 阅读
  3. ubuntu 安装 kvm 启动虚拟机

    2024-05-25 18:32:50       31 阅读
  4. leetcode397周赛场

    2024-05-25 18:32:50       33 阅读
  5. mongoDB初体验

    2024-05-25 18:32:50       27 阅读
  6. 一个月速刷leetcodeHOT100 day08 两道DP题 一道子串

    2024-05-25 18:32:50       35 阅读
  7. uniapp Vue2钉钉h5开发pdf无法预览的问题

    2024-05-25 18:32:50       30 阅读
  8. leetcode725-Split Linked List in Parts

    2024-05-25 18:32:50       33 阅读