zoukankan      html  css  js  c++  java
  • 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.  
        )
  • 相关阅读:
    字符串去特定字符
    字符串的查找删除
    输出梯形
    元素节点的 innerText、innerHTML、outerHTML、outerText
    JavaScript DOM 特殊集合Collection
    Collection 访问方式
    JS Browser BOM
    异常
    JCBD
    try-with-resources 方式关闭注意事项
  • 原文地址:https://www.cnblogs.com/ExMan/p/14312262.html
Copyright © 2011-2022 走看看