爱陪小樱桃

导航

 

常见缓存问题:

1.缓存穿透
2.缓存击穿
3.缓存雪崩

缓存穿透

缓存穿透: 是指客户端请求的数据在缓存中都不存在,这样缓存永远也不会生效,这些请求都会打到数据库。

缓存穿透常见的解决方案有两种:

缓存空对象

  • 优点:实现简单,维护方便
  • 缺点:额外的内存消耗,可能造成短期不一致。
    缓存穿透的流程:例如:前端客户端先从redis查询商户,判断缓存是否命中,如果命中,缓存直接返回,如果没有命中从数据库中查询,数据中库中查询到,直接返回同时更新到Redis中,如果数据库没有查询到直接返回404。

2.如果解决上边的问题:就需要把数据库返回不能直接返回404了,我们需要把数据的空值也直接写入到Redis中,此放入redis同样也有一个问题:就是你从Redis读取的时候同样也会存在空,你需要做一个判断从Redis读取的结果是否是空,如果不是空正常返回,但是如果命中的是空,直接返回空,这样下次就是直接从Redis中获取空结果。

布隆过滤:

  • 优点:内存占用少,没有多余的key
  • 缺点:实现复杂,存在误判可能。

缓存穿透产生的原因:

用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大的压力。
2.解决方案:缓存null值,步隆过滤。
3.增加ID的复杂度,避免被猜测ID.
4.做好数据的基础格式校验。
5.加强用户权限的校验,
6.热点参数的限流
7.对于空值也可以做限流。

缓存雪崩:

定义什么是缓存雪崩:

听名字肯定可以感觉到,肯定是发生了比较严重的问题,是指在同一时段大量的缓存key同时失效,或者Redis服务宕机,导致大量的请求到达数据库,带来了巨大的压力。

a.正常情况应该是大部分请求直接到redis,然后呢,直接返回,少数请求直接到达数据库,突然时间一瞬间几万几十万的key都过期了,导致这些请求直接到达数据,可能导致数据库直接到达数据库,或者Redis服务器直接宕机了,这时候客户端再来访问,直接访问到了数据库,这时候哦数据库肯定会有巨大压力。

解决方案:

1.为了避免TTL同时到期,可以给TTL添加随机值,有时候我们在进行换粗预热的时候:给TTL增加随机数,这样可以是使key分散在不同的时间段,
2.redis宕机这种才是最严重,怎么避免呢,提高redis高可用性,先搭建Redis集群形成主从,一个挂了,另一个立刻能够补充,这样就可以很大程度保证Redis高可用性。
3.给缓存增加限流策略:整个Redis全挂了,提前做好一些放错误处理,比如:快速失败,拒绝请求,这样就可以保护数据库,虽然牺牲了部分用户。
4.给业务天津爱多级缓存:比如设置ngix 缓存,数据库缓存,Redis缓存等等,比如我们防弹衣穿了多件。这种方案一般会在大型电商里用到,用于亿级别的性能。

缓存击穿:

缓存击穿定义:

1.如果说缓存雪崩是所有的key同时失效,那么缓存击穿时部分key失效,带来的问题,这是因为这部分key 不是一般的key,这种key是热点key,就是一个被高并发访问,并且缓存重建业务较为复杂的key突然失效了,无效的请求访问会在瞬间给数据库带来大量的冲击。

有的时候:我们不是所有的查出来的东西都放在redis里面,比如我们查询的时候,逻辑比较复杂涉及很多个表,比较耗时,那么这段时间我们的Redis等于一直都没有缓存,在这段时间内,无数请求过来都无法命中Redis,只能访问数据库。等缓存重建后才能返回Redis。在这期间这些请求都打入到了数据库,在这么长的一段时间内,无数请求,我们改怎么解决呢?

解决方案:

  • 互斥锁:
    假如有个请求,没有命中,此请求需要进行缓存重建,此请求需要获取锁,获取锁成功,缓存创建成功,释放锁,在锁未释放期间:另外的请求如果来了,检测到没有释放,就让此请求休眠一会儿,进行循环锁,直到有请求释放锁,,(没有额外的内存消耗,内存占用比较小)
  1. 优点:
    没有额外的内存消耗,内存占用小
    保证一致性
    实现简单
  2. 缺点:
    线程需要等待,性能受影响
    可能有死锁的风险
  • 逻辑过期:
    以前存的时候我们会设置TTL,缓存突然间失效导致,不设置TTL,我们怎么知道缓存过期了,所以说是逻辑过期,所以我们在value里面增加一个expire过期时间,如果逻辑过期了,我们设置锁,为了避免互相等待,他会创建一个新的线程,有此新线程进行获取锁等操作,此时自己返回旧的数据。
  1. 优点:
    线程无序等待,性能好。
  2. 缺点:
    不保证一致性
    有额外的内存消耗
    实现复杂

例如抢特价购券秒杀:

例如创建:基本券的基本信息基本表,创建秒杀券表。
基本券信息表:
券ID,
商铺id,
代金券标题,
副标题,
使用规则,
支付金额单位分,
抵扣金额,
券类型:普通,券还是秒杀券,
状态:上架,下架
创建时间
更新时间

秒杀券的表:
关联的优惠券ID
库存
创建时间
生效时间
无效时间
更新时间
秒杀优惠券表和优惠券是一一对应的关系。

我们可以认为:基本券表里面包含秒杀券。秒杀券表相当于扩展了一些字段。

如何实现下单秒杀:

