NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

缓存,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:43550

评论

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

Ansible最佳实践之 AWX 使用 Ansible 与 API 通信tags

山河已无恙

12月月更

多引擎可视化数据流实现方案

元年技术洞察

数据中台 数字化转型 专利解析 方舟企业数字化 PaaS 平台 #方舟平台

Ansible最佳实践之 AWX 作业创建和启动

山河已无恙

12月月更

并发编程详解:十三个工具类,十大设计模式,从理论基础到案例实战

钟奕礼

Java 程序员 java面试 java编程

Ansible最佳实践之 AWX 构建高级作业工作流的创建和调度

山河已无恙

12月月更

关于 Git 重写历史的一些笔记

山河已无恙

12月月更

如何通过C#/VB.NET将PDF转为Word

在下毛毛雨

C# PDF word 转换

MySQL从入门到实战讲解,京东T5大牛学习笔记分享,看完我哭了!

钟奕礼

Java 程序员 java面试 java编程

Java jar 如何防止被反编译?代码写的太烂,害怕被人发现

小小怪下士

Java 程序员 反编译

预告|2022 星策 Summit MLOps 分论坛议程公布!

星策开源社区

人工智能 机器学习 开源 AI MLOps

Ansible最佳实践之 AWX 启用facts缓存和模板问卷调查

山河已无恙

12月月更

Serverless Devs 重大更新,基于 Serverless 架构的 CI/CD 框架:Serverless-cd

Serverless Devs

Serverless Serverless Devs

Ansible之 AWX 管理清单和凭据的一些笔记

山河已无恙

12月月更

Ansible最佳实践之 AWX 创建管理项目的一些笔记

山河已无恙

12月月更

GitHub狂飙30K+star面试现场,专为程序员面试打造,现已开源可下载

钟奕礼

Java 程序员 java面试 java编程

腾讯云数据库SaaS服务2022再升级 全面助力企业提升数据库运维能力

科技热闻

阿里P8面试官总结的《2023最新java面试题》,搞定90%以上的技术面

钟奕礼

Java 程序员 java面试 java编程

低代码领域:发展路径、市场规模及未来趋势洞察!

优秀

低代码

Redis之String类型和Hash类型的介绍和案例应用

C++后台开发

redis 数据结构 hash 后端开发 C++开发

基于分发与计算的GRTN全球实时传输网络

阿里云视频云

互联网 科技 GRTN 全球实时网络传播 分发与计算

非结构化数据暴增,企业如何稳定高效的支撑日均百亿级访问

秃头也爱科技

GaussDB(for Cassandra)新特性发布:支持Lucene二级索引,让复杂查询更智能

秃头也爱科技

SAP MM 为UB类型的STO执行VL10B,报错-没有项目类别表存在(表T184L NL 0002 V)-之对策

SAP虾客

SAP MM UB类型STO VL10B T184L

广西移动圆满完成区运会通信保障任务

Geek_2d6073

【python小脚本】监听日志文件异常数据发送告警短信

山河已无恙

12月月更

教你用JavaScript实现乘法游戏

小院里的霍大侠

JavaScript 前端开发 编程实战 实战案例 初学者

PGL图学习项目合集&数据集分享&技术归纳业务落地技巧[系列十]

汀丶人工智能

神经网络 图神经网络 12月日更 11月月更 12月月更

拿到8000元的火焰杯比赛奖金,感谢霍格沃兹测试开发学社

测吧(北京)科技有限公司

软件测试比赛

降价背后,函数计算规格自主选配功能揭秘

Serverless Devs

Serverless 前端 函数计算FC

Ansible最佳实践之AWK VS Anssible Tower 界面介绍

山河已无恙

12月月更

裸辞不慌!入职蚂蚁金服P6,掌握并发编程我是这样吊打面试官的

钟奕礼

Java java面试 java编程 程序员‘

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