From c335b9a2145b073e97187457a104c1d9e4b0a1de Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Wed, 23 Mar 2016 16:52:25 +0800 Subject: [PATCH] Released 0.1.5 --- README.md | 18 ++++---- project.clj | 2 +- src/clj_rate_limiter/core.clj | 79 ++++++++++++++++++++--------------- 3 files changed, 56 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index a7d2535..507709b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It's transformed from [rolling-rate-limiter](https://github.com/classdojo/rollin Leiningen: ```clj -[clj-rate-limiter "0.1.3"] +[clj-rate-limiter "0.1.5"] ``` ### Basic @@ -20,12 +20,12 @@ Create an in-memory rate limiter with maximum 100 requsts in 1 seconds: (require '[clj-rate-limiter.core :as r]) (def limiter (r/create (r/rate-limiter-factory :memory - :interval 1000 + :interval 1000 :max-in-interval 100))) - -(println (r/allow? limiter "key1")) -(println (r/allow? limiter "key2")) -(count (filter true? (repeatedly 1000 #(r/allow? limiter "key3")))) ;;should be 100 + +(println (r/allow? limiter "key1")) +(println (r/allow? limiter "key2")) +(count (filter true? (repeatedly 1000 #(r/allow? limiter "key3")))) ;;should be 100 ``` The `:interval` sets the time window unit in millseconds, and `:max-in-interval` sets the maximum requests in a time window, and `:memory` make the rate limiter store requests in memory. @@ -34,13 +34,13 @@ After the limiter is created, you can use `(allow? limiter key)` to test if the In a cluster, you may want to created a redis-backed limiter: ```clj -;;redis spec and pool as decribed in +;;redis spec and pool as decribed in ;;https://github.com/ptaoussanis/carmine (def redis {:spec {:host "localhost" :port 6379 :timeout 5000} :pool {:max-active (* 3 (.availableProcessors (Runtime/getRuntime))) :min-idle (.availableProcessors (Runtime/getRuntime)) :max-wait 5000}}) - + (def limiter (r/create (r/rate-limiter-factory :redis :redis redis @@ -62,7 +62,7 @@ You can set the minimum time in millseconds between requests by `:min-difference :redis redis :namespace "APIs" :interval 1000 - :min-difference 1 + :min-difference 1 :max-in-interval 100))) ``` diff --git a/project.clj b/project.clj index 7d8922a..852ab99 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject clj-rate-limiter "0.1.4" +(defproject clj-rate-limiter "0.1.5" :description "Rate limiter for clojure that supports a rolling window, either in-memory or backed by redis" :url "https://github.com/killme2008/clj-rate-limiter" :license {:name "Eclipse Public License" diff --git a/src/clj_rate_limiter/core.clj b/src/clj_rate_limiter/core.clj index 326a5d3..7837ff1 100644 --- a/src/clj_rate_limiter/core.clj +++ b/src/clj_rate_limiter/core.clj @@ -187,13 +187,13 @@ (remove-permit [_ id ts] (let [id (or id "") key (format "%s-%s" namespace id) - before (- ts (mills->nanos interval))] - (when ts + before (- (System/nanoTime) (mills->nanos interval))] + (when (and ts (pos? ts)) (car/wcar {:spec redis :pool pool} (car/multi) (car/zrem key ts) - (car/zremrangebyscore key 0 before) + (car/zremrangebyscore (release-key key) 0 before) (car/zadd (release-key key) ts ts) (car/expire (release-key key) (long (Math/ceil (/ interval 1000)))) @@ -216,33 +216,46 @@ (comment (defn- benchmark [] - (let [rf (rate-limiter-factory :redis - :redis {:spec {:host "localhost" :port 6379 :timeout 5000} - :pool {:max-active (* 3 (.availableProcessors (Runtime/getRuntime))) - :min-idle (.availableProcessors (Runtime/getRuntime)) - :max-wait 5000}} - :flood-threshold 10 - :interval 1000 - :max-in-interval 1000) - r (create rf) - cost (atom {:sum 0 :times 0}) - ts 100 - cl (java.util.concurrent.CountDownLatch. ts)] - (time - (do - (dotimes [n ts] - (-> - (fn [] - (dotimes [m 10000] - (let [{:keys [ts result total current]} (permit? r (mod m 20))] - (when (> total current) - (swap! cost (fn [{:keys [sum times]} s] - {:sum (+ sum s) - :times (inc times)}) - (- total current))) - (remove-permit r (mod m 20) ts))) - (.countDown cl)) - (Thread.) - (.start))) - (.await cl) - (println @cost)))))) + (let [rf (rate-limiter-factory :redis + :redis {:spec {:host "localhost" :port 6379 :timeout 5000} + :pool {:max-active (* 3 (.availableProcessors (Runtime/getRuntime))) + :min-idle (.availableProcessors (Runtime/getRuntime)) + :max-wait 5000}} + :flood-threshold 10 + :interval 1000 + :max-in-interval 100000) + r (create rf) + cost (atom {:total 0 + :current 0 + :max_total 0 + :max_concurrent 0 + :times 0}) + ts 100 + cl (java.util.concurrent.CountDownLatch. ts)] + (time + (do + (dotimes [n ts] + (-> + (fn [] + (dotimes [m 10000] + (let [{:keys [ts result total current]} (permit? r (mod m 1))] + (when result + (swap! cost (fn [{:keys [total current + times max_total max_concurrent]} t c] + {:total (+ total t) + :current (+ current c) + :max_total (if (< max_total t) + t + max_total) + :max_concurrent (if (< max_concurrent c) + c + max_concurrent) + :times (inc times)}) + total + current)) + (remove-permit r (mod m 1) ts))) + (.countDown cl)) + (Thread.) + (.start))) + (.await cl) + (println @cost))))))