You need to enable JavaScript to run this app.
文档中心
缓存数据库 Redis 版

缓存数据库 Redis 版

复制全文
下载 pdf
最佳实践
切换 Memcached 至缓存数据库 Redis 版
复制全文
下载 pdf
切换 Memcached 至缓存数据库 Redis 版
本文介绍如何将 Memcached 切换至缓存数据库 Redis 版,以及切换后如何实现原子更新和分布式锁的最佳实践。​
背景信息 ​
什么是 Memcached​
Memcached 是一个分布式内存对象缓存系统,主要用于减轻数据库等后端存储系统的负载,支持以 String 类型的键-值(key-value)对形式将数据存储在内存中,使用时可通过特定的键来获取对应的值。Memcached 通常不支持数据持久化(部分第三方扩展可以在一定程度上实现简单的持久化,但并不是 Memcached 的原生特性),这意味着一旦服务器重启或者出现意外故障,内存中的数据就会丢失。​
Memcached 与 Redis 特性对比​
Memcached 与 缓存数据库 Redis 版的功能特性和基础命令的支持情况差异如下表所示。​
​
对比项​
​
缓存数据库 Redis 版​
Memcached​
功能特性​
​
应用场景​
除缓存外,还可以用作消息队列、任务队列、会话存储等场景。更多详情,请参见应用场景。​
主要用作简单缓存。​
​
数据结构​
支持多种数据结构。例如 String、List、Set、 Zset、Hash 等。更多详情,请参见数据类型和扩展数据结构。​
只支持 String 类型的键值对。​
​
集群模式​
支持启用和不启用分片集群两种架构,每种架构都分别支持单节点和主备实例类型。更多详情,请参见产品架构。​
通过第三方工具实现集群模式。​
​
高可用​
缓存数据库 Redis 版支持主从切换和多可用区部署,可有效保证高可用。更多详情,请参见高可用。​
不支持高可用。​
​
持久化​
主备实例默认使用 RDB 快照存储实现持久化,且默认开启 AOF 持久化策略。​
说明​
单节点实例不具备数据持久化能力,不支持任何数据持久化策略。关于主备实例和单节点实例功能特性的更多信息,请参见功能特性差异。​
不支持持久化(通常是临时存储)。​
​
数据压缩​
默认不进行数据压缩。​
可以通过客户端进行压缩。​
​
线程支持​
单线程,使用事件驱动模型。​
多线程。​
​
命令复杂度​
支持丰富的命令和功能(如发布/订阅、事务、Lua 脚本等)。更多详情,请参见命令支持。​
简单的存取命令。​
​
事务支持​
支持通过 MULTI/EXEC 等命令实现事务。更多详情,请参见事务命令限制。​
不支持事务。​
​
过期策略​
支持设置过期时间(TTL)。​
支持设置过期时间(TTL)。​
​
​
逐出策略​
支持通过 maxmemory-policy 参数设置数据驱逐策略。更多详情,请参见设置 Key 的过期驱逐策略。​
内存限制较为简单,通常根据内存大小淘汰数据。​
基础命令​
​
存储数据​
通过 SET 命令来存储 String 类型的数据。​
​
通过 SET 命令来存储数据。​
说明​
Memcached 命令的访问协议格式与 Redis 不完全相同,更多详情,请参见 Memcached storage commands。​
​
获取数据​
通过 GET 命令来获取 String 类型的数据。​
​
通过 GET 或 GETS 命令来获取数据。​
说明​
GETS 命令的返回结果中会包含一个 CAS 唯一标志符(cas unique),该值可与 CAS 命令配合使用实现数据原子更新。​
​
数据原子更新​
原生 Redis 不支持数据原子更新的相关命令,但缓存数据库 Redis 版提供了自研 Proxy 的 CAS 命令,来实现数据原子更新。​
通过 CAS 命令实现数据原子更新。​
说明​
使用 CAS 命令前需要先通过 GETS 命令获取 CAS 唯一标志符(cas unique),才可实现数据原子更新。​
​
删除数据​
通过 DEL 命令删除指定 key。​
通过 DELETE 命令删除指定 key。​
​
设置过期时间​
通过 SET 命令中的 EX 字段来设置 key 的过期时间。​
在使用 SET 命令时可通过 exptime 参数来设置过期时间。若设置为 0 表示永不过期。​
​
检查存在性​
​
通过 EXISTS 命令查询指定的一个或多个 key 是否存在,若 key 不存在会返回 0,若 key 存在会返回存在的 key 数量。​
例如,同时查询 key1、 key2、key3 是否存在:​
  • 若 key1、key2、key3 均不存在,则会返回 0。​
  • 若仅 key1 和 key2 存在,key3 不存在,则会返回 2。​
  • 若 key1、key2、key3 均存在,则会返回 3。​
