Using Redis's Incr can easily implement a rate limiter.

There is also a detailed example in the redis official documentation.

FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
current = GET(keyname)

IF current != NULL AND current > 10 THEN
    ERROR "too many requests per second"
END

IF current == NULL THEN
    MULTI
        INCR(keyname, 1)
        EXPIRE(keyname, 1)
    EXEC
ELSE
    INCR(keyname, 1)
END

PERFORM_API_CALL()

An Example

Suppose there is a service that can only accept no more than 10 requests from a user within 1 minute. At this time, we can set the user's IP address as the key. Every time the user requests, the program goes to redis to get the value of the key. If it is greater than or equal to 10, it returns an error; otherwise, increment the value corresponding to the key by 1. If the value is 0, then set an expiration time of 1 minute for the key.


But now there is a requirement. We can push a message to the user within a specified time, but the user is required to receive only 1 message per minute, no more than 5 messages per hour, and no more than 10 messages per day.

For example, I submit a piece of data to this interface now, requiring to send a piece of data to a user at 2019-11-11 11:11:11. Then when I submit another piece of data, requiring to send a piece of data to the same user at 2019-11-11 11:11:12, the interface will return an error.

At this point, using only Incr cannot satisfy this requirement.

Solution

Use set or zset to use user ip + send date as key, convert send time to seconds of the day as value, and insert into set or zset. Every time information is submitted to the user, all send times in set can be obtained, and then compared one by one. If conditions are not met, error is returned.

The following is pseudo code implementation

function RateLimit(ip,sendtime){
  // Get send date based on send time
  sendDate = getSendDate(sendtime)
  // Get seconds difference between send time and 0 o'clock of the day
  second = getSendSecond(sendtime)
  // List all send times in the send date
  secondList = listSeconds(ip + sendDate)
  // Check successfully
  if check(second,secondList) {
    // Add send time to cache
    return addToList(ip + sendDate,second)
  }
  return false
}

The following is golang implementation

func RateLimit(ctx context.Context, ip string, sendTime time.Time) error {
    sendTime = sendTime.UTC()
 if sendTime.Before(time.Now()) {
  return nil
 }
 zeroStr := sendTime.Format("2006-01-02")
 key := ip + zeroStr
 zero, _ := time.Parse("2006-01-02", zeroStr)
    // Get seconds from send time to today's time
 second := int(sendTime.Sub(zero).Seconds())
 ress, err := cache.SMembers(ctx, key)
 if err != nil {
  return err
 }
    // Process return parameters, convert []string to []int
    var sends []int
 for _, v := range ress {
  if vv, err := strconv.Atoi(v); err == nil {
   sends = append(sends, vv)
  }
 }
 if len(sends) >= 10 {
  return errors.ErrMsg1DayLimit
 }

 var expire bool

 if len(sends) == 0 {
  expire = true
 }

 var hourCount int

 for _, s := range sends {
  if math.Abs(float64(second-s)) < 60 {
   return errors.ErrMsg1MinuteLimit
  }
  if math.Abs(float64(second-s)) < 3600 {
   hourCount++
  }
 }
 if hourCount > 5 {
  return errors.ErrMsg1HourLimit
 }
    // Asynchronous update
    go func() {
        // Start transaction
  t := cache.Pipeline()
  defer t.Close()
  t.SAdd(context.Background(), key, []byte(strconv.Itoa(second)))
  if expire {
   ttl := zero.Add(time.Hour*24).Sub(time.Now()).Seconds()
   if ttl <= 0 {
    t.Rollback()
    return
   }
   t.Expire( key, int64(ttl))
  }
  t.Commit()
 }()
    return nil
}

The above can implement a rate limiter for a specified time.