【深入浅出Redis】Redis常见问题以及解决方案,可用于面试
前面分享了几篇Redis系列文章,那么我们在使用Redis的过程中都会遇到什么问题?如何解决?都有哪些方案?
下面给大家介绍下
redis系列问题以及优化
Redis-hotkey热key
大量请求可能会使redis宕机,继而打到数据库崩溃;因此需要对热key优化
如何发现热key?
- 使用自带的–hotkeys查找(前提条件是 Redis Server 的maxmemory-policy 参数设置为 LFU 算法)
- 京东零售的hotkey(https://gitee.com/jd-platform-opensource/hotkey)项目支持热key发现以及处理
- 根据业务情况提前预估
- 业务代码中记录分析打点统计
- 借助阿里云分析(https://www.alibabacloud.com/help/zh/redis/user-guide/use-the-real-time-key-statistics-feature)
如何解决hotkey?
- 读写分离,读从从节点上请求
- 使用redis cluster,热点数据分散在存储多个redis的节点上
- 阿里云方案
内存碎片
info memory 可查看内存相关信息
redis-cli -p 6379 info | grep mem_fragmentation_ratio
如何清理Redis内存碎片?
重启节点野可以做到内存碎片整理
config set activedefrag yes
# 内存碎片占用空间达到 500mb 的时候开始清理
config set active-defrag-ignore-bytes 500mb
# 内存碎片率大于 1.5 的时候开始清理
config set active-defrag-threshold-lower 50
下面设置尽量减少对Redis性能的影响
# 内存碎片清理所占用 CPU 时间的比例不低于 20%
config set active-defrag-cycle-min 20
# 内存碎片清理所占用 CPU 时间的比例不高于 50%
config set active-defrag-cycle-max 50
Redis异步队列
一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当sleep 一会再重试。
不用sleep可以使用blpop,在没有消息的时候,它会阻塞住直到消息到来
Redis watch机制
Redis Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。注意使用multi 开始事务,exec 提交事务。
语法, redis Watch 命令基本语法如下:
WATCH key [key …]
如何判断一个节点宕机
如果一个节点认为另外一个节点宕机,那么就是 pfail ,主观宕机。如果多个节点都认为另外一个节点宕机了,那么就是 fail ,客观宕机,跟哨兵的原理几乎一样,sdown,odown。
cluster中,在 cluster-node-timeout 内,某个节点一直没有返回 pong ,那么就被认为 pfail 。如果一个节点认为某个节点 pfail 了,那么会在 gossip ping 消息中, ping 给其他节点,如果超过半数的节点都认为 pfail 了,那么就会变成 fail 。
Redis分布式锁可能存在的问题
死锁问题
没有正确执行锁释放,锁也不会自动释放,一直持有,导致死锁
解决方案:设置超时时间,setnx和expire可以一起执行,具备原子性
锁续命问题
场景:当 A 用户获取锁成功,但是 A 执行业务操作的时间比较长,就可能会导致业务执行时间大于锁失效时间,那么 A 还在执行业务,锁失效了,就会导致其他用户也可以成功竞争到锁,这样就没办法很好的锁住业务,就会导致脏数据的出现。
解决方案:当 A 竞争到锁时,就需要开启一个 timer 对锁续命,续命就是重新设置锁的失效时间,由定时器定时的去做这件事情。业务操作完成,unlock,timer.cancel()。Redission经典使用,代码示例如下
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*
* Redisson的配置类,提供RedissonClient实例
* */
@Configuration
public class RedissionConfiguration {
@Value("${spring.redis.host}")
private String host;
@Bean
public RedissonClient getRedissionClient(){
Config config=new Config();
//设置看门狗时间
// config.setLockWatchdogTimeout(30000);
//集群模式,集群节点的地址须使用“redis://”前缀,否则将会报错。
//此例集群为3节点,各节点1主1从,逗号间隔
config.useMasterSlaveServers().setMasterAddress("redis://" + host + ":6379");
return Redisson.create(config);
}
}
@Autowired
private RedissonClient getRedissionClient;
@GetMapping("/lock")
public void lock(){
Boolean ret = false;
RLock lock = getRedissionClient.getLock("a");
try {
/**
* waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
* leaseTime 锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
* 具有Watch Dog 自动延期机制 默认续30s 每10s检查续命30s 那其实还是相当于阻塞 和没有设置时间是一个道理,只能依赖unlock
*/
System.out.println("start");
ret = lock.tryLock(10, TimeUnit.SECONDS);
System.out.println(ret);
if (ret) {
TimeUnit.SECONDS.sleep(15);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (ret) {
lock.unlock();
}
}
}
主从架构-锁失效问题
场景:当 redis 是一个主从架构时,当 A 用户从 master 节点获取到了锁,这时候出现这么一个情况,这个锁没有及时同步到 slave 节点时,master 节点挂了,由 redis 哨兵做了主从切换,以前的 slave 成了主,而这时 slave 是没有锁的这个 key 的,那么就会导致其他用户也可以成功竞争到锁进行业务操作,从而导致脏数据。
解决方案:使用Redission的红锁
缓存雪崩
场景:大量的key设置相同的过期时间,同时失效,造成瞬间DB请求量大,压力剧增引起雪崩
解决方案
- 给缓存过期时间设置随机时间不同时失效
- 热点数据不过期
- 提高redis可用性(治标不治本)
- 服务限流&降级(治标不治本)
- 缓存预热
缓存穿透
场景:访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时会挂掉
解决方案:
- 采用布隆过滤器(推荐),使用大的bitmap,存储可能访问的key,不存在的key直接过滤(布隆规则是如果没查到证明一定不存在,如果查到存在则不一定存在)
- 访问key未查到值,也将空值写进缓存,设置较短时间(使其不查数据库)
- 校验完善:好的校验会防止不合法的参数请求,减少穿透的概率
缓存击穿
场景:一个存在的key,过期的瞬间有大量请求,击穿db
解决方案
- 互斥锁,可用分布式锁控制的强,如果服务器不多也可用本地锁,就算击穿也不会将db打死
- 缓存空值,设置一个合理时间
- 热点数据不过期
- 限流,都可以
大key和大value有什么危害
- 内存不均匀:单value较大时,可能会导致节点之间的内存使用不均匀,间接地影响key的部分和负载不均匀;
- 阻塞请求:redis为单线程,单value较大读写需要较长的处理时间,会阻塞后续的请求处理;
- 阻塞网络:单value较大时会占用服务器网卡较多带宽,可能会影响该服务器上的其他Redis实例或者应用
String和Hash,哪个存储对象更好
- String 存储的是序列化后的对象数据,存放的是整个对象。Hash 是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。如果对象中某些字段需要经常变动或者经常需要单独查询对象中的个别字段信息,Hash 就非常适合。
- String 存储相对来说更加节省内存,缓存相同数量的对象数据,String 消耗的内存约是 Hash 的一半。并且,存储具有多层嵌套的对象时也方便很多。如果系统对性能和资源消耗非常敏感的话,String 就非常适合。
在绝大部分情况,我们建议使用 String 来存储对象数据即可!
Redis6.0后为什么引入了多线程?
Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。
虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
Redis6.0 的多线程默认是禁用的(暂时不建议开),只使用主线程。如需开启需要设置 IO 线程数 > 1,需要修改 redis 配置文件 redis.conf:
配置指令:io-threads 4 #设置1的话只会开启主线程,官网建议4核的机器建议设置为2或3个线程,8核的建议设置为6个线程
Redis使用优化
- key越短越好
- 避免使用keys *
- 存储前压缩
- 设置有效期
- 选择合适的回收策略
- 不需要持久化时关闭
- 添加多条数据尽量用管道
其他序列文章
redis.conf配置文件详细解释 调优必备
【深入浅出Redis-高可用篇】带你吃透Redis高可用以及弹性扩容方案
Redis过期策略和持久化机制全面揭秘,教你如何合理配置
一次redis OOM问题分析解决,rdbtools安装分析redis内存