本文介绍如何将 Memcached 切换至缓存数据库 Redis 版,以及切换后如何实现原子更新和分布式锁的最佳实践。
Memcached 是一个分布式内存对象缓存系统,主要用于减轻数据库等后端存储系统的负载,支持以 String 类型的键-值(key-value)对形式将数据存储在内存中,使用时可通过特定的键来获取对应的值。Memcached 通常不支持数据持久化(部分第三方扩展可以在一定程度上实现简单的持久化,但并不是 Memcached 的原生特性),这意味着一旦服务器重启或者出现意外故障,内存中的数据就会丢失。
Memcached 与 缓存数据库 Redis 版的功能特性和基础命令的支持情况差异如下表所示。
为何将 Memcached 切换至缓存数据库 Redis 版
从上述功能特性和基础命令的支持情况差异中可以看出:
- Memcached 作为最基础的缓存使用,不支持常见的数据结构类型,例如 String、List、Set、 Zset、Hash 等,使用不便。
- Memcached 缺少高可用能力,使用中无法保证服务可靠。
- Memcached 多年来市场占有率逐步降低,不再支持主流服务框架,旧的服务框架也缺少维护。
- 缓存数据库 Redis 版提供的是托管型的缓存数据库服务,兼容 Redis 数据库引擎,帮助您在云上轻松、快速地构建 Redis 数据库。缓存数据库 Redis 版提供了高性能且安全的 Redis 数据库解决方案,按需计费结合动态扩展能力能够显著地帮助企业降低成本,同时,它也有助于消除管理、运维数据库的复杂性。更多详情,请参见什么是缓存数据库 Redis 版。
如何将 Memcached 切换至缓存数据库 Redis 版
仅需如下两步即可将 Memcached 切换至缓存数据库 Redis 版:
- 创建一个 Redis 7.0 社区版实例。具体操作步骤,请参见创建实例。
- 提交工单联系技术支持申请使用 ExString 数据结构。您可以通过 ExString 数据结构实现业务双写和预热缓存。关于 ExString 数据结构的更多信息,请参见 ExString。
假设已通过 SET 命令创建了一个 key doctest,且希望仅当 doctest 的值为 5 时,将 doctest 值更新为 100。那么,您可以通过缓存数据库 Redis 版的自研的 CAS 命令实现上述需求。命令示例如下。 Memcached 的 CAS 命令以及缓存数据库 Redis 版 ExString 数据结构所支持的 EXSET 命令,均可用于实现数据的原子更新。 您可以将依赖 Memcached 的 CAS 命令,迁移至缓存数据库 Redis 版 ExString 数据结构所支持的 EXSET 命令,以实现数据原子更新。两者命令的使用方式对比如下。
缓存数据库 Redis 版命令使用示例
Memcached 命令使用示例
- 先通过 EXGET 命令获取指定的 version。命令示例如下。
返回示例如下。
- 再通过 EXSET 命令设置指定 version 的 value。命令示例如下。
返回示例如下。
再次执行 EXGET 命令确认 value 和 version。命令示例如下。
返回示例如下。已成功写入新值,且 version 递增 1。
- 先通过 GETS 命令获取指定 key 的 version。命令示例如下。
- 再通过 CAS 命令设置指定 version 的 value。命令示例如下。
返回示例如下。
注意
由于 Redis 的主从复制采用异步机制,在主节点故障(Failover)时,可能导致最近已确认写入的数据丢失。若丢失的数据涉及分布式锁的状态信息(如锁持有者标识、锁有效期等),则可能破坏锁的互斥性或安全性,进而引发资源冲突或数据不一致等严重业务异常。关于 Redis 主从切换影响的更多详情,请参见切换影响及建议。 开源 Redis 支持在 SET 命令中通过 NX 和 EX 参数实现分布式锁。例如,当执行 SET unique_lock 1735876863 NX EX 5 命令时,表示:
- 创建一个名为 unique_lock 的锁对象。
- 且仅当不存在该对象时才会被创建,若创建成功,表示加锁成功。
但上述加锁逻辑却存在天然缺陷。例如,当用户 1 希望解锁时却由于系统问题阻塞了一段时间,导致用户 1 解锁时用户 2 已成功加锁,此时若用户 1 再次尝试加锁则会成功获取锁,那么用户 1 和用户 2 极有可能形成数据竞态。
由于开源 Redis 不支持 CAS,为实现原子操作,业务常常需要引入复杂的 Lua 脚本。可尽管如此,在极端情况下仍然存在不可靠锁事件。为解决上述问题,缓存数据库 Redis 版在自研 Proxy 命令中支持了 CAS 和 CAD 操作,可以通过原子操作实现标准的分布式锁用法。 例如,您可以通过如下命令创建一个锁对象,并为其设置锁过期时间。命令示例如下。
加锁成功后,您可以通过如下命令进行解锁。命令示例如下。
您还可以通过如下命令定时更新锁的过期时间,实现锁的续租。
缓存数据库 Redis 版提供的 ExString 在原生 Redis String 数据结构的基础上,额外支持携带版本(version)信息,且版本信息允许修改。当版本信息不匹配导致 Exstring 更新失败时,会返回 update version is stale 错误提示。基于上述逻辑,您可以通过循环执行 EXCAS 命令来实现乐观锁模型。 乐观锁加锁代码示例如下(本文以 Python 语言为例)。
基于 Exstring 实现更高级别的锁粒度控制
缓存数据库 Redis 版支持在 EXSET 命令中使用如下 3 种 version 相关参数,帮助实现更高级别的锁粒度控制: - VER version:指定版本号。当 key 存在且传入的版本号和 key 当前的版本号相等时,版本号会在原来的版本号基础上加 1。
- ABS version:绝对版本号。设置后会直接写入 value,并将 key 的版本号修改为该选项所设的版本号。
- GT version:比较版本号。 设置后,只要指定 key 的版本号大于指定的 GT 版本号就会写入 value,且版本号会在原来的版本号基础上加 1。该参数在放宽锁限制粒度的同时,还能在一定程度上对并发进行控制。
例如,您可以通过 GT version 参数,当高于指定版本号时,实现加锁自动生效的互斥锁。代码示例如下(本文以 Python 语言为例)。