ホームページ  >  記事  >  バックエンド開発  >  Go でユーザーの 1 日あたりの制限を実装する方法

Go でユーザーの 1 日あたりの制限を実装する方法

藏色散人
藏色散人転載
2022-01-10 15:55:173214ブラウズ

この記事は、golang チュートリアル コラムによって、Go でユーザーの 1 日の制限を実装する方法を紹介するために書かれたものです。困っている友人の役に立てば幸いです。

Go にユーザーの 1 日あたりの制限を実装します (たとえば、特典は 1 日に 3 回しか受け取れません)

バグ管理システムを作成し、この PeriodLimit を使用する場合、各テスターが 1 日に 1 つのバグのみを送信するように制限できます。仕事はずっと楽になりましたか? :P

現在、マイクロサービス アーキテクチャが非常に人気がある本質的な理由は、システム全体の複雑さを軽減し、システム リスクをサブシステムに均等に分散してシステムの安定性を最大化し、それをさまざまなサブシステムに分割することです。サブシステム導入後は、各サブシステムを独立して開発、テスト、リリースすることができ、研究開発のリズムと効率が大幅に向上します。

しかし、呼び出しリンクが長すぎる、展開アーキテクチャの複雑さが増す、さまざまなミドルウェアが分散シナリオをサポートする必要があるなどの問題も生じます。マイクロサービスの正常な動作を保証するには、サービス ガバナンスが不可欠であり、これには通常、電流制限、ダウングレード、サーキット ブレーカーが含まれます。

電流制限とは、負荷制限を超えてシステムがダウンすることを避けるために、インターフェイス呼び出しの頻度を制限することを指します。例:

  • #E コマース フラッシュ セール シナリオ

  • #さまざまな販売者の API の現在の制限

一般的に使用される電流制限アルゴリズムは次のとおりです。

  • 固定時間ウィンドウ電流制限
  • スライディング タイム ウィンドウ電流制限
  • リーキー バケット電流制限
  • トークンバケット制限フロー

この記事では主に固定時間ウィンドウ電流制限アルゴリズムについて説明します。

動作原理

ある時点から、各リクエストのリクエスト数は 1 になります。同時に、リクエストの数が 1 であるかどうかを判断します。現在の時間枠内のリクエストは制限を超えています。制限を超えた場合、リクエストは拒否されます。次の時間枠がリクエストの待機を開始すると、リクエストはクリアされます。

Go でユーザーの 1 日あたりの制限を実装する方法

メリットとデメリット

メリット

簡単これは効率的であり、ユーザーが 1 日に記事を 10 件しか投稿できない、SMS 認証コードを 5 回しか送信できない、ログインを 5 回しか試行できないなど、制限されたシナリオに特に適しています。このようなシナリオは実際には非常に一般的です。仕事。

欠点

固定時間ウィンドウ電流制限の欠点は、クリティカル セクションの要求バースト シナリオを処理できないことです。

現在の制限が 1 秒あたり 100 リクエストで、ユーザーが 500 ミリ秒の中間から開始して 1 秒以内に 200 リクエストを開始したとします。この時点で、200 リクエストはすべて通過できます。これは、電流を 1 秒あたり 100 回に制限するという予想と矛盾します。根本的な原因は、電流制限の粒度が粗すぎることです。

Go でユーザーの 1 日あたりの制限を実装する方法

#go-zero コードの実装

##core/limit/periodlimit.go

Go-zero は、redis の有効期限を使用して、固定時間枠をシミュレートします。

redis lua スクリプト:

-- KYES[1]:限流器key-- ARGV[1]:qos,单位时间内最多请求次数-- ARGV[2]:单位限流窗口时间-- 请求最大次数,等于p.quotalocal limit = tonumber(ARGV[1])-- 窗口即一个单位限流周期,这里用过期模拟窗口效果,等于p.permitlocal window = tonumber(ARGV[2])-- 请求次数+1,获取请求总数local current = redis.call("INCRBY",KYES[1],1)-- 如果是第一次请求,则设置过期时间并返回 成功if current == 1 then
  redis.call("expire",KYES[1],window)
  return 1-- 如果当前请求数量小于limit则返回 成功elseif current limit则返回 失败else
  return 0end
固定時間ウィンドウ電流リミッターの定義

type (
  // PeriodOption defines the method to customize a PeriodLimit.
  // go中常见的option参数模式
  // 如果参数非常多,推荐使用此模式来设置参数
  PeriodOption func(l *PeriodLimit)

  // A PeriodLimit is used to limit requests during a period of time.
  // 固定时间窗口限流器
  PeriodLimit struct {
    // 窗口大小,单位s
    period     int
    // 请求上限
    quota      int
    // 存储
    limitStore *redis.Redis
    // key前缀
    keyPrefix  string
    // 线性限流,开启此选项后可以实现周期性的限流
    // 比如quota=5时,quota实际值可能会是5.4.3.2.1呈现出周期性变化
    align      bool
  }
)

align パラメーターに注意してください。 = true の場合、リクエストの上限は定期的に変更されます。
たとえば、クォータ = 5 の場合、実際のクォータは 5.4.3.2.1 になる可能性があり、定期的な変更を示します


電流制限ロジック

実際には、電流制限ロジックは上記 lua スクリプトが実装されていますが、戻り値

0: Redis の障害やオーバーロードなどのエラーを示していることに注意してください
  • 1: allowed
  • ##2: 許可 ただし、現在のウィンドウでは上限に達しています。バッチ業務を実行している場合は、スリープして次のウィンドウを待つことができます (作成者は慎重に検討しました)
  • 3: 拒否
  • // Take requests a permit, it returns the permit state.
    // 执行限流
    // 注意一下返回值:
    // 0:表示错误,比如可能是redis故障、过载
    // 1:允许
    // 2:允许但是当前窗口内已到达上限
    // 3:拒绝
    func (h *PeriodLimit) Take(key string) (int, error) {
      // 执行lua脚本
      resp, err := h.limitStore.Eval(periodScript, []string{h.keyPrefix + key}, []string{
        strconv.Itoa(h.quota),
        strconv.Itoa(h.calcExpireSeconds()),
      })
    
      if err != nil {
        return Unknown, err
      }
    
      code, ok := resp.(int64)
      if !ok {
        return Unknown, ErrUnknownCode
      }
    
      switch code {
      case internalOverQuota:
        return OverQuota, nil
      case internalAllowed:
        return Allowed, nil
      case internalHitQuota:
        return HitQuota, nil
      default:
        return Unknown, ErrUnknownCode
      }
    }
  • この固定ウィンドウの現在の制限は、たとえば、ユーザーが確認コードのテキスト メッセージを 1 日に 5 回しか送信できないように制限するために使用できます。中国のタイムゾーン (GMT 8) に対応させるため、実際には現在の制限時間は 0 時から開始する必要がありますが、この時点では追加のアライメント (align を true に設定) が必要です。
  • // 计算过期时间也就是窗口时间大小
    // 如果align==true
    // 线性限流,开启此选项后可以实现周期性的限流
    // 比如quota=5时,quota实际值可能会是5.4.3.2.1呈现出周期性变化
    func (h *PeriodLimit) calcExpireSeconds() int {
      if h.align {
        now := time.Now()
        _, offset := now.Zone()
        unix := now.Unix() + int64(offset)
        return h.period - int(unix%int64(h.period))
      }
    
      return h.period
    }
プロジェクト アドレス

github.com/zeromicro/go-zero

go-zero

および

star の使用を歓迎します私たちを応援してください!

以上がGo でユーザーの 1 日あたりの制限を実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。