缓存失效
缓存雪崩
大量缓存key失效或者缓存实例宕机导致的大批量数据在缓存中无法查到,进而请求数据库,造成数据库压力过大的情况。应对这种情况,对于前者,在设置key的过期时间的时候就应该避免大量key过期时间相同,可以在过期时间上添加一个随机时间范围,避免短时间内大量数据被删除。对于后者,缓存实例可以采用集群部署,使用redis的主从同步方案,这样即使主库挂掉,也会迅速选择一个从库作为主库提供服务。
同时缓存雪崩问题对缓存服务进行熔断、降级或限流,避免整个应用挂掉。
缓存击穿
少量访问量巨大的热点key失效,导致数据库访问压力激增。这种情况可以将热点key不设置过期时间,同时缓存的数据替换策略使用过期数据替换的策略,例如volatile-lru、volatile-lfu、volatile-ttl、volatile-random。
缓存穿透
用户查询的key在缓存中不存在,同时在数据库中也不存在。数据可能被误删,也有可能是外部攻击导致。这种情况请求会先去查缓存,然后缓存无数据,再去查数据库,数据库也无数据,从而导致数据库和缓存的压力都增大。
解决这个问题,可以对不存在的key也赋予指定值进行缓存,再取出的业务逻辑中判断是否为空值。也可以采用布隆过滤器,先对请求的key有无值进行判断,无值则直接返回。也可以在前端判断是否有大量的恶意请求进行封禁拦截。
布隆过滤器
布隆过滤器是基于一个长bit数组。写入数据时,对于一个key采用多种不同的哈希算法求得哈希值,然后将多个哈希值分别映射到bit数组中的指定位,将其标记为1;查询数据时,同样计算key的多个哈希值,然后查看bit数组的指定位置是否为1。如果有一个位置不为1,则说明数据不存在。但是如果均唯一也不能说明该key就存在,也有可能是存储的key过多或哈希碰撞,正好指定位置都是1。
布隆过滤器确定不存在的数据,一定是不存在的。但是布隆过滤器通过的数据,不一定就存在。但是当bit数组足够大的时候,它依然可以过滤相当大部分的不存在数据。
熔断、降级和限流。
熔断
熔断类似于电路中的保险丝,当链路中存在短路情况而导致电流激增的时候,它可以将链路熔断,避免其他电器受到损害。
类似的,在分布式服务中,假如服务A依赖与服务B,但是服务B的响应延迟突然变的很高或者不可访问,如果A服务继续等待B服务响应的话,A服务势必也会被B服务拖垮,而A服务可能还在其他链路上,从而影响所有与A服务相关的链路。这个时候就可以对B服务进行熔断。再次调用B服务时就直接返回错误,这样可以避免A服务仍然正常,与A服务相关的除B服务以外的其他链路不受影响。
同时熔断服务可以定时向B服务发送探活请求,如果B服务恢复了,或者延迟到达可以接受的阈值,那么就恢复对B服务的访问,从自动恢复链路。
降级
降级是基于整个服务的资源考虑,在高峰期,暂时关闭低优先级的服务,来确保高优先级的服务正常运行。
分布式服务中,同一个服务一般不可能只提供一个功能,例如用户服务,可能既提供查询用户优惠券的服务,又提供查询用户购买记录的服务。但是大促期间,显然是查询优惠券的服务优先级更高,而此时大多数用户不会去查询自己的购买记录。所以在大促期间,可以将查询购买记录的服务暂时关闭,这样该服务的物理资源、软件资源(例如内存、数据库、缓存等)就会被节省出来来确保核心服务的正常运行。
降级既可以手动降级(手动通过配置降级开关是否生效)也可以定时降级(定时执行降级开关打开关闭操作)。
限流
限流则直接限制服务的访问量,超出阈值的访问,既可以直接拒绝,也可以放入消息队列中等待执行。
计数器算法
固定时间窗口只能接受最大的请求数量,下一个时间窗口将请求数量清零。容易出现突刺现象。例如1s允许最大请求次数是100个,可能0.95s-1s来了100个请求、紧接着1-1.05s来了100个请求,这样下来0.1s的时间内出现了200个请求,大大超过了服务允许的范围。
在这个基础上优化,可以将固定窗口优化成滑动窗口。一连串时间窗口,每个窗口允许固定数量的请求数,一个窗口退出之后,下一个新进来的窗口请求数量从0开始算。本质上是将固定窗口的大小缩小,但是仍无法避免突刺现象。
漏桶算法
采用一个有容量的桶来接收请求,服务器以固定的速度从桶中取出数据,大量请求来临时便被存储在桶中,桶被装满之后后续请求被拒绝,有点类似消息队列。优点是不会出现短时间内需要处理大量请求的问题,缺点是请求处理速度固定,无论是请求繁忙还是空闲。
令牌桶算法
在漏桶的基础上做出改进,不再是服务器从桶中取出请求来处理,而是有一个线程不停的向桶中放入令牌,每一个请求到来时先从桶中获取令牌,如果获取成功,就被处理,获取失败就被拒绝。令牌桶满时,多余的令牌也会被丢掉。这样的有点就是请求的处理速度不会被固定死,当令牌充裕时,下一次大量请求会被快速处理。缺点是启动之后令牌桶需要预热,向桶中放入一定令牌之后才能开始工作,否则明明服务器很闲,却会有请求被拒绝。