写点什么

缓存,Promies 和锁

  • 2020-03-01
  • 本文字数:2457 字

    阅读完需:约 8 分钟

缓存,Promies和锁

Instagram 最近在他们的软件工程博客发布了一篇有关缓存值 promise 的概念的帖子,在缓存值未被命中情况下,需要耗费一定时间从底层的数据库管理系统中获取到未命中的缓存值,但这可能会导致数据管理系统拥塞。此外,如果多个并发的请求在请求获取缓存中一个不存在的值,触发多个工作实例从底层数据库获取该值并填充到缓存中,会引起数据库的拥塞(参考下图)。



在帖子中,Instagram 的 NickCooper 展示一种信号通知机制,当缓存正在准备某个值时,可以向其他的对该值请求发送通知,所以其他请求暂停对该值获取,避免直接访问底层数据库。


这不是什么新方法(它是读写缓存的特性之一,因为其对数据库透明的,开箱即用),但这个方法值得好好讨论一下,在这篇文章中,我将分享展示该方法的一个简单应用,将读写缓存的更多优势给发挥出来。

Redis

在详细介绍我的方法应用之前,简单介绍下如何通过 redis 来实现的我的目标。


  1. 支持多种语言,应用客户端只需要一个 redis 客户端库和需要使用 Redis 的 key 的名称。可以在不同程序和网络边界之间,同其他的客户端生成/解析 promises。

  2. 集群模式下扩展更高效,不需要通过轮询或者其他低效的模式,Redis 在此更能体现其独特的优势。

  3. 在避免无效的工作和保持可伸缩性之间有效地权衡。作为分布式系统的一部分,更强有力的保证需要更多的协作。


