Redis对于Rails生产I18n来说太慢了吗?

Whe*_*yls 11 ruby-on-rails internationalization redis

我最近从默认的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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

对我来说绝对有意义,为什么I18n的速度很慢...我在整个代码库中排队了几十个I18n.如果我可以在前面将它们一起批量处理,我会保持良好状态:

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

但我真的没有看到任何现有I18n后端的干净方法.有没有人解决这个问题?

编辑

我采用了Chris Heald的建议并创建了一个后备文件,其中包含一个简单的缓存区域.要点在这里:

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

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

UPDATE

我的解决方案现在可用作宝石:

https://github.com/wheeyls/cached_key_value_store

我还在博客上写了这个问题:

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

Chr*_*ald 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
Run Code Online (Sandbox Code Playgroud)

我刚从阅读I18n源代码中解决了这个问题,而我根本没有测试它,所以它可能需要一些工作,但我认为它足够好地传达了这个想法.