惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
C
CERT Recently Published Vulnerability Notes
P
Proofpoint News Feed
Vercel News
Vercel News
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
Help Net Security
Help Net Security
月光博客
月光博客
N
News and Events Feed by Topic
Cloudbric
Cloudbric
博客园 - 司徒正美
L
LangChain Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tenable Blog
The Register - Security
The Register - Security
The Hacker News
The Hacker News
I
InfoQ
The Last Watchdog
The Last Watchdog
MyScale Blog
MyScale Blog
Schneier on Security
Schneier on Security
WordPress大学
WordPress大学
小众软件
小众软件
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
宝玉的分享
宝玉的分享
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
K
Kaspersky official blog
L
LINUX DO - 热门话题
N
News | PayPal Newsroom
F
Fortinet All Blogs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security @ Cisco Blogs
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
H
Help Net Security
Google Online Security Blog
Google Online Security Blog
S
Schneier on Security
C
Cisco Blogs
N
News and Events Feed by Topic
V2EX - 技术
V2EX - 技术
Latest news
Latest news
PCI Perspectives
PCI Perspectives
T
The Blog of Author Tim Ferriss
P
Palo Alto Networks Blog
T
Tor Project blog
Project Zero
Project Zero
云风的 BLOG
云风的 BLOG
Webroot Blog
Webroot Blog
Attack and Defense Labs
Attack and Defense Labs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org

Posts on WKLKEN THINKING

apisix 中的 lrucache apisix 中的服务发现机制 apisix 中的负载均衡 apisix etcd机制 聊聊框架 关于 k8s 的 zero downtime deployment 一些建议 apisix 遇到的一些问题 关于在除夕前一天换了一个洗衣机的故事 Django DRF 性能优化 DRF 的一些实践 Part1: Serializer DRF继承关系图 Better Code: 关于接口的灵活性 新的仓库: wklken/naming Better Code: 抽象: 可扩展性与可维护性的抉择 Better Code: 异常时, 该提示用户哪些信息? Better Code: 更好的异常日志打印 Go: some libs Go: go-redis/cache升级的坑 Go: logrus性能提升 Go: gin validation 远程办公的一点总结 Go: 开发过程中的一些bug 项目管理实践: 风险驱动开发 Go: 一种error wrap调用链处理方式 漫谈技术选型 Go: 基于 apitest 做handler层单元测试 Go: go-sql-driver interpolateparams参数优化 [分享]深度工作 你需要更多的思考时间 Django项目重构小结 工作七年小结: 学习,生活及其他 [分享]bash日常: bash-utils 极客时间推广海报 2017总结: 予时光以意义 k8s APIServer源码: api注册详细细节 k8s APIServer源码: api注册主体流程 k8s APIServer源码: 服务启动 k8s APIServer源码: go-restful框架 重构 - 读书笔记(Python示例) 写给新人的沟通建议 vim 杂谈 - 关于快速编辑 vim 杂谈 - 关于移动 读书笔记-重构: 章11 处理概括关系 读书笔记-重构: 章10 简化函数调用 读书笔记-重构: 章9 简化表达式 读书笔记-重构: 章8 重新组织数据 读书笔记-重构: 章7 在对象之间搬移特性 读书笔记-重构: 章6 重新组织函数 Python 代码规范小结 [分享]关于vim ElasticSearch集群部署文档 Logstash+ElasticSearch处理mysql慢查询日志 [分享]关于代码调试DE那些事 Logstash+ElasticSearch+Kibana- 实现相对通用的数据收集分析 ELK维护的一些点(二) [分享]Python源码剖析-数据结构 一些Centos Python生产环境的部署命令 摘录<<6个月学会任何一种外语>> ELK 维护的一些点 也许是一个新的开始 一些vim的个性化配置 读书笔记-调试九法 这段时间的一些想法 Python 源码阅读 - 垃圾回收机制 我为什么要写博客 APUE笔记-第一章 UNIX基础知识 Python源码阅读-闭包的实现 Python源码阅读-内存管理机制(二) Python源码阅读-内存管理机制(一) Python-基础-数据结构小结 '活动'设计的一些trick 一些简单的Python测试题 我的tmux配置及说明【k-tmux】 Review and Restart 工作四周年小结 vim插件: surround & repeat[成对符号编辑] vim插件: gundo[时光机] vim插件: expand-region[区域选中] vim插件: quickrun[快速执行] vim插件: trailing-whitespace[行尾空格处理] vim插件: closetag[成对标签补全] vim插件: ctrlp[文件搜索] vim插件: airline[状态栏增强] vim插件: theme[主题] vim插件: tagbar[大纲式导航] vim插件: nerdcommenter[快速注释] vim插件: rainbow_parentheses[括号高亮] vim插件: syntastic[语法检查] vim插件: delimitmate[符号自动补全] vim插件: matchit[成对标签跳转] vim插件: easy-align[快速对齐] vim插件: multiple-cursors[多光标操作] vim插件: vim-signature[快速标记跳转] vim插件: easymotion[快速跳转] vim插件: vundle[管理插件] Elasticsearch几个问题的解决 分享一份 Vim 简介PPT k-vim 更新9.0版本 关于知识管理工具的思考 Logstash+ElasticSearch+Kibana处理nginx访问日志
缓存使用的一些经验
2022-06-26 · via Posts on WKLKEN THINKING