我的应用方案主要依赖 redis 的三个特性:key 过期(TTL),原子操作(SETNX)和 Pub/Sub;一般来说,我只是很好的利用了我在前面一篇帖子(https://redislabs.com/blog/what-to-choose-for-your-synchronous-and-asynchronous-communication-needs-redis-streams-redis-pub-sub-kafka-etc-best-approaches-synchronous-asynchronous-communication/)中提到的原则:共享状态有益于协作,反之亦然。


Redis 很适合用于状态共享,使用 Pub/Sub 消息机制实现一个锁,帮我构建一个跨网络 promise 机制。

内存锁的介绍

接下来,我将介绍 redis 内存锁模式的实现,它的工作模式如下:


  1. 一个服务应用实例,需要从 redis 中获取 key 值 foo, 如果获取到,就直接返回。

  2. 如果 foo 这个 key 值不存在,可以通过 SET NX 创建一个 key 值 lock:foo,NX 参数确保如果存在通过并发设置请求,只有一个请求能够设置键值成功(key 的 value 值在此处不重要)。

  3. 3.如果这个 key 值在缓存存在,我们获取到其对应的 value 值,完成后,接下来将其保存到 redis 中,并在叫做 notif:foo 的 Pub/Sub 通道中发布一条消息,通知所有等待该值的客户端,可以从缓存中获取到该值。

  4. 4.如果不能获取到对应的锁,此时只需订阅到 notif:foo 的 Pub/Sub 通道,等待被通知对应的缓存值已经存在。

  5. 实际上,这个算法稍微有点复杂,因此我们能很好的处理并发和超时(无过期的锁/Promises 在分布式环境中是无用的)。我们的命名的稍微有点复杂,因为需要为每个资源指定一个一个名称空间,以便多个独立的服务使用同一个集群时,不会面临键名冲突的风险,除此之外,解决这类问题不存在复杂性。

代码

你可以从 github 上获取相关示例代码(https://github.com/kristoff-it/redis-memolock),示例代码是基于 Go 语言实现的。


package main
import ( "fmt" "time" "github.com/go-redis/redis" "github.com/kristoff-it/redis-memolock/go/memolock")
func main () { // First, we need a redis connection pool: r := redis.NewClient(&redis.Options{ Addr: "localhost:6379", // use default Addr Password: "", // no password set DB: 0, // use default DB })
// A memolock instance handles multiple resources of the same type, // all united by the same tag name, which will be then used as a key // prefix in Redis. queryResourceTag := "likes" queryMemoLock, _ := memolock.NewRedisMemoLock(r, queryResourceTag, 5 * time.Second) // This instance has a 5 second default lock timeout: // Later in the code you can use the memolock to cache the result of a function and // make sure that multiple requests don't cause a stampede.
// Here I'm requesting a queryset (saved in Redis as a String) and providing // a function that can be used if the value needs to be generated: resourceID := "kristoff" requestTimeout := 10 * time.Second cachedQueryset, _ := queryMemoLock.GetResource(resourceID, requestTimeout, func () (string, time.Duration, error) { fmt.Println("Cache miss!\n") // Sleeping to simulate work. <- time.After(2 * time.Second)
result := fmt.Sprintf(`{"user":"%s", "likes": ["redis"]}`, resourceID) // The function will return a value, a cache time-to-live, and an error. // If the error is not nil, it will be returned to you by GetResource() return result, 5 * time.Second, nil }, )
fmt.Println(cachedQueryset) fmt.Println("Launch the script multiple times, see what changes. Use redis-cli to see what happens in Redis.")}
复制代码


如果你在 5S 中内运行该程序的两个实例,无论第一个实例是否运行结束(即,参数值已被缓存),还是在继续运行,“Cachemiss!”只会显示一次。

为什么这比其他解决方案更好?

两大原因:


  1. MemoLock 不仅可以保护 DBMS,还可以保护其他的昂贵的资源。

  2. 即使我们只想限制查询集缓存,CQRS 也会告诉我们,数据存储在 DBMS 中对制定的查询不一定是最有用的数据格式。对查询数据进行必要的转换应该是业务逻辑的一部分,除非在必要的条件下,对通过存储过程对每个请求都要重新转换下数据。


本文转载自 中间件小哥 公众号。


原文链接:https://mp.weixin.qq.com/s/WhFVFTHKNEIt9Jec34rB0A


2020-03-01 21:43629

评论

发布
暂无评论
发现更多内容

极客时间架构 1 期:第 12 周 数据应用(一) - 命题作业

Null

架构师训练营 week8 学习总结

花果山

极客大学架构师训练营

【架构师训练营第 1 期 12 周】 学习总结

Bear

极客大学架构师训练营

第12周 C!数据有这么大

Pyr0man1ac

TronChain波场链系统APP开发|TronChain波场链软件开发

系统开发

第十二周 数据应用(一)

9527

训练营第十二周作业 1

仲夏

第八周作业

Griffenliu

第八周学习总结

Griffenliu

week8性能优化(二)作业和学习总结

杨斌

第十二周 数据应用(一)作业

蓝黑

极客大学架构师训练营

第十二周作业

wanlinwang

极客大学架构师训练营

hashmap 是如何炼成的

helbing

数据结构

大数据应用总结一

天天向上

极客大学架构师训练营

架构师训练营 week8 课后作业

花果山

极客大学架构师训练营

训练营第十二周作业 2

仲夏

周练习 12

何毅曦

第十二周

Geek_ce484f

极客大学架构师训练营

量化交易软件系统开发|量化交易APP开发

系统开发

第七周-作业1

Mr_No爱学习

架构师训练营第 12 周课后练习

薛凯

架构师训练营第 11 周课后练习

薛凯

架构师训练营第 8 周课后练习

菜青虫

极客大学架构师训练营

架构师训练营第 8 周学习总结

菜青虫

极客大学架构师训练营

极客大学 - 架构师训练营 第十二周作业

9527

第12周 作业

Pyr0man1ac

十二周作业

orchid9

极客时间架构 1 期:第 12 周 数据应用(一) - 学习总结

Null

第三周学习总结

J

极客大学架构师训练营

HiveQL分析

天天向上

极客大学架构师训练营

架构师训练营 week12作业

FG佳

架构师一期

缓存,Promies和锁_行业深度_翻译自redis.io_InfoQ精选文章