1. 分布式场景下的问题解决
1.1 分布式锁释放问题及解决
(1)锁不能及时释放问题
(2)锁释放方式
- 通过UUID唯一匹配分布式锁的key,并设置锁超时时间
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. 布隆过滤器
解决缓存穿透查询问题
网友评论