有两点需要我们注意:

  • 秒杀开始和结束,如果尚未开始或已经结束则无法下单
  • 库存是否充足,不足则无法下单。

流程:前端提交券的ID,然后接口查询优惠券信息,判断秒杀是否开始,如果未开始:返回异常结束。
2.如果已经开始:代表一个正常请求&&合理的时间,然后判断库存是否充足,如果库存不足,返回异常。
如果库存充足,自然就去去库存扣除,如果扣除成功,下单。(只是实现了下单)

作为测试如何测试上述的场景呢?

1.按照上边的流程,通过页面可以测试基本功能,但是对于秒杀来说,仅仅通过页面是不够的,因为一瞬间会有很多请求。

2.需要进行高并发测试:

案例:如果库存100,我们使用jemeter同时请求200个理论应该是一半失败,结果确有多余一半的部分成功了,这是什么问题,怎么出现超卖呢?

超卖问题的产生:
在搞并发下:
线程1:先查询,在判断,>0,进行下单。
线程2:先查询,发现库存=0,不能进行下单。
但是高并发下:线程1在查询库存的时候1,然后进行下单,在下单的过程中,线程2也来查询发现库存也是1,这时候也进行扣减(只要在线程1,扣减前,多线程进行下单)。

解决上述方案就是加锁:)

锁的分类:悲观锁,乐观锁:

  • 悲观锁:定义:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行,例如:Synchroized ,Lock,数据库里面存在的锁都是属于悲观锁。

  • 乐观锁:属于乐观派,认为线程安全问题不一定发生,因此不加锁,只是在更新数据时候去判断有没有其他线程对数据做了修改,如果判断没有修改则认为是安全的,自己才更新数据,如果判断已经被其他的线程修改发生了安全问题,此时可以重试或者异常。(性能好一点,关键点:在于判断怎么判断数据有没有被修改),方法1:给数据加个版本号,数据每次被修改都会加个版本号,我要判断数据有没有被修改,就判断版本号有没有变化,在实现中发现:version版本号和库存都需要判断,其实可以拿库存作为版本号来判断,(这就是CAS法)

总结:

本次内容:主要是解决了一个并发安全问题,超卖的问题,解决方案有哪些?

1.悲观锁:添加同步锁,然线程串行执行

优点:简单粗暴
缺点:性能一般,串行执行。

2.乐观锁:不加锁,就是在更新的时候判断一下是否有其他线程在修改。(版本号version)

优点:性能稍微好点
缺点:基于数据变更,缺点就是成功率低,比如1000个人来下单,理论库存是1000的话,应该可以的,但是检测到数据有更新,其实只有一个人成功(所以为了解决这个问题,我们只需要判断库存大于0即可,不在判断数据是否一致)。比如有的不是库存,他只能通过数据是否更新来判断是否一致,这种情况下要想提升成功率,我们可以采用分段加锁的情况。(例如京东,淘宝这种,仅仅使用乐观锁是不够的)

修改上述的描述业务:要求同一个用户一个优惠券只能下一单(主要是为了引流,加大宣传)

简单来说就是用来了,先查询一下用户是否下过此券的单。(条件券ID&&用户ID联合查询)

所以对用户ID进行加锁,这样可以保证一个用户一个锁进行查询,而不是对整个方法加锁,如果对整个方法加锁,只能保证一个来用户来访问,别的等待,

在当前多台集群多并发的安全问题:

在一个JVM1,用userid锁的监视器,可以判断锁是否释放,当新的JVM2的时候:用userid作为梭的时候,锁的监视器和jvm1的监视器不是同一,各自走自己的监视器,每个jvm都有一个监视器是成功的,那么就会又一次出现线程安全问题,在集群模式下,或者分布式的情况,导致每个梭都会有一个线程运行,那么问题:就要保证如何保证多个JVM怎么使用同一把锁?

多个jvm怎么使用同一把锁?(分布式锁)

分布式锁:定义:
满足分布式系统或者集群模式下多进程可见并且互斥的锁,
特点1:多进程可见,例如Redis,或者MySQL
特点2:互斥
特点3:高可用(大多数情况下是可用的)
特点4:高并发
加锁本身就会影响性能,而且获取锁的动作又很慢,就是雪上加霜了,所以获取锁要高并发,
特点5:安全性,获取梭失败,要不能影响后边获取锁。

MYSQL 如何实现互斥:

1.例用数据库本身的互斥梭机制
2.高可用性:好
3.高性能:一般
4.安全性:可以及时释放锁,自己释放锁

Redis 如何实现互斥:

1.例用setnx这样的互斥命令
2.高可用性:好
3.高性能:好
4.安全性:例如超时时间,到期释放

Zookeeper :

1.例用节点的唯一性和有序性实现互斥
2.高可用性:好
3.高性能:一般
4.安全性:节点是临时节点,一点宕机会释放。

基于Redis的分布式锁:

获取锁:

互斥:确保只有一个线程获取锁,利用setnx获取锁

释放锁:

把锁删除;
如果服务宕机,长时间没有人操作,就要添加超时时间expire,到期自动删除。

所以:要保证获取锁set和ex同时发生,不然会出现在expire还没有生效的时候宕机了,就无法获取到锁了。

非阻塞式获取锁:如果获取不到锁,就返回失败。
阻塞式获取锁:获取失败,就一直等待别的人释放锁。

posted on 2024-07-15 21:42  cherry小樱桃  阅读(13)  评论(0编辑  收藏  举报