写点什么

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

评论

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

写了2年文章的我,昨天第一次露脸直播。

王中阳Go

深度思考 高效工作 学习方法 程序员 微服务架构

Serverless 奇点已来,下一个十年将驶向何方?

Serverless Devs

《PyTorch 深度学习实战》学习笔记--Mac M1 安装PyTorch2.0

IT蜗壳-Tango

模块五作业

张贺

ProgressBar(进度条)

梦笔生花

Android Studio 进度条 ProgressBar

Jenkins 构建过程中提示 GPG 错误

HoneyMoose

二维码的秘密

古时的风筝

二维码 二维码生成

带你认识数仓的增量备份核心设计

华为云开发者联盟

数据库 后端 华为云 数仓 企业号 1 月 PK 榜

《金融电子化》:隐私计算赋能银行助贷业务自主风控 | 华夏银行×洞见科技

洞见科技

隐私计算 风控

2023年,祝你有个好习惯!

石云升

习惯 年终总结 1月月更

关于 Serverless 应用架构对企业价值的一些思考

Serverless Devs

2023-01-09:以下go语言代码输出什么?A:+Inf; B:zero; C:something else; D:doesn‘t compile。 package main import (

福大大架构师每日一题

golang go语言 福大大 选择题

React源码分析6-hooks源码

flyzz177

React

一次JVM GC长暂停的排查过程

京东科技开发者

Java 后端 JVM 虚拟机 企业号 1 月 PK 榜

ShardingSphere分库分表schema名称导致NPE问题排查记录

小小怪下士

Java 程序员 后端

公共服务 智慧政务数据可视化大屏一体化系统

2D3D前端可视化开发

数据可视化 数字政务 智慧政务 数字政府 可视化大屏

ClickHouse 挺快,esProc SPL 更快

陈橘又青

云图说 | Workflow:流水线工具,助您高效完成AI开发

华为云开发者联盟

人工智能 华为云 AI开发 企业号 1 月 PK 榜

恭喜龙蜥获得中国开源云联盟2022年度中国“最佳开源实践案例”和“杰出开源贡献者”奖项

OpenAnolis小助手

开源 龙蜥社区 COSCL 木兰峰会 中国开源云联盟

2022年终总结:一年读完的40本书

石云升

读书笔记 年终总结 1月月更

React源码分析7-state计算流程和优先级

flyzz177

React

LogicFlow安装与准备工作

小鑫同学

前端 vite Vue 3 Vue3 Typescript

如何把 高并发限流 实现的那叫一个优雅!

风铃架构日知录

Java 程序员 高并发 IT 限流

一次关于 MySQL 主从模式采用 GTID 的实践记录

风铃架构日知录

Java MySQL IT 主从复制 MySQL 数据库

微服务的版本号要怎么设计?

江南一点雨

微服务 语义化

Git操作不规范,战友提刀来相见!

王中阳Go

golang git 深度思考 高效工作 学习方法

Jenkins 项目的 gpg: signing failed: Bad passphrase 错误

HoneyMoose

一个词语总结2022,你的是什么? | 2022 年度总结

陈言必行

2022年终总结

React源码分析5-commit

flyzz177

React

Postgresql分析慢sql

查拉图斯特拉说

数据库 postgresql db PgSQL PG库

重磅发布丨从云原生到 Serverless,先行一步看见更大的技术想象力

Serverless Devs

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