Redis多级缓存设计与性能优化

2023-01-09  本文已影响0人  蓝调_4f2b

1. 分布式场景下的问题解决

1.1 分布式锁释放问题及解决

(1)锁不能及时释放问题
(2)锁释放方式

String lockKey = "lock:product_101";
String clientId = UUid.randmoUUID().toString();
Boolean result = StringRedisTemplate.opsForvalue().setIfAbsent(lockKey, clientId,  10, TimeUnit.Seconds);
finally {  if(clientId.equals(StringRedisTemplate.opsForvalue().get(lockKey)) {
  StringRedisTemplate.delete(lockKey);
}
}

1.2 锁续命方案

主线程分出子线程执行定时任务,定时查看主线程锁是否超时;若没有超时,刷新(续命)主线程计时器


锁续命方案.png

注:原理为使用Lua脚本完成并发控制

1.3 Lua脚本优点

(1)保证原子性:不允许其他Redis线程中间插入执行Lua命令
(2)减少网络开销
(3)替换数据库事务功能

2. Redis主从节点锁失效问题

2.1 分布式锁在并发场景下加锁问题

示意图.png

注:超过半数Redis节点加锁成功则认为对该key加锁成功

2.2 提升分布式锁性能

(1)缩小锁力度
(2)将key值合理分割,为每个client分配key值
例如:对库房中1000个商品而言,可以按100的维度拆开
product_101_stock_1:100
product_101_stock_2:100

3. 大规模商品缓存数据冷热分离场景

设置key的过期时间

public product get(long productId) {
  Product product = null;
  String productCacheKey = RedisKeyPrefixConst.product_cache;
  String productStr = redisUtil.get(productCacheKey);
  if (!StringUtils.isEmpty(productStr)) {
    product = JSON.parseOfObject(productStr, PRODUCT_CLASS);
    // 读延期
    redisUtils.expire(productCacheKey, product_cache_timeout, TimeUnit.Seconds);
    return product;
  }
  product = productDap.get(productId);
  if (product != null) {
  redisUtil.set(productCacheKey, JSON.toJSONString(product), TimeOut, TimeUnit.Seconds);   // 设置超时时间
}
}

4. Redis缓存问题及处理

4.1 缓存穿透

(1)大量请求查询Redis中不存在的数据,导致每次请求都要通过后段查询到DB,缓存层失去保护后端的作用
(2)解决方式:
(2.1)缓存空对象 + 为空对象的key加上过期时间
(2.2)使用布隆过滤器

4.2 缓存失效(击穿)

(1)大批量缓存在同一时间同时失效,导致大量请求同时穿透缓存直达DB中,造成DB瞬间压力过大
(2)解决方式:为每个key设置不同的过期时间

4.3 缓存雪崩

(1)缓存层支持不住或宕机时,请求流量疯狂进入后端/DB中;一段时间后,积攒的流量使后端服务挂掉
(2)处理方式:
Redis高可用架构(哨兵,集群架构)
流量压力过高时,使用熔断/限流机制
降级处理

4.4 突发性热点缓存重建导致系统压力暴增问题

解决思路:
(1)使用双检锁机制
(2)使用分布式锁机制

4.5 缓存与DB双写不一致问题

缓存与DB双写不一致问题.png

解决思路:
(1)对于并发几率很小的数据(如个人维度的订单数据、
用户数据等),这种几乎不用考虑这个问题
(2)就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。
(3)如果不能容忍缓存数据不一致,可以通过加分布式读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁。

public Product get(long Id) {
  Product product = null;
  String key = PRODUCT_CACHE + Id;
  product = getProductFromCache(key);
  if (product != null) {
      return product;
  }
 // 加锁
RLock hotCacheLock = redison.getLock(hot_cache_prefix + Id);
hotCacheLock.lock();
}

注:Redisson读写锁实现原理:Lua脚本

5. Redis过期删除策略

5.1 被动删除

当读/写一个已过期key时,触发惰性删除策略,删除已过期的key

5.2 主动删除

由于惰性删除无法保证冷数据(不访问的数据)及时删除,Redis会定期主动删除一些已过期的key(一些定时任务)

5.3 当前已用内存超过maxmemory时,触发设置好的删除策略

(1)ttl 删除策略:筛选时,针对设置过期时间的键值对,根据该值先后的过期时间进行删除
(2)lru 删除策略:由于淘汰太久没有访问的数据,以最近一次的访问作为参考进行删除(时间维度)
(3)lfu 删除策略:淘汰最近一段时间内被访问次数最少的数据(次数维度)

5.4 LRU与LFU算法使用场景

(1)当存在热点数据较多时,LFU效率较好
(2)其他场景优先选用LRU

5.5 其他优化点

(1)配置好maxmemory_police(最大内存)
不然Redis内存超出物理内存限制时,内存数据将与磁盘频繁交换,极大影响效率

6. 布隆过滤器

解决缓存穿透查询问题

上一篇 下一篇

猜你喜欢

热点阅读