redis-throttler 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c2efd874b28958cb9d0e7131dc9131162b51a6b8
4
- data.tar.gz: 37a41540b2955373ab62e7acda33bfa404e6f931
3
+ metadata.gz: c3648bd744161a9c30693fd53fb7bfb490ab54f3
4
+ data.tar.gz: 1b7c3424b04912da66ed591db45e14a36e518e5d
5
5
  SHA512:
6
- metadata.gz: 4cdf4289b6e02e4b75a2f57dbd61b6de2f4b770b99dfea206d77a826099549647513022295dc5a4c272e5f713e07409cdf0bf61495db8536827da9a8aca7779d
7
- data.tar.gz: a4e7b42fbbdde3a74ada112876b67a26d65dc42dfecdb3c227d19e378e5c7299981aa7b07966c35d0002ba0ad1be7b542647333fe2f48c166708bca1ea7e9e63
6
+ metadata.gz: 7bff0a3c2299681c052da3d0333f6c7858ae5b689a2c434ead0e18de1bcfa83dbc94b9f241afb4509fe05c5eef3c2d5b9c5cd18cc0faf6b2aae0b20abd8f0bfa
7
+ data.tar.gz: bf0eac98c7f1cff826f20c79330f135b4252d58d8b47b77ce45000e20521d0df3dffebd52450efd4abb1d330e0217c2c289d15600d426a8f27610cf3af6ee1cd
@@ -0,0 +1,120 @@
1
+ module RedisThrottler
2
+ class Base
3
+ # Create a RedisThrottler object.
4
+ #
5
+ # @param [String] key A name to uniquely identify this rate limit. For example, 'emails'
6
+ # @param [Hash] options Options hash
7
+ # @option options [Integer] :bucket_span (600) Time span to track in seconds
8
+ # @option options [Integer] :bucket_interval (5) How many seconds each bucket represents
9
+ # @option options [Integer] :bucket_expiry (@bucket_span) How long we keep data in each bucket before it is auto expired. Cannot be larger than the bucket_span.
10
+ # @option options [Redis] :redis (nil) Redis client if you need to customize connection options
11
+ #
12
+ # @return [RedisThrottler] RedisThrottler instance
13
+ #
14
+ def initialize(key, options = {})
15
+ @key = key
16
+ @bucket_span = options[:bucket_span] || 600
17
+ @bucket_interval = options[:bucket_interval] || 5
18
+ @bucket_expiry = options[:bucket_expiry] || @bucket_span
19
+ if @bucket_expiry > @bucket_span
20
+ raise ArgumentError.new("Bucket expiry cannot be larger than the bucket span")
21
+ end
22
+ @bucket_count = (@bucket_span / @bucket_interval).round
23
+ if @bucket_count < 3
24
+ raise ArgumentError.new("Cannot have less than 3 buckets")
25
+ end
26
+ @redis = options[:redis]
27
+ end
28
+
29
+ # Increment counter for a given subject.
30
+ #
31
+ # @param [String] subject A unique key to identify the subject. For example, 'user@foo.com'
32
+ # @param [Integer] count The number by which to increment the counter
33
+ #
34
+ # @return [Integer] increments within interval
35
+ def add(subject, count = 1)
36
+ bucket = get_bucket
37
+ subject = "#{@key}:#{subject}"
38
+ redis.pipelined do
39
+ redis.hincrby(subject, bucket, count)
40
+ redis.hdel(subject, (bucket + 1) % @bucket_count)
41
+ redis.hdel(subject, (bucket + 2) % @bucket_count)
42
+ redis.expire(subject, @bucket_expiry)
43
+ end.first
44
+ end
45
+
46
+ # Returns the count for a given subject and interval
47
+ #
48
+ # @param [String] subject Subject for the count
49
+ # @param [Integer] interval How far back (in seconds) to retrieve activity.
50
+ #
51
+ # @return [Integer] current count for subject
52
+ def count(subject, interval)
53
+ bucket = get_bucket
54
+ interval = [interval, @bucket_interval].max
55
+ count = (interval / @bucket_interval).floor
56
+ subject = "#{@key}:#{subject}"
57
+
58
+ keys = (0..count - 1).map do |i|
59
+ (bucket - i) % @bucket_count
60
+ end
61
+ redis.hmget(subject, *keys).inject(0) {|a, i| a + i.to_i}
62
+ end
63
+
64
+ # Check if the rate limit has been exceeded.
65
+ #
66
+ # @param [String] subject Subject to check
67
+ # @param [Hash] options Options hash
68
+ # @option options [Integer] :interval How far back to retrieve activity.
69
+ # @option options [Integer] :threshold Maximum number of actions
70
+ #
71
+ # @return [Boolean] true if exceeded
72
+ def exceeded?(subject, options = {})
73
+ count(subject, options[:interval]) >= options[:threshold]
74
+ end
75
+
76
+ # Check if the rate limit is within bounds
77
+ #
78
+ # @param [String] subject Subject to check
79
+ # @param [Hash] options Options hash
80
+ # @option options [Integer] :interval How far back to retrieve activity.
81
+ # @option options [Integer] :threshold Maximum number of actions
82
+ #
83
+ # @return [Integer] true if within bounds
84
+ def within_bounds?(subject, options = {})
85
+ !exceeded?(subject, options)
86
+ end
87
+
88
+ # Execute a block once the rate limit is within bounds
89
+ # *WARNING* This will block the current thread until the rate limit is within bounds.
90
+ #
91
+ # @param [String] subject Subject for this rate limit
92
+ # @param [Hash] options Options hash
93
+ # @option options [Integer] :interval How far back to retrieve activity.
94
+ # @option options [Integer] :threshold Maximum number of actions
95
+ # @yield The block to be run
96
+ #
97
+ # @example Send an email as long as we haven't send 5 in the last 10 minutes
98
+ # RedisThrottler.exec_with_threshold(email, [:threshold => 5, :interval => 600]) do
99
+ # send_another_email
100
+ # end
101
+ def exec_within_threshold(subject, options = {}, &block)
102
+ options[:threshold] ||= 30
103
+ options[:interval] ||= 30
104
+ while exceeded?(subject, options)
105
+ sleep @bucket_interval
106
+ end
107
+ yield(self)
108
+ end
109
+
110
+ private
111
+
112
+ def get_bucket(time = Time.now.to_i)
113
+ ((time % @bucket_span) / @bucket_interval).floor
114
+ end
115
+
116
+ def redis
117
+ @redis ||= Redis.new(host: '192.168.99.100', port: 32771)
118
+ end
119
+ end
120
+ end
@@ -1,6 +1,4 @@
1
- require 'redis-throttler'
2
-
3
- class RedisThrottler
1
+ module RedisThrottler
4
2
  module Model
