确保(分布式)缓存只存储分布式系统中的最新值
Making sure (distributed) caches only store the latest value in a distributed system
假设我想使用 Redis 或 Memcached 等内置解决方案来缓存数据库行(作为示例),以避免反复访问数据库,代价高昂。
为了论证,假设我有一个 TABLE(id, x, y) 并且我想缓存所有行,这样我就不必直接从数据库中读取。
问题:
- 考虑以下情况:NodeA 尝试更新给定行的字段
x
,而 NodeB 尝试更新 y
,然后两者同时尝试更新缓存行。如果他们尝试“手动”更新他们刚刚更改的字段到缓存中的行,如果我们遵循典型的最后写入获胜,其中一个字段将被丢弃,这是灾难性的。这让我觉得我需要始终用从数据库中读取的完整行来填充缓存的行。
- 但这本身不一定对我有帮助。如果 NodeA 写入
x
并将整行加载到内存中,然后 NodeB 写入 y
并读取内存中的整行,如果 NodeB 在 NodeA 之前写入缓存,则 NodeB 的更改将被覆盖!这让我相信我需要始终以某种方式对数据库和缓存中的行进行版本控制。是这样吗? Memcached 似乎有比较和设置原语,但我在 Redis 中没有看到这样的东西。
- 即使1.和2.不是问题,我仍然需要保证我的写/读具有写后读的一致性,否则可能会发生正在阅读和打算放入的内容缓存不一定是最新版本。如果是这样,我怎样才能确定这一点?通过要求 w + r > n?
这似乎是一个非常常见的用例,我想这几乎是一个已解决的问题。我可以尝试解决这个问题吗?
redis 的键值存储支持高级数据结构,例如 HASH。
如果您正在对缓存的实体进行部分更新(仅更新一组字段作为超集的一部分),并且鉴于您的目标是避免耗时的数据库读取,只需保存 table 条目作为 HASH K/V 对(使用 HSET)并使用 HGETALL 进行读取。
Redis OPS 本质上是原子的,所以如果我没看错的话,这应该可以解决您的问题。
附带说明一下,如果您正在缓存整个实体但进行部分更新,您应该考虑一种更简单的缓存方法,例如通读(使缓存有效性成为 reader 唯一关注的问题) .
相对于数据库访问。除非以某种方式序列化,否则来自不同位置的 Redis 缓存访问在分布式系统中总是有可能出现乱序,因为总是有执行环境(网络、线程)引入可能的延迟。
执行直读缓存将确保数据在最近一次写入后始终得到更新,而无需同步任何其他内容。
Facebook 是这样解决 Memcached 问题的:http://nil.csail.mit.edu/6.824/2020/papers/memcache-faq.txt。
这个想法是使用租约的概念:当收到对缓存值的请求并且没有此类密钥的数据时,将返回一个租约令牌(64 位 id)。
当网络服务器从数据库中获取数据时,它可以使用该令牌将数据存储在缓存中。每次对密钥调用无效请求时,都会创建一个新的租用令牌,因此,如果尝试对旧令牌进行放置,则放置最终会被拒绝。
据我所知,如果不借助 LUA 脚本,真的不可能(轻松地)用 Redis 复制这种行为。
假设我想使用 Redis 或 Memcached 等内置解决方案来缓存数据库行(作为示例),以避免反复访问数据库,代价高昂。
为了论证,假设我有一个 TABLE(id, x, y) 并且我想缓存所有行,这样我就不必直接从数据库中读取。
问题:
- 考虑以下情况:NodeA 尝试更新给定行的字段
x
,而 NodeB 尝试更新y
,然后两者同时尝试更新缓存行。如果他们尝试“手动”更新他们刚刚更改的字段到缓存中的行,如果我们遵循典型的最后写入获胜,其中一个字段将被丢弃,这是灾难性的。这让我觉得我需要始终用从数据库中读取的完整行来填充缓存的行。 - 但这本身不一定对我有帮助。如果 NodeA 写入
x
并将整行加载到内存中,然后 NodeB 写入y
并读取内存中的整行,如果 NodeB 在 NodeA 之前写入缓存,则 NodeB 的更改将被覆盖!这让我相信我需要始终以某种方式对数据库和缓存中的行进行版本控制。是这样吗? Memcached 似乎有比较和设置原语,但我在 Redis 中没有看到这样的东西。 - 即使1.和2.不是问题,我仍然需要保证我的写/读具有写后读的一致性,否则可能会发生正在阅读和打算放入的内容缓存不一定是最新版本。如果是这样,我怎样才能确定这一点?通过要求 w + r > n?
这似乎是一个非常常见的用例,我想这几乎是一个已解决的问题。我可以尝试解决这个问题吗?
redis 的键值存储支持高级数据结构,例如 HASH。
如果您正在对缓存的实体进行部分更新(仅更新一组字段作为超集的一部分),并且鉴于您的目标是避免耗时的数据库读取,只需保存 table 条目作为 HASH K/V 对(使用 HSET)并使用 HGETALL 进行读取。
Redis OPS 本质上是原子的,所以如果我没看错的话,这应该可以解决您的问题。
附带说明一下,如果您正在缓存整个实体但进行部分更新,您应该考虑一种更简单的缓存方法,例如通读(使缓存有效性成为 reader 唯一关注的问题) .
相对于数据库访问。除非以某种方式序列化,否则来自不同位置的 Redis 缓存访问在分布式系统中总是有可能出现乱序,因为总是有执行环境(网络、线程)引入可能的延迟。
执行直读缓存将确保数据在最近一次写入后始终得到更新,而无需同步任何其他内容。
Facebook 是这样解决 Memcached 问题的:http://nil.csail.mit.edu/6.824/2020/papers/memcache-faq.txt。
这个想法是使用租约的概念:当收到对缓存值的请求并且没有此类密钥的数据时,将返回一个租约令牌(64 位 id)。
当网络服务器从数据库中获取数据时,它可以使用该令牌将数据存储在缓存中。每次对密钥调用无效请求时,都会创建一个新的租用令牌,因此,如果尝试对旧令牌进行放置,则放置最终会被拒绝。
据我所知,如果不借助 LUA 脚本,真的不可能(轻松地)用 Redis 复制这种行为。