在一个大的项目中, 使用了全缓存模型, 即, 所有数据都会经过cache.

简单分层: 应用->内存缓存->redis缓存->数据库

是一个典型的多读写少的场景, 并且数据量, 请求量非常大.

总结了一些使用经验, 供参考

1. 更新缓存的Design Pattern: 使用Cache aside

简洁优雅

关于缓存更新, 可以阅读这篇文章: CoolShell: 缓存更新的套路

为什么选择Cache Aside Pattern, 因为这个模式足够简单, 出现不一致的概率非常低, 对于大多数项目来说够用了.

而其他几种模式, 复杂度会高很多.

2. 并发很高时, 需要防缓存击穿

当并发很高的时候, 一个热点key失效, 会触发回数据库重查的逻辑, 此时会有大量请求落到数据库

需要做防缓存击穿的处理.

一般各种语言的库, 都有考虑到这一点, 例如 go-redis/cache

如果是golang并且自定义了cache, 可以使用 singleflight, 其他语言也可以找类似机制的库.

这个库很轻量

# define
type Cache struct {
	name              string
	keyPrefix         string
	codec             *cache.Cache
	cli               *redis.Client
	defaultExpiration time.Duration
	G                 singleflight.Group
}

# usage
// if missing, call retrieveFunc
data, err, _ := c.G.Do(key.Key(), func() (interface{}, error) {
		return retrieveFunc(key)
	})

3. 缓存空值, 需要防缓存穿透

如果一个key不存在, 在缓存中查不到, 在数据库中也查不到, 那么这个key的请求每次都会穿透到数据库

此时, 可以引入 bloomfilter 或者 cuckoofilter;

但是, 更简单的做法是, 缓存空值; 当成一个普通的key处理(缓存失效/数据一致性处理等)

4. 总是设置过期时间, 并且带随机数避免缓存雪崩

大部分场景下, 给每一个缓存key设置 TTL是一个很好的习惯. 可以避免无用数据占用资源, 及时淘汰掉使用较少的数据.

但是, 设置TTL的时候, 建议加上一个范围内容的随机数, 避免缓存在同一时间失效, 造成缓存雪崩.

TTL = 900s + randint(0,10)

5. key 中使用namespace+version前缀

key = {namespace}:{version}:{type}:{uniqueKey}

在实际应用部署中, 由于可能跟其他应用共用一套缓存, 所以建议缓存的key加入前缀, 防止冲突(如果冲突, 非常难以debug)

