golang接口IP限流,IP黑名单,IP白名单的实例

增加中间件

可以选择普通模式和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等共享中间件中获取,而不应该从任何一个单点服务获取。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • Golang 限流器的使用和实现示例

    限流器是服务中非常重要的一个组件,在网关设计.微服务.以及普通的后台应用中都比较常见.它可以限制访问服务的频次和速率,防止服务过载,被刷爆. 限流器的算法比较多,常见的比如令牌桶算法.漏斗算法.信号量等.本文主要介绍基于漏斗算法的一个限流器的实现.文本也提供了其他几种开源的实现方法. 基于令牌桶的限流器实现 在golang 的官方扩展包 time 中(github/go/time),提供了一个基于令牌桶算法的限流器的实现. 原理 令牌桶限流器,有两个概念: 令牌:每次都需要拿到令牌后,才可以访问

  • 聊聊Golang中很好用的viper配置模块

    前言 viper 支持Yaml.Json. TOML.HCL 等格式,读取非常的方便. 安装 go get github.com/spf13/viper 如果提示找不到golang.org/x/text/这个库,是因为golang.org/x/text/这个库在GitHub上托管的路径不一致. 解决办法: 可以从https://github.com/golang/text下载源码下来,然后到$GOPATH/src下面创建golang.org/x/文件夹(已存在的忽略),把压缩包的文件解压到gol

  • Golang实现请求限流的几种办法(小结)

    在开发高并发系统时,有三把利器用来保护系统:缓存.降级和限流.那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了. 简单的并发控制 利用 channel 的缓冲设定,我们就可以来实现并发的限制.我们只要在执行并发的同时,往一个带有缓冲的 channel 里写入点东西(随便写啥,内容不重要).让并发的 goroutine在执行完成后把这个 channel 里的东西给读走.这样整个并发的数量就讲控制在这个 channel的缓冲区大小上. 比如我们可以用一个 bool

  • Golang使用第三方包viper读取yaml配置信息操作

    Golang有很多第三方包,其中的 viper 支持读取多种配置文件信息.本文只是做一个小小demo,用来学习入门用的. 1.安装 go get github.com/spf13/viper 2.编写一个yaml的配置文件,config.yaml database: host: 127.0.0.1 user: root dbname: test pwd: 123456 3.编写学习脚本main.go,读取config.yaml配置信息 package main import ( "fmt&quo

  • golang 使用 viper 读取自定义配置文件

    viper 支持 Yaml.Json. TOML.HCL 等格式,读取非常的方便. viper 官网有案例:https://github.com/spf13/viper go get github.com/spf13/viper 创建 config.yaml 文件 database: driver: mysql host: 127.0.0.1 port: 3306 username: blog dbname: blog password: 123456 建一个 config.go 用于初始化配置

  • golang常用库之配置文件解析库-viper使用详解

    golang常用库:gorilla/mux-http路由库使用 golang常用库:配置文件解析库-viper使用 golang常用库:操作数据库的orm框架-gorm基本使用 golang常用库:字段参数验证库-validator使用 一.viper简介 viper 配置管理解析库,是由大神 Steve Francia 开发,他在google领导着 golang 的产品开发,他也是 gohugo.io 的创始人之一,命令行解析库 cobra 开发者.总之,他在golang领域是专家,很牛的一个

  • 详解Java分布式IP限流和防止恶意IP攻击方案

    前言 限流是分布式系统设计中经常提到的概念,在某些要求不严格的场景下,使用Guava RateLimiter就可以满足.但是Guava RateLimiter只能应用于单进程,多进程间协同控制便无能为力.本文介绍一种简单的处理方式,用于分布式环境下接口调用频次管控. 如何防止恶意IP攻击某些暴露的接口呢(比如某些场景下短信验证码服务)?本文介绍一种本地缓存和分布式缓存集成方式判断远程IP是否为恶意调用接口的IP. 分布式IP限流 思路是使用redis incr命令,完成一段时间内接口请求次数的统

  • Redis Lua脚本实现ip限流示例

    目录 引言 相比Redis事务来说,Lua脚本有以下优点 Lua脚本 java代码 IP限流Lua脚本 引言 分布式限流最关键的是要将限流服务做成原子化,而解决方案可以使使用redis+lua或者nginx+lua技术进行实现,通过这两种技术可以实现的高并发和高性能.首先我们来使用redis+lua实现时间窗内某个接口的请求数限流,实现了该功能后可以改造为限流总并发/请求数和限制总资源数.Lua本身就是一种编程语言,也可以使用它实现复杂的令牌桶或漏桶算法.如下操作因是在一个lua脚本中(相当于原

  • 详解Golang实现请求限流的几种办法

    简单的并发控制 利用 channel 的缓冲设定,我们就可以来实现并发的限制.我们只要在执行并发的同时,往一个带有缓冲的 channel 里写入点东西(随便写啥,内容不重要).让并发的 goroutine在执行完成后把这个 channel 里的东西给读走.这样整个并发的数量就讲控制在这个 channel的缓冲区大小上. 比如我们可以用一个 bool 类型的带缓冲 channel 作为并发限制的计数器. chLimit := make(chan bool, 1) 然后在并发执行的地方,每创建一个新

  • Java使用Semaphore对单接口进行限流

    目录 一.实战说明 1.1 效果说明 1.2 核心知识点 二. 环境搭建 三.限流演示 3.1 并发请求工具 3.2 效果示例图 一.实战说明 1.1 效果说明 本篇主要讲如何使用Semaphore对单接口进行限流,例如有如下场景 a. A系统的有a接口主要给B系统调用,现在希望对B系统进行限流,例如处理峰值在100,超过100的请求快速失败 b. 接口作为总闸入口,希望限制所有外来访问,例如某个房间只能同时100个玩家在线,只有前面的处理完后面的才能继续请求 c. 其他类型场景,也就是资源固定

  • Springboot使用redis实现接口Api限流的实例

    前言 该篇介绍的内容如题,就是利用redis实现接口的限流(  某时间范围内 最大的访问次数 ) . 正文 惯例,先看下我们的实战目录结构: 首先是pom.xml 核心依赖: <!--用于redis数据库连接--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId>

  • Nginx限流和黑名单配置的策略

    目录 1 背景介绍 2 Nginx 的限流策略 2.1 limit_req_zone限制访问频率 2.2 limit_conn_zone限制最大连接数 3 黑名单设置 1 背景介绍 为了防止一些抢票助手所发起的一些无用请求,我们可以使用 nginx 中的限流策略进行限流操作.常见的限流算法:计数器.漏桶算法.令牌桶算法 Java高并发系统限流算法的应用 从作用上来说,漏桶和令牌桶算法最明显的区别就是是否允许突发流量(burst)的处理,漏桶算法能够强行限制数据的实时传输(处理)速率,对突发流量不

  • Springboot使用redis实现接口Api限流的示例代码

    前言 该篇介绍的内容如题,就是利用redis实现接口的限流(  某时间范围内 最大的访问次数 ) . 正文 惯例,先看下我们的实战目录结构: 首先是pom.xml 核心依赖: <!--用于redis数据库连接--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId>

  • golang接口IP限流,IP黑名单,IP白名单的实例

    增加中间件 可以选择普通模式和LUA脚本模式,建议选择普通模式,实际上不需要控制的那么精确. package Middlewares import ( "github.com/gin-gonic/gin" "strconv" "time" "voteapi/pkg/app/response" "voteapi/pkg/gredis" "voteapi/pkg/util" ) const

  • golang高并发限流操作 ping / telnet

    需求 当需要同时ping/telnet多个ip时,可以通过引入ping包/telnet包实现,也可以通过go调用cmd命令实现,不过后者调用效率较差,所以这里选择ping包和telnet包 还有就是高并发的问题,可以通过shell脚本或者go实现高并发,所以我选择的用go自带的协程实现,但是如果要同时处理1000+个ip,考虑到机器的性能,需要ratelimit控制开辟的go协程数量,这里主要写一下我的建议和淌过的坑 ping 参考链接: https://github.com/sparrc/go

随机推荐