go语言web开发系列之十四:用gin框架实现基于ip地址的限流

一,安装用到的库

1,从命令行安装 x/time/rate库

iuhongdi@ku:~$ go get -u golang.org/x/time/rate

说明:刘宏缔的go森林是一个专注golang的博客,
          地址:https://blog.csdn.net/weixin_43881017

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,地址:

https://github.com/liuhongdi/digv14

2,功能说明:

          演示了针对ip的地址的限流,

          限制同一ip地址在单位时间内可以发起请求的次数

3,项目结构:如图:

三,配置文件

config/config.yaml

  1.  
    Database:
  2.  
    DBType: mysql
  3.  
    UserName: root
  4.  
    Password: password
  5.  
    Host: 127.0.0.1:3306
  6.  
    DBName: dig
  7.  
    Charset: utf8
  8.  
    ParseTime: True
  9.  
    MaxIdleConns: 10
  10.  
    MaxOpenConns: 30
  11.  
    Server:
  12.  
    RunMode: debug
  13.  
    HttpPort: 8000
  14.  
    ReadTimeout: 60
  15.  
    WriteTimeout: 60
  16.  
    Log:
  17.  
    LogFilePath: /data/gologs/logs
  18.  
    LogInfoFileName: info
  19.  
    LogWarnFileName: warn
  20.  
    LogFileExt: log
  21.  
    AccessLog:
  22.  
    LogFilePath: /data/gologs/logs
  23.  
    LogFileName: access
  24.  
    LogFileExt: log
  25.  
    Limiter:
  26.  
    CountPerSecond: 7

四,go代码说明

1,global/limiter.go

  1.  
    package global
  2.  
     
  3.  
    import (
  4.  
    "golang.org/x/time/rate"
  5.  
    "sync"
  6.  
    )
  7.  
     
  8.  
    type IPRateLimiter struct {
  9.  
    ips map[string]*rate.Limiter
  10.  
    mu *sync.RWMutex
  11.  
    r rate.Limit
  12.  
    b int
  13.  
    }
  14.  
     
  15.  
    var (
  16.  
    RateLimiter *IPRateLimiter
  17.  
    )
  18.  
     
  19.  
    // 创建一个RateLimiter
  20.  
    func SetupIPRateLimiter() (error) {
  21.  
    var r rate.Limit
  22.  
    r=1
  23.  
    b := LimiterSetting.CountPerSecond
  24.  
    RateLimiter = &IPRateLimiter{
  25.  
    ips: make(map[string]*rate.Limiter),
  26.  
    mu: &sync.RWMutex{},
  27.  
    r: r,
  28.  
    b: b,
  29.  
    }
  30.  
    return nil
  31.  
    }
  32.  
     
  33.  
    // 添加一个ip到map
  34.  
    func (i *IPRateLimiter) AddIP(ip string) *rate.Limiter {
  35.  
    i.mu.Lock()
  36.  
    defer i.mu.Unlock()
  37.  
     
  38.  
    limiter := rate.NewLimiter(i.r, i.b)
  39.  
    i.ips[ip] = limiter
  40.  
    return limiter
  41.  
    }
  42.  
     
  43.  
    //通过ip得到limiter
  44.  
    func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
  45.  
    i.mu.Lock()
  46.  
    limiter, exists := i.ips[ip]
  47.  
    if !exists {
  48.  
    i.mu.Unlock()
  49.  
    return i.AddIP(ip)
  50.  
    }
  51.  
    i.mu.Unlock()
  52.  
    return limiter
  53.  
    }