5
3
 
6
4
  def self.included(base)
@@ -20,7 +18,7 @@ class RedisThrottler
20
18
  threshold = opts[:for] || 900
21
19
  interval = opts[:interval] || 5
22
20
 
23
- limiter = RedisThrottler.new("#{klass}:#{key}", bucket_interval: interval, bucket_span: threshold)
21
+ limiter = RedisThrottler::Base.new("#{klass}:#{key}", bucket_interval: interval, bucket_span: threshold)
24
22
  @limits[key] = "#{subject.to_s} limit #{limit} per #{threshold} sec"
25
23
 
26
24
  # includes('?') will return true
@@ -1,3 +1,3 @@
1
- class RedisThrottler
2
- VERSION = '0.1.4'
1
+ module RedisThrottler
2
+ VERSION = '0.1.5'
3
3
  end
@@ -1,120 +1,9 @@
1
1
  require 'redis'
2
+ require 'redis-throttler/model'
3
+ require 'redis-throttler/base'
2
4
 
3
- class RedisThrottler
4
- # Create a RedisThrottler object.
5
- #
6
- # @param [String] key A name to uniquely identify this rate limit. For example, 'emails'
7
- # @param [Hash] options Options hash
8
- # @option options [Integer] :bucket_span (600) Time span to track in seconds
9
- # @option options [Integer] :bucket_interval (5) How many seconds each bucket represents
10
- # @option options [Integer] :bucket_expiry (@bucket_span) How long we keep data in each bucket before it is auto expired. Cannot be larger than the bucket_span.
11
- # @option options [Redis] :redis (nil) Redis client if you need to customize connection options
12
- #
13
- # @return [RedisThrottler] RedisThrottler instance
14
- #
15
- def initialize(key, options = {})
16
- @key = key
17
- @bucket_span = options[:bucket_span] || 600
18
- @bucket_interval = options[:bucket_interval] || 5
19
- @bucket_expiry = options[:bucket_expiry] || @bucket_span
20
- if @bucket_expiry > @bucket_span
21
- raise ArgumentError.new("Bucket expiry cannot be larger than the bucket span")
22
- end
23
- @bucket_count = (@bucket_span / @bucket_interval).round
24
- if @bucket_count < 3
25
- raise ArgumentError.new("Cannot have less than 3 buckets")
26
- end
27
- @redis = options[:redis]
28
- end
29
-
30
- # Increment counter for a given subject.
31
- #
32
- # @param [String] subject A unique key to identify the subject. For example, 'user@foo.com'
33
- # @param [Integer] count The number by which to increment the counter
34
- #
35
- # @return [Integer] increments within interval
36
- def add(subject, count = 1)
37
- bucket = get_bucket
38
- subject = "#{@key}:#{subject}"
39
- redis.pipelined do
40
- redis.hincrby(subject, bucket, count)
41
- redis.hdel(subject, (bucket + 1) % @bucket_count)
42
- redis.hdel(subject, (bucket + 2) % @bucket_count)
43
- redis.expire(subject, @bucket_expiry)
44
- end.first
45
- end
46
-
47
- # Returns the count for a given subject and interval
48
- #
49
- # @param [String] subject Subject for the count
50
- # @param [Integer] interval How far back (in seconds) to retrieve activity.
51
- #
52
- # @return [Integer] current count for subject
53
- def count(subject, interval)
54
- bucket = get_bucket
55
- interval = [interval, @bucket_interval].max
56
- count = (interval / @bucket_interval).floor
57
- subject = "#{@key}:#{subject}"
58
-
59
- keys = (0..count - 1).map do |i|
60
- (bucket - i) % @bucket_count
61
- end
62
- redis.hmget(subject, *keys).inject(0) {|a, i| a + i.to_i}
63
- end
64
-
65
- # Check if the rate limit has been exceeded.
66
- #
67
- # @param [String] subject Subject to check
68
- # @param [Hash] options Options hash
69
- # @option options [Integer] :interval How far back to retrieve activity.
70
- # @option options [Integer] :threshold Maximum number of actions
71
- #
72
- # @return [Boolean] true if exceeded
73
- def exceeded?(subject, options = {})
74
- count(subject, options[:interval]) >= options[:threshold]
75
- end
76
-
77
- # Check if the rate limit is within bounds
78
- #
79
- # @param [String] subject Subject to check
80
- # @param [Hash] options Options hash
81
- # @option options [Integer] :interval How far back to retrieve activity.
82
- # @option options [Integer] :threshold Maximum number of actions
83
- #
84
- # @return [Integer] true if within bounds
85
- def within_bounds?(subject, options = {})
86
- !exceeded?(subject, options)
87
- end
88
-
89
- # Execute a block once the rate limit is within bounds
90
- # *WARNING* This will block the current thread until the rate limit is within bounds.
91
- #
92
- # @param [String] subject Subject for this rate limit
93
- # @param [Hash] options Options hash
94
- # @option options [Integer] :interval How far back to retrieve activity.
95
- # @option options [Integer] :threshold Maximum number of actions
96
- # @yield The block to be run
97
- #
98
- # @example Send an email as long as we haven't send 5 in the last 10 minutes
99
- # RedisThrottler.exec_with_threshold(email, [:threshold => 5, :interval => 600]) do
100
- # send_another_email
101
- # end
102
- def exec_within_threshold(subject, options = {}, &block)
103
- options[:threshold] ||= 30
104
- options[:interval] ||= 30
105
- while exceeded?(subject, options)
106
- sleep @bucket_interval
107
- end
108
- yield(self)
109
- end
110
-
111
- private
112
-
113
- def get_bucket(time = Time.now.to_i)
114
- ((time % @bucket_span) / @bucket_interval).floor
115
- end
116
-
117
- def redis
118
- @redis ||= Redis.new(host: '192.168.99.100', port: 32771)
5
+ module RedisThrottler
6
+ def self.included(base)
7
+ base.include(RedisThrottler::Model)
119
8
  end
