Redis使用细节(持续更新中)

Redis使用细节

分布式锁

因为Redis是单线程的,所以可以用setnx来模拟锁的获取释放从而实现分布式锁

在用setnx实现分布式锁时,会出现一些问题

  • 业务超时解锁,导致并发问题。业务执行时间超过了锁超时的时间
  • redis主从切换临界点问题,主从切换后,A持有的锁还没有同步到新的主节点,B在新的主节点获取到了锁
  • redis集群脑裂,导致出现多个主节点

大key和热key

大key定义

  • String类型:value的字节数大于10KB即为大Key

  • Hash/Set等复杂结构类型:元素个数大于5000个或总value字节数大于10M即为大key

大key危害

  • 读取成本高
  • 容易导致慢查询
  • 主从复制异常,服务阻塞,无法正常响应请求

消除大key的方法

1.拆分

将大key拆分为小key,例如一个String拆分成多个String

2.压缩

将value压缩后写入Redis,读取时解压后再用,注意选择一个合适的算法

3.集合类结构hash消除的方法

  • 拆分,用hash取余,位掩码的方式决定放在哪个key中
  • 区分冷热:比如榜单列表场景使用zset,只缓存前10页数据,后续的数据走db

热key定义

用户访问一个key的QPS特别高,导致server出现CPU负载突增或者不均的情况,热key没有明确的标准,一般QPS超过500就有可能被识别为热Key

解决热Key的方法

1.设置Localcache

在访问Redis前,在业务侧设置Localcache,降低访问Redis的QPS,如果Localcache过期或者未命中,则从Redis中将数据更新到LocalCache

2.拆分

将一个热key复制写入多份,访问的时候访问多个key,但是value是同一个,但是代价是更新时需要更新多个key

慢查询场景

容易导致慢查询的操作

  • 批量操作一次性传入过多的key/value,如mset/hmset等操作,建议单批次不要超过100

  • zset大部分命令都是O(logn),当大小超过5k以上时,简单的zadd/zerm也可能导致慢查询

  • 操作大key

缓存穿透和缓存雪崩

缓存穿透:热点数据查询绕过缓存,直接查询数据库

缓存雪崩:大量缓存同时过期

缓存穿透的危害:

  • 查询一个一定不存在的数据,这样的所有请求都会打到db上
  • 缓存过期时,一个热key过期,也会有大量的请求同时击穿至db

减少缓存穿透的方法

  • 缓存空值,在查询到在缓存和数据库中都不存在,则可以缓存一个空值
  • 布隆过滤器,使用一个算法来存储合法key

避免缓存雪崩的方法

  • 将缓存失效的时间分散开,比如在原有的失效时间基础上增加一个随机值
  • 使用缓存集群,避免单机宕机造成的缓存雪崩

缓存击穿问题

缓存击穿也叫热点key问题,被高并发访问以及缓存重建业务较复杂的key突然失效了,会对数据库带来巨大冲击。

解决方法:

互斥锁和逻辑过期

1.互斥锁就是在做缓存重建时进行加锁,其余线程进行带有休眠的重试

缺点:

线程需要等待,性能受影响

会有死锁风险

2.逻辑过期,不设置真正的过期时间,而是设置一个字段作为逻辑过期

获取互斥锁后开启一个新线程,进行缓存重建,同时原始线程返回一个旧的信息

其余线程发现过期后获取互斥锁失败后不进行等待,直接也返回旧数据

缺点:

不能保证一致性

数据库和缓存如何保持一致性

无论是先更新数据库还是先更新缓存,都有可能无法保证数据库的一致性

于是引出旁路缓存策略(cache aside pattern)

更新数据时,不更新缓存,而是删除缓存中的数据。然后到读取数据时,发现缓存中没有数据之后,再从数据库中读取数据,更新到缓存中

该策略可以分为两个策略,读策略和写策略

写策略是更新数据库或删除缓存

读策略是缓存未命中后读取数据库的数据和回写数据

先删除缓存,再更新数据库会造成数据不一致的问题

而先更新数据库,再删除缓存虽然也可能会存在数据不一致的问题

但是因为写缓存的速度远远比写数据库的速度要快,所以缓存不一致的情况很少发生

所以,最好的方案就是先更新数据库,再删除缓存

删除相对于更新的好处除了数据一致性的问题,还因为每次更新数据库都更新缓存,无效写比较多。

然后给缓存数据加上过期时间,防止意外的缓存与数据库的数据不一致

但是该方法在每次更新数据库时,缓存的数据会被删除,这样会对缓存的命中率带来影响

除了旁路更新策略以外

还有write behind caching pattern

调用者只操作缓存,由其它线程异步的将缓存数据持久化到数据库,保证最终一致

不过目前主流的还是旁路缓存策略

然后我们还要保证数据库与缓存操作的原子性

在单体系统里,将缓存和数据库操作放在同一个事务

如果不使用事务的话,要保证操作一致性,可以采用重试机制

Redis分布式锁误删问题

在使用的时候设置值为该线程所产生的唯一的一个id,当这个线程阻塞后导致锁过期时,新的线程再重新加锁,原来的线程执行完毕之后判断当前锁是不是本线程产生的锁,如果不是则就不删除,这样就防止误删问题,不过为了加锁时间长于业务执行的时间,我们需要别的解决方法

Redission分布式锁

这里重点讲解一下ReentrantLock,可重入锁

解决锁的重入以及重试问题,并且防止业务用时超过加锁时间

加锁和解锁是用Lua脚本原子性的实现的

锁的可重试

先尝试获取锁,然后申请锁的耗时如果大于等于最大等待时间,则申请锁失败

可以订阅锁释放事件,通过awit方法阻塞等待锁释放,防止无效的锁资源申请问题,一旦锁释放会发消息通知等待的线程进行竞争

锁的续期机制

Redission提供了一个续期机制,只要一旦加锁成功,就会启动一个watchdog,每隔一段时间就会检查一下是否持有锁,如果持有,就会延长锁的时间

缺点就是采用多个Redis时会出现一些问题

锁的可重入机制

多记录一个加锁次数,每次访问使得锁的重入次数加1

posted @ 2023-08-25 19:29  ANewPro  阅读(16)  评论(0编辑  收藏  举报