redis-throttle 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.adoc +178 -0
- data/lib/redis/throttle.rb +10 -276
- data/lib/redis-throttle.rb +4 -0
- data/lib/redis_throttle/api.rb +118 -0
- data/lib/redis_throttle/class_methods.rb +43 -0
- data/lib/redis_throttle/concurrency.rb +75 -0
- data/lib/redis_throttle/rate_limit.rb +73 -0
- data/lib/redis_throttle/version.rb +6 -0
- data/lib/redis_throttle.rb +286 -0
- metadata +31 -29
- data/lib/redis/throttle/api.rb +0 -120
- data/lib/redis/throttle/class_methods.rb +0 -45
- data/lib/redis/throttle/concurrency.rb +0 -77
- data/lib/redis/throttle/errors.rb +0 -10
- data/lib/redis/throttle/rate_limit.rb +0 -75
- data/lib/redis/throttle/script.rb +0 -53
- data/lib/redis/throttle/version.rb +0 -8
- /data/lib/{redis/throttle → redis_throttle}/api.lua +0 -0
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "./api"
|
4
|
-
|
5
|
-
class Redis
|
6
|
-
class Throttle
|
7
|
-
module ClassMethods
|
8
|
-
# Syntax sugar for {Throttle#concurrency}.
|
9
|
-
#
|
10
|
-
# @see #concurrency
|
11
|
-
# @param (see Throttle#initialize)
|
12
|
-
# @param (see Throttle#concurrency)
|
13
|
-
# @return (see Throttle#concurrency)
|
14
|
-
def concurrency(bucket, limit:, ttl:, redis: nil)
|
15
|
-
new(:redis => redis).concurrency(bucket, :limit => limit, :ttl => ttl)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Syntax sugar for {Throttle#rate_limit}.
|
19
|
-
#
|
20
|
-
# @see #concurrency
|
21
|
-
# @param (see Throttle#initialize)
|
22
|
-
# @param (see Throttle#rate_limit)
|
23
|
-
# @return (see Throttle#rate_limit)
|
24
|
-
def rate_limit(bucket, limit:, period:, redis: nil)
|
25
|
-
new(:redis => redis).rate_limit(bucket, :limit => limit, :period => period)
|
26
|
-
end
|
27
|
-
|
28
|
-
# Return usage info for all known (in use) strategies.
|
29
|
-
#
|
30
|
-
# @example
|
31
|
-
# Redis::Throttle.info(:match => "*_api").each do |strategy, current_value|
|
32
|
-
# # ...
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# @param match [#to_s]
|
36
|
-
# @return (see Api#info)
|
37
|
-
def info(match: "*", redis: nil)
|
38
|
-
api = Api.new(:redis => redis)
|
39
|
-
strategies = api.strategies(:match => match.to_s)
|
40
|
-
|
41
|
-
api.info(:strategies => strategies)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Redis
|
4
|
-
class Throttle
|
5
|
-
class Concurrency
|
6
|
-
# @!attribute [r] bucket
|
7
|
-
# @return [String] Throttling group name
|
8
|
-
attr_reader :bucket
|
9
|
-
|
10
|
-
# @!attribute [r] limit
|
11
|
-
# @return [Integer] Max allowed concurrent units
|
12
|
-
attr_reader :limit
|
13
|
-
|
14
|
-
# @!attribute [r] ttl
|
15
|
-
# @return [Integer] Time (in seconds) to hold the lock before
|
16
|
-
# releasing it (in case it wasn't released already)
|
17
|
-
attr_reader :ttl
|
18
|
-
|
19
|
-
# @param bucket [#to_s] Throttling group name
|
20
|
-
# @param limit [#to_i] Max allowed concurrent units
|
21
|
-
# @param ttl [#to_i] Time (in seconds) to hold the lock before
|
22
|
-
# releasing it (in case it wasn't released already)
|
23
|
-
def initialize(bucket, limit:, ttl:)
|
24
|
-
@bucket = -bucket.to_s
|
25
|
-
@limit = limit.to_i
|
26
|
-
@ttl = ttl.to_i
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns `true` if `other` is a {Concurrency} instance with the same
|
30
|
-
# {#bucket}, {#limit}, and {#ttl}.
|
31
|
-
#
|
32
|
-
# @see https://docs.ruby-lang.org/en/master/Object.html#method-i-eql-3F
|
33
|
-
# @param other [Object]
|
34
|
-
# @return [Boolean]
|
35
|
-
def ==(other)
|
36
|
-
return true if equal? other
|
37
|
-
return false unless other.is_a?(self.class)
|
38
|
-
|
39
|
-
@bucket == other.bucket && @limit == other.limit && @ttl == other.ttl
|
40
|
-
end
|
41
|
-
|
42
|
-
alias eql? ==
|
43
|
-
|
44
|
-
# @api private
|
45
|
-
#
|
46
|
-
# Compare `self` with `other` strategy:
|
47
|
-
#
|
48
|
-
# - Returns `nil` if `other` is neither {Concurrency} nor {RateLimit}
|
49
|
-
# - Returns `1` if `other` is a {RateLimit}
|
50
|
-
# - Returns `1` if `other` is a {Concurrency} with lower {#limit}
|
51
|
-
# - Returns `0` if `other` is a {Concurrency} with the same {#limit}
|
52
|
-
# - Returns `-1` if `other` is a {Concurrency} with bigger {#limit}
|
53
|
-
#
|
54
|
-
# @return [-1, 0, 1, nil]
|
55
|
-
def <=>(other)
|
56
|
-
complexity <=> other.complexity if other.respond_to? :complexity
|
57
|
-
end
|
58
|
-
|
59
|
-
# @api private
|
60
|
-
#
|
61
|
-
# Generates an Integer hash value for this object.
|
62
|
-
#
|
63
|
-
# @see https://docs.ruby-lang.org/en/master/Object.html#method-i-hash
|
64
|
-
# @return [Integer]
|
65
|
-
def hash
|
66
|
-
@hash ||= [@bucket, @limit, @ttl].hash
|
67
|
-
end
|
68
|
-
|
69
|
-
# @api private
|
70
|
-
#
|
71
|
-
# @return [Array(Integer, Integer)] Strategy complexity pseudo-score
|
72
|
-
def complexity
|
73
|
-
[1, @limit]
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
@@ -1,75 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Redis
|
4
|
-
class Throttle
|
5
|
-
class RateLimit
|
6
|
-
# @!attribute [r] bucket
|
7
|
-
# @return [String] Throttling group name
|
8
|
-
attr_reader :bucket
|
9
|
-
|
10
|
-
# @!attribute [r] limit
|
11
|
-
# @return [Integer] Max allowed units per {#period}
|
12
|
-
attr_reader :limit
|
13
|
-
|
14
|
-
# @!attribute [r] period
|
15
|
-
# @return [Integer] Period in seconds
|
16
|
-
attr_reader :period
|
17
|
-
|
18
|
-
# @param bucket [#to_s] Throttling group name
|
19
|
-
# @param limit [#to_i] Max allowed units per `period`
|
20
|
-
# @param period [#to_i] Period in seconds
|
21
|
-
def initialize(bucket, limit:, period:)
|
22
|
-
@bucket = -bucket.to_s
|
23
|
-
@limit = limit.to_i
|
24
|
-
@period = period.to_i
|
25
|
-
end
|
26
|
-
|
27
|
-
# Returns `true` if `other` is a {RateLimit} instance with the same
|
28
|
-
# {#bucket}, {#limit}, and {#period}.
|
29
|
-
#
|
30
|
-
# @see https://docs.ruby-lang.org/en/master/Object.html#method-i-eql-3F
|
31
|
-
# @param other [Object]
|
32
|
-
# @return [Boolean]
|
33
|
-
def ==(other)
|
34
|
-
return true if equal? other
|
35
|
-
return false unless other.is_a?(self.class)
|
36
|
-
|
37
|
-
@bucket == other.bucket && @limit == other.limit && @period == other.period
|
38
|
-
end
|
39
|
-
|
40
|
-
alias eql? ==
|
41
|
-
|
42
|
-
# @api private
|
43
|
-
#
|
44
|
-
# Compare `self` with `other` strategy:
|
45
|
-
#
|
46
|
-
# - Returns `nil` if `other` is neither {Concurrency} nor {RateLimit}
|
47
|
-
# - Returns `-1` if `other` is a {Concurrency}
|
48
|
-
# - Returns `1` if `other` is a {RateLimit} with lower {#limit}
|
49
|
-
# - Returns `0` if `other` is a {RateLimit} with the same {#limit}
|
50
|
-
# - Returns `-1` if `other` is a {RateLimit} with bigger {#limit}
|
51
|
-
#
|
52
|
-
# @return [-1, 0, 1, nil]
|
53
|
-
def <=>(other)
|
54
|
-
complexity <=> other.complexity if other.respond_to? :complexity
|
55
|
-
end
|
56
|
-
|
57
|
-
# @api private
|
58
|
-
#
|
59
|
-
# Generates an Integer hash value for this object.
|
60
|
-
#
|
61
|
-
# @see https://docs.ruby-lang.org/en/master/Object.html#method-i-hash
|
62
|
-
# @return [Integer]
|
63
|
-
def hash
|
64
|
-
@hash ||= [@bucket, @limit, @period].hash
|
65
|
-
end
|
66
|
-
|
67
|
-
# @api private
|
68
|
-
#
|
69
|
-
# @return [Array(Integer, Integer)] Strategy complexity pseudo-score
|
70
|
-
def complexity
|
71
|
-
[0, @limit]
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "digest"
|
4
|
-
require "redis/errors"
|
5
|
-
|
6
|
-
require_relative "./errors"
|
7
|
-
|
8
|
-
class Redis
|
9
|
-
class Throttle
|
10
|
-
# @api private
|
11
|
-
#
|
12
|
-
# Lazy-compile and run acquire script by it's sha1 digest.
|
13
|
-
class Script
|
14
|
-
# Redis error fired when script ID is unkown.
|
15
|
-
NOSCRIPT = "NOSCRIPT"
|
16
|
-
private_constant :NOSCRIPT
|
17
|
-
|
18
|
-
LUA_ERROR_MESSAGE = %r{
|
19
|
-
ERR\s
|
20
|
-
(?<message>Error\s(?:compiling|running)\sscript)
|
21
|
-
\s\([^()]+\):\s
|
22
|
-
(?:@[^:]+:\d+:\s)?
|
23
|
-
[^:]+:(?<loc>\d+):\s
|
24
|
-
(?<details>.+)
|
25
|
-
}x.freeze
|
26
|
-
private_constant :LUA_ERROR_MESSAGE
|
27
|
-
|
28
|
-
def initialize(source)
|
29
|
-
@source = -source.to_s
|
30
|
-
@digest = Digest::SHA1.hexdigest(@source).freeze
|
31
|
-
end
|
32
|
-
|
33
|
-
def call(redis, keys: [], argv: [])
|
34
|
-
__eval__(redis, keys, argv)
|
35
|
-
rescue Redis::CommandError => e
|
36
|
-
md = LUA_ERROR_MESSAGE.match(e.message.to_s)
|
37
|
-
raise unless md
|
38
|
-
|
39
|
-
raise ScriptError, "#{md[:message]} @#{md[:loc]}: #{md[:details]}"
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def __eval__(redis, keys, argv)
|
45
|
-
redis.evalsha(@digest, keys, argv)
|
46
|
-
rescue Redis::CommandError => e
|
47
|
-
raise unless e.message.include?(NOSCRIPT)
|
48
|
-
|
49
|
-
redis.eval(@source, keys, argv)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
File without changes
|