120
9
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'rspec'
2
2
  require 'redis-throttler'
3
+ require 'redis-throttler/base'
3
4
 
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe RedisThrottler do
4
4
 
5
5
  before do
6
- @rl = RedisThrottler.new('test')
6
+ @rl = RedisThrottler::Base.new('test')
7
7
  @rl.send(:redis).flushdb
8
8
  end
9
9
 
@@ -13,13 +13,13 @@ describe RedisThrottler do
13
13
 
14
14
  it 'should not allow bucket count less than 3' do
15
15
  expect do
16
- RedisThrottler.new('test', {:bucket_span => 1, :bucket_interval => 1})
16
+ RedisThrottler::Base.new('test', {:bucket_span => 1, :bucket_interval => 1})
17
17
  end.to raise_error(ArgumentError)
18
18
  end
19
19
 
20
20
  it 'should not allow bucket expiry to be larger than the bucket span' do
21
21
  expect do
22
- RedisThrottler.new("key", {:bucket_expiry => 1200})
22
+ RedisThrottler::Base.new("key", {:bucket_expiry => 1200})
23
23
  end.to raise_error(ArgumentError)
24
24
  end
25
25
 
@@ -94,7 +94,7 @@ describe RedisThrottler do
94
94
  # end
95
95
 
96
96
  it 'counts correclty if bucket_span equals count-interval ' do
97
- @rl = RedisThrottler.new('key', {:bucket_span => 10, bucket_interval: 1})
97
+ @rl = RedisThrottler::Base.new('key', {:bucket_span => 10, bucket_interval: 1})
98
98
  @rl.add('value1')
99
99
  expect(@rl.count('value1', 10)).to eql(1)
100
100
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-throttler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Surdam
@@ -114,6 +114,7 @@ files:
114
114
  - Rakefile
115
115
  - gemspec.yml
116
116
  - lib/redis-throttler.rb
117
+ - lib/redis-throttler/base.rb
117
118
  - lib/redis-throttler/model.rb
118
119
  - lib/redis-throttler/version.rb
119
120
  - redis-throttler.gemspec