高并发基础之缓存
1 基于缓存的思考
1.1 引入缓存引发的问题
- 缓存和数据库信息不一致
解决方案:读的时候先读缓存,如果缓存没有就到数据库取数据做相应处理然后放入缓存,更新的时候先删除缓存后更新数据库。
理由:①删除缓存的原因是如果是更新缓存对于要缓存的数据可能需要经过多重处理,而如果该缓存后续没有被使用到将会是一种浪费资源。②先删除缓存后更新数据库的原因是如果反过来在程序半路崩溃的情况数据库中为新数据而缓存中还是旧数据会导致数据不一致,而先删除缓存可以保证基本的数据一致就算中途崩溃最后也是缓存中没有数据,数据库为旧数据的情况)
缺点:极端情况下,先删除缓存后更新数据库的时候可能会有读请求进来,然后读请求读取数据库中的旧数据并且更新缓存,最后导致数据库中为新数据,而缓存为旧数据
改进(如果要求强一致):对于同一个key的更新和读取同时进行的情况串行化,串行化的方式可以采用内部队列的方式,这个看具体情况变通
- 缓存雪崩
引发原因:①数据批量到期②缓存机器宕机了,从而流量全部落在数据库,重点的是批量
解决方案:①利用主从+哨兵机制建立集群让缓存变得高可用减少宕机引发的雪崩情况,并且做好持久化工作便于宕机快速恢复,②批量到期可能选择将到期时间随机生成并分散开来,并且分析热点数据设置永不过期,但是从整体角度讲可以使用hystrix进行限流和降级通过牺牲请求执行率,确保数据库正常提供服务
- 缓存穿透
引发原因:大量请求既不存在缓存中也不存在db中的key,重点是不存在的key
解决方案:①在搜寻db中没有的数据时默认返回一个空并且进行缓存处理②利用布隆过滤器,通过布隆过滤器可以在一定程度上过滤掉不存在的数据,但是也会有一些无法被过滤(有可能出现不同key但是hash出来的值相同),再和第一个方案结合一下就行
- 缓存击穿
引发原因:热点key缓存失效或者更新db删除缓存导致请求直接请求db,重点是热点
解决方案:缓存失效可以通过①设置该key永不过期②线程定时刷新过期时间解决,更新数据导致的缓存失效可以通过①对需要更新数据时的时访问db的操作加互斥锁解决(缺点是在更新很频繁的时候,需要不停的阻塞可能效果不大好)
- 缓存并发竞争key
引发原因:多客户端同时对一个redis服务进行set同一个数据时,由于顺序错误导致修改的最终结果错误
解决方案:利用分布式锁,①利用redis中的setnx获取锁保证同一时刻只有一个客户端执行,加上时间作为判断标准保证后面改变的值不会被前面的改变操作覆盖,②利用zookeeper获取临时节点,按照获取节点的顺序从小到大顺序执行,当执行完成删除节点
1.2 通过确定QPS判断是否需要引入缓存
QPS计算:QPS(TPS)= 并发数/平均响应时间 或者 并发数 = QPS*平均响应时间
简单的例子:假设一个系统峰值为10分钟,用户量有1000
qps为1000/(10*60)
平均响应时间1(s)
并发数为1.66
大致流程就是计算用户量->确定峰值->确定响应时间->确定并发数,当然实际情况可能复杂一点对于峰值的计算可能也没有规律可循,在有些情况下要提到qps但是并发数已经到了瓶颈就需要通过减少相应时间来解决问题,这时候就可以考虑引入缓存。