11

我最近从默认的 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/

4

1 回答 1

5

网络流量总是比本地工作慢。您可能会考虑使用内存中的缓存,然后只需在每个请求(甚至只是一个短计时器)上提取当前本地化版本,以确定是否使缓存无效。看起来有一个 Memoization 模块(根据此处的源代码),您可以将其混合到 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 源代码中破解了这个,我根本没有测试它,所以它可能需要一些工作,但我认为它很好地传达了这个想法。

于 2013-05-25T05:07:25.857 回答