关于消息传递:Clojure中限制core.async通道的速率

Rate limiting core.async channels in Clojure

我正在将Clojure与core.async一起使用,并且遇到一种情况,我想对通过通道处理的消息数设置速率限制。

我特别想:

  • 定义速率限制,例如 每秒1,000条消息
  • 只要消息数量少于速率限制,就可以正常(及时)处理消息
  • 如果超出了速率限制,请对事件进行某种明智的替代处理(例如,告诉客户稍后重试)
  • 具有相当低的开销

实现此目标的最佳方法是什么?


问题分类:

  • 定义速率限制,例如每秒1,000条消息
  • 只要消息数量少,就可以正常(及时)处理消息
    比速率限制
  • 如果超出了速率限制,请对事件进行某种明智的替代处理(例如,告诉客户
    稍后再试)
  • 具有相当低的开销
  • 我正在通过简单地在循环中组成通道的解决方案来解决这个问题。

    一种常见的速率限制算法称为令牌桶。您有固定大小的令牌桶,并以固定速率添加令牌。只要您有令牌,就可以发送消息。

    桶的大小决定了"突发性"(您能以多快的速度赶上最大速率),而速率决定了最大平均速率。这些将是我们代码的参数。

    让我们创建一个以给定速率发送消息(无关紧要)的通道。 (#1)

    1
    2
    3
    4
    5
    6
    7
    8
    (defn rate-chan [burstiness rate]
      (let [c (chan burstiness) ;; bucket size is buffer size
            delta (/ 1000 rate)]
        (go
          (while true
            (>! c :go) ;; send a token, will block if bucket is full
            (<! (timeout delta)))) ;; wait a little
        c))

    现在我们想要一个通过速率限制另一个通道的通道。 (#2)

    1
    2
    3
    4
    5
    6
    7
    (defn limit-chan [in rc]
      (let [c (chan)]
        (go
          (while true
            (<! rc) ;; wait for token
            (>! c (<! in)))) ;; pass message along
        c))

    现在,如果没有消息等待,我们可以使用默认的这些渠道:

    1
    2
    3
    4
    5
    6
    7
    (defn chan-with-default [in]
      (let [c (chan)]
        (go
          (while true
            ;; take from in, or if not available, pass useful message
            (>! c (alts! [in] :default :rate-exceeded))))
        c))

    现在,我们已经解决了所有问题。

    1
    2
    3
    (def rchan (-> (chan)
                   (limit-chan (rate-chan 100 1000))
                   (chan-with-default)))

    就#4而言,这并不是绝对最快的解决方案。但这是使用可组合部件的部件,可能足够快。如果您希望更快,可以做一个循环来完成所有这些操作(而不是将其分解为较小的函数)。最快的方法是自己实现接口。


    我写了一个小图书馆来解决这个问题。它的实现与Eric Normand的实现极为相似,但采用了一些针对高吞吐量通道的措施(对于近毫秒的睡眠时间,超时并不精确)。

    它还支持对一组通道进行节流,并进行节流。

    在这里查看。


    这是一种使用原子计数发送的消息并将其定期重置为零的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    (def counter (atom 0))

    (def time-period 1000) ;milliseconds

    (def max-rate 1000) ;max number of messages per time-period

    (def ch (chan))

    (defn alert-client []
      (println"That's enough!"))

    (go (while true (<! (timeout time-period)) (reset! counter 0))); reset counter periodically

    (defn process [msg]
      (if (> (swap! counter inc) max-rate) (alert-client) (put! ch msg)))

    (doseq [x (range 1001)] (process x)) ; throw some messages at the channel

    您将需要更多代码来使用通道中的消息。如果您不确定是否能够以节流的速率持续使用消息,则可能需要指定通道缓冲区的大小或通道类型(丢弃/滑动)。


    您在寻找什么被称为断路器。我认为Wikipedia页面的描述很差:

    http://en.wikipedia.org/wiki/Circuit_breaker_design_pattern

    不过,我们的Scala朋友做的绝对出色:

    http://doc.akka.io/docs/akka/2.2.3/common/circuitbreaker.html

    还有一个clojure库,但是您必须自己与core.async进行集成:

    https://github.com/krukow/clojure-circuit-breaker

    https://github.com/josephwilk/circuit-breaker

    关于断路器和Clojure扩展的博客文章:

    http://blog.josephwilk.net/clojure/building-clojure-services-at-scale.html

    看起来您可能想要考虑提供Clojure绑定的netflix Hystrix之类的东西:

    https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-clj

    高温超导