func (s *store) Take(ctx context.Context, key string) (limit uint64, remaining uint64, next uint64, ok bool, retErr error) {
	if atomic.LoadUint32(&s.stopped) == 1 {
		retErr = limiter.ErrStopped
		return
	}
	now := uint64(time.Now().UTC().UnixNano())
	conn, err := s.pool.GetContext(ctx)
	if err != nil {
		retErr = fmt.Errorf("failed to get connection from pool: %w", err)
		return
	}
	if err := conn.Err(); err != nil {
		retErr = fmt.Errorf("connection is not usable: %w", err)
		return
	}
	defer closeConnection(conn, &retErr)
	nowStr := strconv.FormatUint(now, 10)
	tokensStr := strconv.FormatUint(s.tokens, 10)
	intervalStr := strconv.FormatInt(s.interval.Nanoseconds(), 10)
	a, err := redis.Int64s(s.luaScript.Do(conn, key, nowStr, tokensStr, intervalStr))
	if err != nil {
		retErr = fmt.Errorf("failed to run script: %w", err)
		return
	}
	if len(a) < 4 {
		retErr = fmt.Errorf("response has less than 4 values: %#v", a)
		return
	}
	limit, remaining, next, ok = uint64(a[0]), uint64(a[1]), uint64(a[2]), a[3] == 1
	return
}
Take attempts to remove a token from the named key. If the take is
successful, it returns true, otherwise false. It also returns the configured
limit, remaining tokens, and reset time, if one was found. Any errors
connecting to the store or parsing the return value are considered failures
and fail the take.
package redisstore
import (
	"context"
	"fmt"
	"strconv"
	"sync/atomic"
	"time"
	"github.com/gomodule/redigo/redis"
	"github.com/sethvargo/go-limiter"
)
const (
	fieldInterval  = "i"
	fieldMaxTokens = "m"
	fieldTokens    = "k"
	weekSeconds = 60 * 60 * 24 * 7
	cmdEXPIRE  = "EXPIRE"
	cmdHINCRBY = "HINCRBY"
	cmdHMGET   = "HMGET"
	cmdHSET    = "HSET"
	cmdPING    = "PING"
)
var _ limiter.Store = (*store)(nil)
type store struct {
	tokens    uint64
	interval  time.Duration
	pool      *redis.Pool
	luaScript *redis.Script
	stopped uint32
}
type Config struct {
	Tokens uint64
	Interval time.Duration
	Dial func() (redis.Conn, error)
}
func New(c *Config) (limiter.Store, error) {
	return NewWithPool(c, &redis.Pool{
		MaxActive:   100,
		IdleTimeout: 5 * time.Minute,
		Dial:        c.Dial,
		TestOnBorrow: func(c redis.Conn, _ time.Time) error {
			_, err := c.Do(cmdPING)
			return err
		},
	})
}
func NewWithPool(c *Config, pool *redis.Pool) (limiter.Store, error) {
	if c == nil {
		c = new(Config)
	}
	tokens := uint64(1)
	if c.Tokens > 0 {
		tokens = c.Tokens
	}
	interval := 1 * time.Second
	if c.Interval > 0 {
		interval = c.Interval
	}
	luaScript := redis.NewScript(1, luaTemplate)
	s := &store{
		tokens:    tokens,
		interval:  interval,
		pool:      pool,
		luaScript: luaScript,
	}
	return s, nil
}