2,global/setting.go

  1.  
    package global
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    "github.com/liuhongdi/digv14/pkg/setting"
  6.  
    "time"
  7.  
    )
  8.  
    //服务器配置
  9.  
    type ServerSettingS struct {
  10.  
    RunMode string
  11.  
    HttpPort string
  12.  
    ReadTimeout time.Duration
  13.  
    WriteTimeout time.Duration
  14.  
    }
  15.  
    //数据库配置
  16.  
    type DatabaseSettingS struct {
  17.  
    DBType string
  18.  
    UserName string
  19.  
    Password string
  20.  
    Host string
  21.  
    DBName string
  22.  
    Charset string
  23.  
    ParseTime bool
  24.  
    MaxIdleConns int
  25.  
    MaxOpenConns int
  26.  
    }
  27.  
    //日志配置
  28.  
    type LogSettingS struct {
  29.  
    LogFilePath string //保存到的目录
  30.  
    LogInfoFileName string //info级日志文件的名字
  31.  
    LogWarnFileName string //warn级日志文件的名字
  32.  
    LogAccessFileName string //Access日志文件的名字
  33.  
    LogFileExt string //文件的扩展名
  34.  
    }
  35.  
    //访问日志配置
  36.  
    type AccessLogSettingS struct {
  37.  
    LogFilePath string //保存到的目录
  38.  
    LogFileName string //Access日志文件的名字
  39.  
    LogFileExt string //文件的扩展名
  40.  
    }
  41.  
    //限流配置
  42.  
    type LimiterSettingS struct {
  43.  
    CountPerSecond int //每秒的访问次数
  44.  
    }
  45.  
    //定义全局变量
  46.  
    var (
  47.  
    ServerSetting *ServerSettingS
  48.  
    DatabaseSetting *DatabaseSettingS
  49.  
    LogSetting *LogSettingS
  50.  
    AccessLogSetting *AccessLogSettingS
  51.  
    LimiterSetting *LimiterSettingS
  52.  
    )
  53.  
     
  54.  
    //读取配置到全局变量
  55.  
    func SetupSetting() error {
  56.  
    s, err := setting.NewSetting()
  57.  
    if err != nil {
  58.  
    return err
  59.  
    }
  60.  
    err = s.ReadSection("Database", &DatabaseSetting)
  61.  
    if err != nil {
  62.  
    return err
  63.  
    }
  64.  
     
  65.  
    err = s.ReadSection("Server", &ServerSetting)
  66.  
    if err != nil {
  67.  
    return err
  68.  
    }
  69.  
     
  70.  
    err = s.ReadSection("Log", &LogSetting)
  71.  
    if err != nil {
  72.  
    return err
  73.  
    }
  74.  
     
  75.  
    err = s.ReadSection("AccessLog", &AccessLogSetting)
  76.  
    if err != nil {
  77.  
    return err
  78.  
    }
  79.  
     
  80.  
    err = s.ReadSection("Limiter", &LimiterSetting)
  81.  
    if err != nil {
  82.  
    return err
  83.  
    }
  84.  
     
  85.  
    fmt.Println("setting:")
  86.  
    fmt.Println(ServerSetting)
  87.  
    fmt.Println(DatabaseSetting)
  88.  
    fmt.Println(LogSetting)
  89.  
    fmt.Println(AccessLogSetting)
  90.  
    fmt.Println(LimiterSetting)
  91.  
    return nil
  92.  
    }

3,main.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "github.com/gin-gonic/gin"
  5.  
    _ "github.com/jinzhu/gorm/dialects/mysql"
  6.  
    "github.com/liuhongdi/digv14/global"
  7.  
    "github.com/liuhongdi/digv14/router"
  8.  
    "log"
  9.  
    )
  10.  
     
  11.  
    //init
  12.  
    func init() {
  13.  
    //setting
  14.  
    err := global.SetupSetting()
  15.  
    if err != nil {
  16.  
    log.Fatalf("init.setupSetting err: %v", err)
  17.  
    }
  18.  
     
  19.  
    //logger
  20.  
    err = global.SetupLogger()
  21.  
    if err != nil {
  22.  
    log.Fatalf("init.SetupLogger err: %v", err)
  23.  
    }
  24.  
     
  25.  
    //access logger
  26.  
    err = global.SetupAccessLogger()
  27.  
    if err != nil {
  28.  
    log.Fatalf("init.SetupAccessLogger err: %v", err)
  29.  
    }
  30.  
     
  31.  
    //db
  32.  
    err = global.SetupDBLink()
  33.  
    if err != nil {
  34.  
    log.Fatalf("init.SetupLogger err: %v", err)
  35.  
    global.Logger.Fatalf("init.setupDBEngine err: %v", err)
  36.  
    }
  37.  
     
  38.  
    //ratelimiter
  39.  
    err = global.SetupIPRateLimiter()
  40.  
    if err != nil {
  41.  
    log.Fatalf("init.SetupIPRateLimiter err: %v", err)
  42.  
    global.Logger.Fatalf("init.SetupIPRateLimiter err: %v", err)
  43.  
    }
  44.  
     
  45.  
    global.Logger.Infof("------应用init结束")
  46.  
    //global.Logger.
  47.  
    }
  48.  
     
  49.  
    func main() {
  50.  
    global.Logger.Infof("------应用main函数开始")
  51.  
    //设置运行模式
  52.  
    gin.SetMode(global.ServerSetting.RunMode)
  53.  
    //引入路由
  54.  
    r := router.Router()
  55.  
    //run
  56.  
    r.Run(":"+global.ServerSetting.HttpPort)
  57.  
    }