​
通过 GET 或 GETS 命令查询数据时:​
  • 若指定 key 存在,则会返回 key 所对应的名称(key)、标志(flags)、返回数据的字节数(bytes)、对应的数据(data)、CAS 唯一标志符(cas unique,仅 GETS 命令支持返回该参数)等数据。​
  • 若指定 key 不存在或已过期,则会返回 END。​
​
获取多个键的值​
通过 MGET 命令获取多个 key 的值。​
通过 GET 或 GETS 命令查询多个 key 的值。​
​
批量设置数据​
通过 MSET 命令为多个 key 分别设置对应的值。​
无。​
​
查看统计信息​
通过 INFO 命令查看 Redis 服务器相关设置和统计信息。​
通过 statistics 相关命令查看 Memcached 服务器相关设置和统计信息。 ​
​
清空数据​
通过 FLUSHDB 或 FLUSHALL 命令清空数据。​
通过 FLUSH_ALL 命令清空数据。​
​
为何将 Memcached 切换至缓存数据库 Redis 版 ​
从上述功能特性和基础命令的支持情况差异中可以看出:​
  • Memcached 作为最基础的缓存使用,不支持常见的数据结构类型,例如 String、List、Set、 Zset、Hash 等,使用不便。​
  • Memcached 缺少高可用能力,使用中无法保证服务可靠。​
  • Memcached 多年来市场占有率逐步降低,不再支持主流服务框架,旧的服务框架也缺少维护。​
  • 缓存数据库 Redis 版提供的是托管型的缓存数据库服务,兼容 Redis 数据库引擎,帮助您在云上轻松、快速地构建 Redis 数据库。缓存数据库 Redis 版提供了高性能且安全的 Redis 数据库解决方案,按需计费结合动态扩展能力能够显著地帮助企业降低成本,同时,它也有助于消除管理、运维数据库的复杂性。更多详情,请参见什么是缓存数据库 Redis 版。​
如何将 Memcached 切换至缓存数据库 Redis 版 ​
仅需如下两步即可将 Memcached 切换至缓存数据库 Redis 版:​
  1. 创建一个 Redis 7.0 社区版实例。具体操作步骤,请参见创建实例。​
  1. 提交工单联系技术支持申请使用 ExString 数据结构。您可以通过 ExString 数据结构实现业务双写和预热缓存。关于 ExString 数据结构的更多信息,请参见 ExString。​
最佳实践 1:数据原子更新​
通过 CAS 实现数据原子更新​
假设已通过 SET 命令创建了一个 key doctest,且希望仅当 doctest 的值为 5 时,将 doctest 值更新为 100。那么,您可以通过缓存数据库 Redis 版的自研的 CAS 命令实现上述需求。命令示例如下。​
​
# 传入的值与 doctest 已设置的值一致,更新成功,doctest 的值会被新值覆盖​
CAS doctest 5 100​
# 传入的值与 doctest 已设置的值不一致,更新失败。​
CAS doctest 6 100​
​
通过 EXSET 实现原子更新​
Memcached 的 CAS 命令以及缓存数据库 Redis 版 ExString 数据结构所支持的 EXSET 命令,均可用于实现数据的原子更新。​
您可以将依赖 Memcached 的 CAS 命令,迁移至缓存数据库 Redis 版 ExString 数据结构所支持的 EXSET 命令,以实现数据原子更新。两者命令的使用方式对比如下。​
​
缓存数据库 Redis 版命令使用示例
Memcached 命令使用示例
​
  1. 先通过 EXGET 命令获取指定的 version。命令示例如下。​
  • ​
    EXGET doctestkey_2​
    ​
返回示例如下。​
  • ​
    1) "doctestvalue_2a"​
    2) (integer) 2​
    ​
  1. 再通过 EXSET 命令设置指定 version 的 value。命令示例如下。​
  • ​
    EXSET doctestkey_2 doctestvalue_2b ver 2​
    ​
返回示例如下。​
  • ​
    OK​
    ​
再次执行 EXGET 命令确认 value 和 version。命令示例如下。​
  • ​
    EXGET doctestkey_2​
    ​
返回示例如下。已成功写入新值,且 version 递增 1。​
  • ​
    1) "doctestvalue_2b"​
    2) (integer) 3​
    ​
  • ​
