【问题标题】:Is Redis too slow for Rails production I18n?对于 Rails 生产 I18n,Redis 是否太慢?
【发布时间】:2013-05-25 03:24:44
【问题描述】:

我最近从默认的 Simple I18n 后端切换到 I18n 的 Redis 后端。我这样做是为了让我们更容易处理翻译,但我发现每个页面的性能都会受到很大影响。

我在 MBP 上安装了 Rails 3.2 和 Redis 2.6.4 运行了一些基准测试来演示。我使用hiredis-rb 作为我的客户。

在执行两个不同的后端时,这是一个非常明显的区别。使用简单的后端,第一次调用会有短暂的延迟——我假设翻译正在加载到内存中——然后性能很好:

pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.143246
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.00415
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.004153
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.004056

Redis 后端一直很慢:

pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.122448
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.263564
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.232637
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.122304

这对我来说是绝对有道理的,为什么这对 I18n 来说很慢...我在整个代码库中排队了几十个 I18n 调用。如果我能把它们放在一起,我的状态会很好:

pry(main)> keys = $redis.keys[0..500]
pry(main)> Benchmark.realtime { $redis.mget keys }
=> 0.04264

但我并没有真正看到使用任何现有 I18n 后端执行此操作的干净方法。有没有人解决过这个问题?

编辑

我接受了 Chris Heald 的建议,并创建了一个带有记忆功能的后端,一个简单的缓存崩溃。要点在这里:

https://gist.github.com/wheeyls/5650947

我会试试这个几天,然后把它变成一个宝石。

更新

我的解决方案现在可以作为 gem 使用:

https://github.com/wheeyls/cached_key_value_store

我还写了一篇关于这个问题的博客:

http://about.g2crowd.com/faster-i18nredis-on-rails/

【问题讨论】:

  • 相对于运行这些基准测试的机器,您的 redis 服务器在哪里?
  • 这些是在我的本地机器上运行的。我将编辑我的问题以澄清。

标签: ruby-on-rails internationalization redis


【解决方案1】:

网络流量总是比本地工作慢。您可能会考虑使用内存中的缓存,然后只需在每个请求(甚至只是一个短计时器)上提取当前本地化版本,以确定是否使缓存无效。看起来有一个 Memoization 模块(每个 the source here),您可以将其混入 I18n 界面。然后,我们只需调整 #lookup 方法,使其每 5 分钟检查一次 Redis 是否有更新的语言环境版本,并确保在保存新翻译时增加语言环境版本。

这为您提供了所有翻译的内存缓存,因此查找速度非常快,同时让您能够即时更改翻译 - 您的翻译可能需要长达 5 分钟的时间来更新,但您不必进行任何显式的缓存清除。

如果您愿意,您可以使用before_filter 对每个请求进行检查,而不仅仅是使用延迟 5 分钟到期,这意味着对 redis 的更多请求,但您不会看到任何陈旧的翻译。

module I18n
  module Backend
    class CachedKeyValueStore < KeyValue
      include Memoize

      def store_translations(locale, data, options = {})
        @store.incr "locale_version:#{locale}"
        reset_memoizations!(locale)
        super
      end

      def lookup(locale, key, scope = nil, options = {})
        ensure_freshness(locale)
        flat_key  = I18n::Backend::Flatten.normalize_flat_keys(locale,
          key, scope, options[:separator]).to_sym
        flat_hash = memoized_lookup[locale.to_sym]
        flat_hash.key?(flat_key) ? flat_hash[flat_key] : (flat_hash[flat_key] = super)
      end

      def ensure_freshness(locale)
        @last_check ||= 0

        if @last_check < 5.minutes.ago
          @last_check = Time.now
          current_version = @store.get "locale_version:#{locale}"
          if @last_version != current_version
            reset_memoizations! locale
            @last_version = current_version
          end
        end
      end
    end
  end
end

我只是从阅读 I18n 源代码中破解了这个,我根本没有测试它,所以它可能需要一些工作,但我认为它很好地传达了这个想法。

【讨论】:

  • 漂亮。这基本上是我想到的解决方案,只有更好。我想知道为什么似乎没有其他人处理过这个问题?
  • 不错。我打算把它粘起来,放在我以后用的那堆里。 :)
猜你喜欢
  • 1970-01-01
  • 2020-10-19
  • 2011-05-26
  • 1970-01-01
  • 1970-01-01
  • 2017-10-03
  • 2012-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多