4,middleware/limit.go

  1.  
    package middleware
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    "github.com/gin-gonic/gin"
  6.  
    "github.com/liuhongdi/digv14/global"
  7.  
    "github.com/liuhongdi/digv14/pkg/result"
  8.  
    "github.com/liuhongdi/digv14/pkg/util"
  9.  
    )
  10.  
    //限流器
  11.  
    func LimitMiddleware() gin.HandlerFunc {
  12.  
    return func(c *gin.Context) {
  13.  
    //得到ip地址
  14.  
    ipAddr:=util.GetRealIp(c)
  15.  
    fmt.Println("current ip:"+ipAddr)
  16.  
    //ipAddr:="127.0.0.1"
  17.  
    limiter := global.RateLimiter.GetLimiter(ipAddr)
  18.  
    if !limiter.Allow() {
  19.  
    fmt.Println("not allow,will return")
  20.  
    resultRes := result.NewResult(c)
  21.  
    resultRes.Error(2004,"访问超出限制")
  22.  
    return
  23.  
    } else {
  24.  
    fmt.Println("allow,next")
  25.  
    c.Next()
  26.  
    }
  27.  
    }
  28.  
    }

5,router/router.go

  1.  
    package router
  2.  
     
  3.  
    import (
  4.  
    "github.com/gin-gonic/gin"
  5.  
    "github.com/liuhongdi/digv14/controller"
  6.  
    "github.com/liuhongdi/digv14/global"
  7.  
    "github.com/liuhongdi/digv14/middleware"
  8.  
    "github.com/liuhongdi/digv14/pkg/result"
  9.  
    "runtime/debug"
  10.  
    )
  11.  
     
  12.  
    func Router() *gin.Engine {
  13.  
    router := gin.Default()
  14.  
    //处理异常
  15.  
    router.NoRoute(HandleNotFound)
  16.  
    router.NoMethod(HandleNotFound)
  17.  
    //router.Use(middleware.AccessLog())
  18.  
    router.Use(middleware.AccessLog()).Use(middleware.LimitMiddleware())
  19.  
    router.Use(Recover)
  20.  
     
  21.  
    // 路径映射
  22.  
    articlec:=controller.NewArticleController()
  23.  
    router.GET("/article/getone/:id", articlec.GetOne);
  24.  
    router.GET("/article/list", articlec.GetList);
  25.  
    return router
  26.  
    }
  27.  
     
  28.  
    //404
  29.  
    func HandleNotFound(c *gin.Context) {
  30.  
    global.Logger.Errorf("handle not found: %v", c.Request.RequestURI)
  31.  
    //global.Logger.Errorf("stack: %v",string(debug.Stack()))
  32.  
    result.NewResult(c).Error(404,"资源未找到")
  33.  
    return
  34.  
    }
  35.  
     
  36.  
    //500
  37.  
    func Recover(c *gin.Context) {
  38.  
    defer func() {
  39.  
    if r := recover(); r != nil {
  40.  
    //打印错误堆栈信息
  41.  
    //log.Printf("panic: %v ", r)
  42.  
    global.Logger.Errorf("panic: %v", r)
  43.  
    //log stack
  44.  
    global.Logger.Errorf("stack: %v",string(debug.Stack()))
  45.  
    //print stack
  46.  
    debug.PrintStack()
  47.  
    //return
  48.  
    result.NewResult(c).Error(500,"服务器内部错误")
  49.  
    }
  50.  
    }()
  51.  
    //继续后续接口调用
  52.  
    c.Next()
  53.  
    }

