缓存问题
缓存穿透
查询不存在的key,会穿透缓存查询数据库,高并发的情况下,给数据库带来压力而宕机
解决方案
- 对查询结果为空的情况也进行缓存,混存ttl设置短一点,或者key 对应的数据insert 之后清理缓存,问题是:缓存太多空值占用了更多的空间
- 使用布隆过滤器,在缓存之前在加一层布隆过滤器,在查询的时候先去布隆过滤器查询key是否存在如果不存在就直接返回,存在再查缓存和DB
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统比如DB 带来很大压力 突然间大量的key失效或者redis 重启,大量访问数据库,数据库崩溃
解决方案
- key 的失效期分散开,不同的key设置不同的有效期
- 设置二级缓存(数据不一定一致)
- 高可用(脏读)
缓存击穿
对于一些设置过期时间的key,如果这些key 可能会在某些时间点被超高并发地访问,是一种非常热点的数据。这个时候 需要考虑一个问题:缓存被击穿的问题这个和缓存雪崩的区别在于针对某一key缓存,前者则是很多的key 缓存在某个时间点过期的时候,恰好在这个时间点对这个 key有大量的并发请求过来,这些请求发现缓存过期一般会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:
-
用分布式锁控制访问的线程 用redis的setnx 互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。
-
不设置超时时间,volatile-lru 但会造成写一致问题 当数据库数据发生更新时,缓存中的数据不会及时更新,这样会造成数据库的数据与缓存中的数据不一致,应用会从缓存中读取到脏数据,可采用延时双删除策略处理
数据不一致
数据源不一样,如果强一致很难,追求是最终一致性,如果要追求高吞吐量,低延迟,可以采用延时双删除 先更新数据库同事删除缓存项(key) 等读的时候再填充缓存 2s后再删除一次缓存项(key) 设置缓存过期时间expired time 比如10s 或者1小时 将缓存删除失败的记录到日志中,利用脚本提取失败记录再次删除(删除失效期过长 7*24)
升级方案
通过数据库的binlong来异步淘汰key,利用工具(canal)将binlog 日志采集发送到mq 中,然后通过ack 机制确认处理删除缓存。
数据并发竞争
这里的并发指的是多个客户端同时set 同一个key 引起的并发问题 多客户端同时并发写一个 key,一个key的值为1 ,本来按顺序修改为2,3,4,最后是4,但是顺序变成4,3,2 最后变成2
第一种方案:分布式锁+时间戳
-
整体技术方案 主要是准备一个分布式锁,多个客户端去抢锁,抢到锁就做set操作。 加锁的目的实际上就是把并行读写改为串行读写的方式,从而避免资源竞争
-
redis 分布式锁的视线 主要用到的 redis函数是setnx(),用setnx 实现分布式锁,时间戳的作用是, 加入系统B 先抢到锁,将key1设置为value1 7.05 ,接下来系统A抢到锁,发现自己key1 的时间戳早于缓存中的时间戳,那就不做set操作。
第二种方案,利用消息队列
在并发量过大的情况下,可以通过消息中间件进行处理,并把并行读写进行串行化,把redis 的set操作放在队列中使其串行化,必须一个个执行。
Hot Key
当有大量的请求(几十万)访问某个redis的某个可以使时,由于流量集中达到网络上线,从而导致这个redis 的服务器宕机,造成 缓存击穿,接下来对这个key 的访问将直接访问数据库造成数据库崩溃,或者访问数据库回填redis再访问redis,继续崩溃
如何发现热key
- 预估热key,比如秒杀的商品,火爆的新闻等
- 在客户端进行统计,实现简单
- 如果是proxy,比如,codis,可以在proxy端收集
- 利用redis 自带命令,比如monitor,hotkeys,但是执行缓慢(不要用)
- 利用基于大数据领域的流式计算技术进行实时数据访问次数的统计,如storm,spark streaming,flink 这些都是可以的,发现热点数据后可以写入zookeeper 中
如何处理热key
- 变分布式缓存变成本地缓存(数据不要求时时一致) 发现热key后,把缓存数据取出后,直接加载到本地缓存中,可以采用ehcache,guava cache 都可以,这样系统在访问热key数据时就可以直接访问自己的缓存,
- 在每个 redis主节点上备份热key数据,这样在读取时可以采用随机读取的方式,将访问压力负载到每个redis上。
- 利用对热点数据访问的限流熔断保护措施(首页不行,系统友好性差) 每个系统实例每秒最多请求缓存集群读操作不超过400次,一超过就可以熔断,不让请求缓存集群,直接返回一个空白信息,然后用户稍后自行再次刷新页面之类的 通过系统层自己直接加限流熔断保护措施,可以很好的保护后面的缓存集群。
Big Key
大key 指的是存储的值非常大,带来的影响:
- 大Key 会大量占用内存,在集群中无法均衡
- redis 性能下降,主从复制异常
- 在主动删除或国企删除时会操作时间过长而引起服务阻塞
如何发现大Key
- redis-cli --bigkeys 命令,可以找到某个实例5种数据类型中最大的可以,但是如果redis的key 比较多,执行该命令会比较慢
- 获取redis 的rdb文件,通过rdbtools 分析rdb生成csv,再倒入 mysql或者其他数据库中进行统计分析,根据size_in_bytes 统计bigkey
大 key 如何处理
优化big key 的原则就是 string 减少字符串长度,list,hash,set,zset 等减少成员数。
- string 类型的big Key 尽量不要存入 redis中可以使用文档型数据库mongoDB 或者缓存到CDN 上。 如果必须使用 redis存储,最好单独存储,不要和其他的key 一起存储,采用一主一从或多从。
- 单个简单的key 存储的value 很大,可以尝试将对象分拆成几个key-value ,使用mget 获取值,这样分拆的意义在于分拆单词操作的压力,将操作压力平摊到多次操作中,降低对redis的 IO影响。
- hash ,set,zset,list 中存储过多元素,可以将元素分拆
- 删除大 key不要使用del,因为del 是阻塞命令,删除会影响性能
- 使用lazy delete (unlink)命令删除,相比del ,该命令会在另个线程中回收内存,因为它是非阻塞的,真正的删除在后面异步操作。
分布式锁,(悲观锁,性能差 )
本地锁,对于一个线程操作 互斥锁,分布式锁,让进程互斥,串行化,哪个进程获得锁,那个进程允许操作,进程A完成后释放锁 生命周期内没有使用完,可以继续使用,锁的续租,进程A死了,则分布式到生命周期后自动失效。
watch 利用watch 实现redis 乐观锁
乐观锁基于cas(compare and swap) 思想(比较并替换)是不具有互斥性的,不会产生锁等待而消耗资源,但是需要反复的重试 但是也因为重试的机制,能比较快的响应,因此我们可以利用redis来实现乐观锁,
- 利用redis的watch 功能,监视这个redisKey的状态值
- 获取rediskey 的值
- 创建redis 事务
- 给这个key 的值+1
- 然后去执行这个事务,如果key的值被修改过则回滚,key不加1
setnx
实现原理
共享资源互斥,共享资源串行化,单应用中使用锁(单进程多线程) synchronized ReentrantLock 分布式锁是控制分布式系统之间同步访问共享资源的一种方式,利用 redis单线程特性对共享资源进行串行化处理
实现方式
- 获取锁,set命令,setnx 命令(并发会出现问题) 释放锁 del 命令
- redis+lua 脚本实现
存在问题
- 单机 无法保证高可用,主-从,无法保证数据的强一致性,主机宕机时会重复获取,造成锁的同步。 提出红锁(redLock)方案,client1 在三个里面set 成功两个才能获取锁 redis 在分布式下 AP 模型
- 无法续租问题,超时不能使用,可以用watchdog 解决
本质分析
cap 在分布式情况下不能满足三者共存,只能满足其中的两者,在分布式下p 不能舍弃,舍弃就是单机 cp 强一致,AP 高可用,分布式锁是cp 模型, redis集群是ap模型 redis集群不能保证随时一致性,只保证最终一致性。
为何用redis 实现分布式锁?
业务有关,不需要数据一致性,社交场景,可以使用,需要强一致性,不允许重复获取锁,比如金融,就不要用可以使用cp模型,zookeeper和etcd
redission 分布式锁的使用
redssion 基于 nio
加锁机制
首先根据hash 节点选择一台机器,发送lua脚本到redis 服务器上 lua作用保证这段复杂业务逻辑执行的原子性,keys[1]加锁的key,arg[1] key 生存时间默认30s,arg[2]加锁的客户端id 第一段if 判断语句就是exists mylock 判断一下,如果加锁的锁key 不存在,就用hsetmylock ,设置一个hash数据结构 然后执行pexpire mylock 30000 , ### 锁的互斥机制 如果有新的客户端加锁尝试, 判断exists 锁是否存在,发现已经存在,然后第二个if 判断,判断mylock 锁key 的hash数据结构中是否包含新客户端的id,然后获取到pttl,mylock 返回的一个数字,表示剩余生存时间,此时新客户端进入while 循环不停地尝试加锁 ### 自动延时机制 获取锁成功,启动一个watch dog ,后台线程,每10s 执行一次
### 释放锁 释放锁 发布解锁消息,删除锁,多层锁,一层一层删除
### 特性 1. 互斥性 2. 统一性 3. 可重入性 4. 容错性 ### 引用 数据并发竞争,防止超卖