另外, 需要加入一个version, 在版本发布必要时变更, 以弃用缓存中已有的数据

  1. 由于不断迭代开发, 同一个key对应的value可能会变更, 例如value对应的数据结构新增了一个字段, 那么此时缓存中存量的缓存数据是没有这个字段的, 可能会造成一些bug.
  2. 还有另外一个需要特别小心的是, 升级缓存第三方库的时候, 某些版本可能是breaking change, 例如改变了压缩算法, 此时存量数据将无法正确被获取.一个例子: Can’t upgrade from v7 to v8 directly?

6. 缓存结构体, 使用msgpack替代json

MessagePack: It’s like JSON.but fast and small

优点:

缺点:

  • 在redis等服务端debug获取时不是明文, 不是很利于调试

所以, 缓存数据量比较大, 并且对性能有要求的, 可以使用msgpack

7. value比较大, 可以考虑启用压缩

如果value比较大, 那么在放入缓存前, 可以进行一次压缩, 获取后再解压

当然, 这个会产生额外的资源消耗(CPU), 以及会多一些耗时.

但是, 这个有利于减少网络传输中的包大小. 如果读取是非常高频的话, 那么代价还是值得的.

可以参考 go-redis/cache, 当值超过一定大小时使用 s2 compression 进行压缩

8. 批量操作, 使用pipeline

以redis为例, 批量操作

  1. 代码for循环, 一个个获取
  2. 可以考虑使用mget/mhget之类的多个key
  3. 使用pipeline

可以根据key-value特征, 批量key的数量等, 简单压测下性能, 决定使用哪种方式. 正常情况下, key数量较大的时候, pipeline性能最好.

甚至, 代码实现可以根据key的数量, 自行决定使用mget还是pipeline

9. 内存缓存 vs Redis

大部分情况, 项目中会混用两种缓存.

如果对数据一致性要求比较高, 可以全部使用 Redis.

但是, 其实每一次 Redis 操作代价大于内存操作

某些数据, 例如模型, 主键之类的, 一旦确定, 是不会变更的.

此时, 可以考虑使用内存缓存替代.

如果是golang, 推荐使用 go-cache. 没有其他实现那么强大, 但是胜在不需要序列化/反序列化.

10. 多级缓存 and client-side-cache

如果使用的 Redis6, 并且程序的driver支持, 那么可以直接利用 client-side-caching 特性获取最大的性能. 这个对程序透明, 无需在额外的逻辑处理.

但是, 当前(2022)有很多时候, 部署基建还是老版本Redis, 很多语言的driver也还没有支持, 可能复用不了

那么, 此时如果使用了内存->redis两级缓存, 如何确保数据一致性.

可以做的额外操作:

  1. 实现类似redis6 client-side-cache机制, 通过发布订阅等方式实现
  2. 可以使用一个sorted-set存储5分钟内变更的key, 内存缓存TTL设置5分钟; 每次先获取变更key列表, 本地缓存进行时间戳对比(这个方案对于批量key操作性能提升很大, 相当于把 N 次redis操作, 变成 本地缓存+ 1 次 changedkeylist获取+M次redis操作)

11. 配置建议: 同时支持standalone和sentinel配置

让运维根据实际应用场景, 自行切换使用.

成本不高的话, 也可以支持下redis-cluster配置

注意, 开启pool以获取更好的性能

另外, 也需要关注下如何开启prometheus/otel相关的配置, 以便某些情况下, 监测相关的指标

12. 设置开关, 支持withCache/withoutCache调试

引入缓存后, 在进行问题调试的时候非常不变.

建议加入相关的调试标志, 例如?force=true

  • 加上, 全链路数据获取走数据库
  • 没加, 全链路走缓存

此时, 可以通过对比两次请求的差异, 确定是否是缓存问题

甚至, 可以加入?debug=true以获取各个环节的上下文信息, 快速调试