6,其他相关代码可访问github.com

五,测试效果

  1.  
    root@ku:/data/liuhongdi/digv14# ab -n 50 -c 50 http://127.0.0.1:8000/article/list
  2.  
    This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
  3.  
    Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
  4.  
    Licensed to The Apache Software Foundation, http://www.apache.org/
  5.  
     
  6.  
    Benchmarking 127.0.0.1 (be patient).....done
  7.  
     
  8.  
     
  9.  
    Server Software:
  10.  
    Server Hostname: 127.0.0.1
  11.  
    Server Port: 8000
  12.  
     
  13.  
    Document Path: /article/list
  14.  
    Document Length: 821 bytes
  15.  
     
  16.  
    Concurrency Level: 50
  17.  
    Time taken for tests: 0.056 seconds
  18.  
    Complete requests: 50
  19.  
    Failed requests: 43
  20.  
    (Connect: 0, Receive: 0, Length: 43, Exceptions: 0)
  21.  
    Non-2xx responses: 43
  22.  
    Total transferred: 14441 bytes
  23.  
    HTML transferred: 7897 bytes
  24.  
    Requests per second: 894.84 [#/sec] (mean)
  25.  
    Time per request: 55.876 [ms] (mean)
  26.  
    Time per request: 1.118 [ms] (mean, across all concurrent requests)
  27.  
    Transfer rate: 252.39 [Kbytes/sec] received
  28.  
     
  29.  
    Connection Times (ms)
  30.  
    min mean[+/-sd] median max
  31.  
    Connect: 0 7 2.0 7 9
  32.  
    Processing: 10 25 9.0 25 41
  33.  
    Waiting: 3 24 9.3 24 41
  34.  
    Total: 12 31 9.5 33 46
  35.  
     
  36.  
    Percentage of the requests served within a certain time (ms)
  37.  
    50% 33
  38.  
    66% 37
  39.  
    75% 38
  40.  
    80% 40
  41.  
    90% 43
  42.  
    95% 46
  43.  
    98% 46
  44.  
    99% 46
  45.  
    100% 46 (longest request)

一秒时间内,只有7次成功的访问,

和我们在配置文件中设置的CountPerSecond的值是一致的

六,查看库的版本:

    1.  
      module github.com/liuhongdi/digv14
    2.  
       
    3.  
      go 1.15
    4.  
       
    5.  
      require (
    6.  
      github.com/gin-gonic/gin v1.6.3
    7.  
      github.com/go-playground/universal-translator v0.17.0
    8.  
      github.com/go-playground/validator/v10 v10.2.0
    9.  
      github.com/jinzhu/gorm v1.9.16
    10.  
      github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
    11.  
      github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
    12.  
      github.com/magiconair/properties v1.8.4 // indirect
    13.  
      github.com/mitchellh/mapstructure v1.3.3 // indirect
    14.  
      github.com/pelletier/go-toml v1.8.1 // indirect
    15.  
      github.com/pkg/errors v0.9.1 // indirect
    16.  
      github.com/spf13/afero v1.4.1 // indirect
    17.  
      github.com/spf13/cast v1.3.1 // indirect
    18.  
      github.com/spf13/jwalterweatherman v1.1.0 // indirect
    19.  
      github.com/spf13/pflag v1.0.5 // indirect
    20.  
      github.com/spf13/viper v1.7.1
    21.  
      go.uber.org/multierr v1.6.0 // indirect
    22.  
      go.uber.org/zap v1.16.0
    23.  
      golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
    24.  
      golang.org/x/text v0.3.4 // indirect
    25.  
      golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
    26.  
      gopkg.in/ini.v1 v1.62.0 // indirect
    27.  
      gopkg.in/yaml.v2 v2.3.0 // indirect
    28.  
      )
原文地址:https://www.cnblogs.com/ExMan/p/14312262.html