缓存使用进阶大全

为什么要用缓存?

适用互联网高并发,高性能场景,解决mysql磁盘慢问题

在项目中缓存是如何使用的?

  • 走springboot @CacheConfig
    查询:先查缓存,有返回,没有查数据库,set到缓存返回。
    修改:先删db,后删redis
  • 单独业务使用
    db,缓存一致性问题,缓存竞争后面讨论解决方案

缓存使用有哪些分类,达成的效果是?

  • 分页缓存(查询条件当key,时间控制业务可以接受方位)
  • 数据库行数据缓存(只缓存有用不易改动的列)
  • 业务缓存(通过特定业务特殊设置,特殊淘汰)

整个系统大部分查询全走缓存。冷热数据动态设置,淘汰,最大化利用缓存。

缓存使用不当会造成什么后果?

缓存雪崩

是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机

解决方案:

  • 冷热数据区分,采用不同的实效时间,缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 如果缓存数据库是分布式部署,将热点数据均匀分布。
  • 设置热点数据永远不过期。

缓存穿透

是指缓存和数据库中都没有的数据,而用户不断发起请求,缓存没有起到压力缓冲的作用

解决方案:

  • 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以- 防止攻击用户反复用同一个id暴力攻击
  • 针对key做布隆过滤器
  • 采用灰度发布的方式,先接入少量请求,再逐步增加系统的请求数量,直到全部请求都切换完成
  • 提前缓存预热或定时预热

缓存击穿

是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据(sql又慢),引起数据库压力瞬间增大,造成过大压力。缓存失效时瞬时的并发打到数据库

解决方案:

  • 设置热点数据永远不过期
  • 针对key做加互斥锁 解决-第一次缓存大并发问题 单jvm级别加双重锁double-check, (使用分布式锁会限制并发能力,所以使用单jvm级别限制,特殊场景支付除外)

全量缓存

在处理超大规模并发的场景时,由于并发请求的数量非常大,即使少量的缓存穿透,也有可能打死数据库引发雪崩效应。

解决方案:

  • 对于这种情况,我们可以缓存全量数据来彻底避免缓存穿透问题
    canal订阅binlog异步更新缓存

缓存并发竞争

多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了

  • 如果对这个key操作,不要求顺序
    这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
  • 如果对这个key操作,要求顺序
    假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
    期望按照key1的value值按照 valueA-->valueB-->valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下
系统A key 1 {valueA  3:00}
系统B key 1 {valueB  3:05}
系统C key 1 {valueC  3:10}

那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。

  • 利用队列,将set方法变成串行访问也可以

缓存与数据库双写不一致,数据一致性问题

Cache Aside Pattern 旁路缓存

  • 读请求:先读缓存,如果没有命中,读数据库,再set回缓存
  • 写请求:先删缓存,再删数据库,

为什么建议淘汰缓存,不修改缓存

在1和2两个并发写发生时,由于无法保证时序,此时不管先操作缓存还是先操作数据库,都可能出现:

  • 请求1先操作数据库,请求2后操作数据库
  • 请求2先set了缓存,请求1后set了缓存
    导致,数据库与缓存之间的数据不一致。

Cache Aside Pattern问题

Cache Aside 在高并发场景下也会出现数据不一致。
读操作A,没有命中缓存,就会到数据库中取数据v1。
此时来了一个写操作B,将v2写入数据库,让缓存失效;
读操作A在把v1放入缓存,这样就会造成脏数据。因为缓存中是v1,数据库中是v2

解决方案:

  • b线程:读缓存->未命中->上写锁>从db读数据到缓存->释放锁;a线程:上写锁->写db->删除缓存/改缓存->释放锁;
  • 看业务方能接受多长时间的脏数据,然后缓存就设置多久的过期时间。
  • 或者数据库更新成功后,用MQ去通知刷新缓存


    image
  • canal订阅binlog,终极方案-还可以解决主从库同步问题


    image
  • 降级或补偿方案或兜底方案

redis基础

5.0.7 内存,单线程,c语言,io多路复用

持久方式:

rdb,aof,混合模式
一分钟内修改1W次
5分钟内修改10次
15分钟内修改1次

淘汰机制:

volatile-lru:从设置了过期时间的数据集中,选择最近最久未使用的数据释放;
allkeys-lru:从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最久未使用的数据释放;
volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放;
allkeys-random:从数据集中(包括了设置过期时间以及未设置过期时间)随机选择一个数据进行入释放;
volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;
noeviction:不删除任意数据(但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。

allkeys-lru,针对所有 Key,优先删除最近最少使用的 Key;
volatile-lru,针对带有过期时间的 Key,优先删除最近最少使用的 Key;
volatile-ttl,针对带有过期时间的 Key,优先删除即将过期的 Key(根据 TTL 的值);
allkeys-lfu(Redis 4.0 以上),针对所有 Key,优先删除最少使用的 Key;
volatile-lfu(Redis 4.0 以上),针对带有过期时间的 Key,优先删除最少使用的 Key。
范围有 allkeys 和 volatile两种,算法有 LRU、TTL 和 LFU 三种,建议volatile-lfu

redis有哪几种数据类型,在项目中分别在哪里使用了?

string
计数器,点赞数、收藏数、分享数,token

hash
圈子id uid:lastUpdateTime(最后发布时间)   --点赞,发帖,回复频率控制
userId:name:张三;age:13   -- 用户信息存储,修改

list
用户粉丝列表, 用户点赞列表, 用户收藏列表, 用户关注列表
lrange命令,并发大的最新200评论分页常驻内存
消息队列

set
唯一无序 共同关注的书 | 互相关注 | 粉丝

是否互关去重就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能

zset
唯一有序score,群积分排名晋级管理员 | 书排行榜 | top-n原理跳表
stream
代替之前基于list的发布订阅

redis集群方案

一主一备,一主多从,哨兵模式,
类codis,redis cluster,旁路探活
并发海量建议旁路探活式的集群方式

分布式锁

单jvm级别

与synchronized关键字相比,Lock的使用更灵活,可以有加锁超时时间、公平性等优势

单机redis分布锁

set key value EX 60 NX  超时时间根据业务设计
setnx SET if Not eXists  1:获取锁 0:继续尝试获取(重试次数+休息时间)
ex过期时间防止锁一直被占用

三个请求A,B,C同时竞争锁,被请求A抢先获得,其他请求只能不断尝试获取锁(tryLock)
请求A由于业务比较复杂处理时间已经超时,所以请求B能够获取到锁
请求A终于完成了自己的业务,这个时候执行了DEL user_id,但是他自己的锁已经失效了,删除的是请求B锁。而请求B的业务此时并未处理完,所以此处就出现了问题!
通过value来判断这把锁是否属于自己
SET user_id 10086 EX 30 NX
// 处理业务中

//业务处理完毕
if( (GET user_id) == "XXX" ){
  DEL user_id
}

if( (GET user_id) == "XXX" ){ //获取到自己锁后,进行取值判断且判断为真。此时,这把锁恰好失效。而另外一个请求恰好获得key值为user_id的锁。
此时程序执行了了DEL user_id,删除了别人加的锁
  DEL user_id
}
保证查询和删除的原子性操作,需要引入lua脚本支持
eval()方法可以确保原子性?源于Redis的特性,因为Redis是单线程,在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行

加锁时 key 同,value 不同。
释放锁时,根据value判断,是不是我的锁,不能释放别人的锁。
及时释放锁,而不是利用自动超时
锁超时时间一定要结合业务情况权衡,过长,过短都不行。
程序异常之处,要捕获,并释放锁。如果需要回滚的,主动做回滚、补偿。保证整体的健壮性,一致性
key超时解决:弄个守护线程进行监听key的失效时间,然后在快要失效的时候为期续命

集群redis分布式锁

命令先是落到了主库。假设这时主库down了,而这条数据还没来得及同步到从库,
sentinel将从库中的一台选举为主库了。这时,我们的新主库中并没有mykey这条数据,若此时另外一个client执行 setnx mykey hisvalue , 也会成功,即也能得到锁。这就意味着,此时有两个client获得了锁
为了解决故障转移情况下的缺陷,Antirez 发明了 Redlock 算法,使用redlock算法,需要多个redis实例,加锁的时候,它会想多半节点发送 setex mykey myvalue 命令,只要过半节点成功了,那么就算加锁成功了。释放锁的时候需要想所有节点发送del命令。这是一种基于【大多数都同意】的一种机制

多级缓存实现

nginx+lua,jvm,ZooKeeper(监听修改变动),redis集群

总结:

  • 锁的颗粒度,范围尽量要小。推荐无锁编程(解决热点冲突),热点账户分而治之
  • 缓存数据允许丢失
  • 遵守缓存使用开发规范

参考:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容