跳至内容 跳至搜索

Redis 缓存存储

部署注意事项:请务必使用专用 Redis 缓存,而不是指向持久化 Redis 服务器(例如,用作 Active Job 队列的服务器)。Redis 无法很好地应对混合使用模式,并且默认情况下不会过期缓存条目。

Redis 缓存服务器设置指南:redis.io/topics/lru-cache

  • 支持原生 Redis、hiredis 和 Redis::Distributed

  • 支持使用 Redis::Distributed 在多个 Redis 之间进行 Memcached 风格的切片。

  • 容错。如果 Redis 服务器不可用,不会抛出异常。 Cache 的所有获取操作都将是未命中,写入操作将被丢弃。

  • 本地缓存。块/中间件范围内的热内存主缓存。

  • read_multiwrite_multi 支持 Redis mget/mset。使用 Redis::Distributed 4.0.1+ 进行分布式 mget 支持。

  • delete_matched 支持 Redis KEYS glob 模式。

方法
C
D
I
N
R
S

常量

DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do if logger logger.error { "RedisCacheStore: #{method} 失败,返回 #{returning.inspect}: #{exception.class}: #{exception.message}" } end ActiveSupport.error_reporter&.report( exception, severity: :warning, source: "redis_cache_store.active_support", ) end
 
DEFAULT_REDIS_OPTIONS = { connect_timeout: 1, read_timeout: 1, write_timeout: 1, }
 
MAX_KEY_BYTESIZE = 1024
 

如果键超过 1kB,则使用 Active Support 摘要进行截断

属性

[R] max_key_bytesize
[R] redis

类公共方法

new(error_handler: DEFAULT_ERROR_HANDLER, **redis_options)

创建一个新的 Redis 缓存存储。

有四种方式提供缓存使用的 Redis 客户端::redis 参数可以是 Redis 实例或返回 Redis 实例的代码块,或者 :url 参数可以是字符串或字符串数组,用于创建 Redis 实例或 Redis::Distributed 实例。

Option  Class       Result
:redis  Proc    ->  options[:redis].call
:redis  Object  ->  options[:redis]
:url    String  ->  Redis.new(url: …)
:url    Array   ->  Redis::Distributed.new([{ url: … }, { url: … }, …])

默认情况下不会设置命名空间。如果 Redis 缓存服务器与其他应用程序共享,请提供命名空间:namespace: 'myapp-cache'

默认情况下启用压缩,阈值为 1kB,因此大于 1kB 的缓存值会自动压缩。通过传递 compress: false 禁用压缩,或通过传递 compress_threshold: 4.kilobytes 更改阈值。

默认情况下不会在缓存条目上设置过期时间。预计 Redis 已配置为使用自动删除最久未使用或最不常用键的逐出策略,当达到最大内存时。有关缓存服务器设置,请参见 redis.io/topics/lru-cache

默认情况下不设置竞争条件 TTL。这可用于避免热缓存条目过期时的“惊群效应”缓存写入。有关详细信息,请参见 ActiveSupport::Cache::Store#fetch

设置 skip_nil: true 将不会缓存 nil 结果

cache.fetch('foo') { nil }
cache.fetch('bar', skip_nil: true) { nil }
cache.exist?('foo') # => true
cache.exist?('bar') # => false
# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 149
def initialize(error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
  universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)

  if pool_options = self.class.send(:retrieve_pool_options, redis_options)
    @redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
  else
    @redis = self.class.build_redis(**redis_options)
  end

  @max_key_bytesize = MAX_KEY_BYTESIZE
  @error_handler = error_handler

  super(universal_options)
end

supports_cache_versioning?()

宣布支持缓存版本控制。

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 63
def self.supports_cache_versioning?
  true
end

实例公共方法

cleanup(options = nil)

Cache Store API 实现。

删除过期条目。由 Redis 的最久未使用/最不常用过期时间原生处理,因此不支持手动清理。

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 282
def cleanup(options = nil)
  super
end

clear(options = nil)

清除所有 Redis 服务器上的整个缓存。如果缓存已命名空间,则可以在共享服务器上安全使用。

故障安全:引发错误。

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 290
def clear(options = nil)
  failsafe :clear do
    if namespace = merged_options(options)[:namespace]
      delete_matched "*", namespace: namespace
    else
      redis.then { |c| c.flushdb }
    end
  end
end

decrement(name, amount = 1, options = nil)

使用 Redis decrby 原子操作递减缓存的整数值。返回更新后的值。

如果键未设置或已过期,它将被设置为 -amount

cache.decrement("foo") # => -1

要设置特定值,请调用 write 并传递 raw: true

cache.write("baz", 5, raw: true)
cache.decrement("baz") # => 4

递减非数值或未使用 raw: true 写入的值将失败并返回 nil

故障安全:引发错误。

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 267
def decrement(name, amount = 1, options = nil)
  options = merged_options(options)
  key = normalize_key(name, options)

  instrument :decrement, key, amount: amount do
    failsafe :decrement do
      change_counter(key, -amount, options)
    end
  end
end

delete_matched(matcher, options = nil)

Cache Store API 实现。

支持 Redis KEYS glob 模式

h?llo matches hello, hallo and hxllo
h*llo matches hllo and heeeello
h[ae]llo matches hello and hallo, but not hillo
h[^e]llo matches hallo, hbllo, ... but not hello
h[a-b]llo matches hallo and hbllo

如果要按字面意思匹配特殊字符,请使用 \ 转义特殊字符。

有关详细信息,请参见 redis.io/commands/KEYS

故障安全:引发错误。

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 201
def delete_matched(matcher, options = nil)
  unless String === matcher
    raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
  end
  pattern = namespace_key(matcher, options)

  instrument :delete_matched, pattern do
    redis.then do |c|
      cursor = "0"
      # Fetch keys in batches using SCAN to avoid blocking the Redis server.
      nodes = c.respond_to?(:nodes) ? c.nodes : [c]

      nodes.each do |node|
        begin
          cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
          node.del(*keys) unless keys.empty?
        end until cursor == "0"
      end
    end
  end
end

increment(name, amount = 1, options = nil)

使用 Redis incrby 原子操作递增缓存的整数值。返回更新后的值。

如果键未设置或已过期,它将被设置为 amount

cache.increment("foo") # => 1
cache.increment("bar", 100) # => 100

要设置特定值,请调用 write 并传递 raw: true

cache.write("baz", 5, raw: true)
cache.increment("baz") # => 6

递增非数值或未使用 raw: true 写入的值将失败并返回 nil

故障安全:引发错误。

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 240
def increment(name, amount = 1, options = nil)
  options = merged_options(options)
  key = normalize_key(name, options)

  instrument :increment, key, amount: amount do
    failsafe :increment do
      change_counter(key, amount, options)
    end
  end
end

inspect()

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 164
def inspect
  "#<#{self.class} options=#{options.inspect} redis=#{redis.inspect}>"
end

read_multi(*names)

Cache Store API 实现。

一次读取多个值。返回请求的键 -> 获取的值的哈希表。

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 172
def read_multi(*names)
  return {} if names.empty?

  options = names.extract_options!
  options = merged_options(options)
  keys    = names.map { |name| normalize_key(name, options) }

  instrument_multi(:read_multi, keys, options) do |payload|
    read_multi_entries(names, **options).tap do |results|
      payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
    end
  end
end

stats()

从 Redis 服务器获取信息。

# File activesupport/lib/active_support/cache/redis_cache_store.rb, line 301
def stats
  redis.then { |c| c.info }
end