​
最佳实践 2 :分布式锁实现​
注意​
由于 Redis 的主从复制采用异步机制,在主节点故障(Failover)时,可能导致最近已确认写入的数据丢失。若丢失的数据涉及分布式锁的状态信息(如锁持有者标识、锁有效期等),则可能破坏锁的互斥性或安全性,进而引发资源冲突或数据不一致等严重业务异常。关于 Redis 主从切换影响的更多详情,请参见切换影响及建议。​
基于 CAS 的分布式锁​
开源 Redis 支持在 SET 命令中通过 NX 和 EX 参数实现分布式锁。例如,当执行 SET unique_lock 1735876863 NX EX 5 命令时,表示:​
  • 创建一个名为 unique_lock 的锁对象。​
  • 且仅当不存在该对象时才会被创建,若创建成功,表示加锁成功。​
  • 锁自动在 5 秒后过期,防止出现死锁。​
但上述加锁逻辑却存在天然缺陷。例如,当用户 1 希望解锁时却由于系统问题阻塞了一段时间,导致用户 1 解锁时用户 2 已成功加锁,此时若用户 1 再次尝试加锁则会成功获取锁,那么用户 1 和用户 2 极有可能形成数据竞态。​
​
正常加锁解锁示例
异常解锁导致锁冲突示例
​
​​​
​
由于开源 Redis 不支持 CAS,为实现原子操作,业务常常需要引入复杂的 Lua 脚本。可尽管如此,在极端情况下仍然存在不可靠锁事件。为解决上述问题,缓存数据库 Redis 版在自研 Proxy 命令中支持了 CAS 和 CAD 操作,可以通过原子操作实现标准的分布式锁用法。​
例如,您可以通过如下命令创建一个锁对象,并为其设置锁过期时间。命令示例如下。​
​
SET unique_lock 10.1.1.1:6379 NX EX 5​
​
加锁成功后,您可以通过如下命令进行解锁。命令示例如下。​
​
CAD unique_lock 10.1.1.1:6379​
​
您还可以通过如下命令定时更新锁的过期时间,实现锁的续租。​
​
CAS unique_lock 10.1.1.1:6379 EX 5​
​
基于 Exstring 的乐观锁​
缓存数据库 Redis 版提供的 ExString 在原生 Redis String 数据结构的基础上,额外支持携带版本(version)信息,且版本信息允许修改。当版本信息不匹配导致 Exstring 更新失败时,会返回 update version is stale 错误提示。基于上述逻辑,您可以通过循环执行 EXCAS 命令来实现乐观锁模型。​
乐观锁加锁代码示例如下(本文以 Python 语言为例)。​
​
while(true){​
{ret, value, version} = excas(key, new_value, old_version) ​
# 直接尝试用 EXCAS 命令置换 Value​
if(ret == OK)​
break; # 如果返回值为 OK 则更新成功,跳出循环​
else (if ret.contains("update version is stale")) ​
# 如果返回值包含update version is stale,表示由于版本信息不匹配导致更新失败,此时更新两个变量​
update(value);​
old_version = version;​
}​
​
基于 Exstring 实现更高级别的锁粒度控制​
缓存数据库 Redis 版支持在 EXSET 命令中使用如下 3 种 version 相关参数,帮助实现更高级别的锁粒度控制:​
  • VER version:指定版本号。当 key 存在且传入的版本号和 key 当前的版本号相等时,版本号会在原来的版本号基础上加 1。​
  • ABS version:绝对版本号。设置后会直接写入 value,并将 key 的版本号修改为该选项所设的版本号。​
  • GT version:比较版本号。 设置后,只要指定 key 的版本号大于指定的 GT 版本号就会写入 value,且版本号会在原来的版本号基础上加 1。该参数在放宽锁限制粒度的同时,还能在一定程度上对并发进行控制。​
例如,您可以通过 GT version 参数,当高于指定版本号时,实现加锁自动生效的互斥锁。代码示例如下(本文以 Python 语言为例)。​
​
while(true){​
{ret, value, version} = exset(key, new_value, old_version, GT=true) ​
# 在 EXSET 命令中使用 GT version 参数,若大于指定的 old_version,则置换 Value​
if(ret == OK)​
break; # 如果返回值为 OK 则更新成功,跳出循环​
else (if ret.contains("update version is stale")) ​
# 如果返回值包含update version is stale,表示由于版本信息不匹配导致更新失败,此时更新两个变量​
update(value);​
old_version = version;​
}​
​
​
最近更新时间:2025.12.26 11:33:35
这个页面对您有帮助吗?
有用
有用
无用
无用