10.Redis面试题

1.redis的优缺点

优点:

  • 读写性能优异,Redis能读速度是110000次/s,写的速度是81000次/s
  • 支持RDB和AOF两种数据持久化方式
  • 支持事务,Redis的所有操作都是原子性的,同时redis还支持对几个操作合并为原子性操作执行
  • 数据结构丰富,除了String类型,还有List,Set,Hash,Zset等数据结构
  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离

缺点:

  • 数据库容量收到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适用的场景是小规模数据的高性能操作上。
  • 主机宕机可能会导致宕机前有部分数据未及时同步到从机,导致数据不一致的问题,降低系统可用性
  • Redis不支持自动容错和恢复功能,主机从机的宕机都会导致前端部分的读写请求失败,需要等待机器重启或者手动切换前端IP才能恢复
  • Redis较难支持在线扩容,尤其是集群容量到达上限需要在线扩容会变得很复杂,所以运维人员就需要在系统上线时确保有足够的空间,这对资源造成了很大的浪费

2.redis是单线程还是多线程的?为什么?

首先多线程解决的问题是,当一个线程处理的任务IO密集型的,这个线程总是处于等待数据传输的状态,造成CPU资源的浪费。这是我们可以开辟多条线程,当一个线程等待IO传输时,CPU就可以调度其它线程处理任务,避免CPU资源浪费。
然后,redis的数据都存储在内存中,并不需要IO操作,而多线程的调度会使CPU的上下文切换反而造成额外的耗时,所以使用单线程即可。

3.redis持久化机制是什么?各自的优缺点

Redis提供两种持久化机制RDB和AOF;

RDB(Redis DataBase)是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,产生的数据文件时dump.rdb。可以通过设置配置文件的save参数来定义快照的周期
优点:
1.方便持久化,只有一个dump.rdb文件
2.容灾性好,一个文件可以保存到安全的磁盘
3.保证redis高性能,fork子进程来完成持久化的写操作,让主进程继续处理命令,不会进行任何IO操作
4.适合大规模的数据恢复,对数据完整性要求不高
缺点:
数据安全性低,需要间隔一段时间进行持久化操作,如果redis意外宕机,最后一次修改的数据就没有了。
fork的子进程会占用一定的内存空间。

AOF(append-only file):是将redis所有执行的每个写操作用日志的形式记录下来,只可以追加文件不可以修改文件。redis重启会读取这个日志文件重新构建数据库。换言之,redis重启就是将日志记录的命令从前到后执行一次以完成数据恢复工作
优点:
1.可以设置appendfsync属性为always让每次修改都记录到aof文件中,数据安全性高。
2.即使中途宕机,可以通过redis-check-aof工具解决数据一致性的问题
3.AOF的rewrite机制,会将多个key,value键值对集合起来用一条命令记录,以减少文件的大小。
缺点:
AOF比RDB文件大得多,所以恢复数据慢
数据集大的时候,AOF启动效率比RDB效率低

4.缓存失效问题及解决方案

缓存穿透:当请求一个不存在的数据时,由于缓存中没有,都去查数据库了,数据库也没有此记录,我们没有将此次查询的结果null放到缓存中。所以以后每次请求这个数据都会去存储层查询,失去缓存意义。风险是可能会利用这个不存在的数据进行攻击,数据库瞬间压力增大,导致崩溃。
解决方案:可以给缓存中存值为null的数据,并且设置过期时间

缓存雪崩:由于设置缓存时key采用了相同的过期时间导致缓存同一时间大面积失效,请求都落到数据库中,数据库压力过大导致雪崩
解决方案:过期时间加一个随机值,防止缓存集体失效的事件。

缓存击穿:某一个热点数据在某一个时刻被超高并发的访问,而且这个key此时正好失效,那么所有的请求都落到数据库。
解决方案:加锁,大量并发只让一个人去查数据库,查到以后把数据放到缓存中再释放锁。这时候其它人获取到锁会先查缓存,此时缓存就会有数据,不用去查数据库了。

5.Redis实现分布式锁

可以使用setnx命令实现分布式锁,

  • 使用setnx命令创建key获取锁
  • 给这个key设置一个过期时间
  • 释放锁删除这个key

需要注意的点
(1)给key设置过期时间为了防止释放锁删除key的时候宕机了,导致死锁
(2)删除锁的时候需要判断是不是自己占用的锁,为了防止业务时间过长,自己的锁已经过期,此时锁被其它线程占用,我们把别人正在持有的锁删除了。
(2)我们要保证创建key和设置过期时间为一个原子性操作,是为了防止设置过期时间的时候忽然宕机没有设置成功,因为宕机key也没办法删除了,导致死锁
(3)我们还要保证删除key和判断是不是自己占用的key是一个原子性操作,防止当判断是自己锁的时候,准备删除锁时刚好key过期了,此时是别人占着锁,我们把别人的锁给删掉了。

//P158用redeis中的setnx解决分布式锁的问题
public Map<String,List<Catelog2Vo>> getDataFromDbWithRedisLock(){
    //1.占分布式锁,去redis占坑
    //原子性操作,占据锁的同时要设置过期时间
    String uuid=UUID.randomUUID().toString();
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);

    //为了让锁自动续期,不至于执行途中因为时间过短而失效,可以设置时间长一些,然后finally保证业务操作完成之后,就执行删除锁的操作
    //不管怎样,哪怕崩溃也直接解锁,不关心业务异常
    if(lock){
        System.out.println("获取分布式锁成功");
        Map<String, List<Catelog2Vo>> dataFromDb;
        try {
            //2.加锁成功执行业务
            dataFromDb = getDataFromDb();
        } finally {
            //3.删除锁(判断value如果相等就删除  原子性操作)
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            stringRedisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class), Arrays.asList("lock"), uuid);
        }
        return dataFromDb;
    }else{
        System.out.println("获取分布式锁失败,等待重试");
        //自旋次数太频繁会出现异常,设置100ms的睡眠时间
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //加锁失败重试
        return getDataFromDbWithRedisLock();
    }
}
public boolean tryLock_with_lua(String key, String UniqueId, int seconds) {
    String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
            "redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
    List<String> keys = new ArrayList<>();
    List<String> values = new ArrayList<>();
    keys.add(key);
    values.add(UniqueId);
    values.add(String.valueOf(seconds));
    Object result = jedis.eval(lua_scripts, keys, values);
    //判断是否成功
    return result.equals(1L);
}

6.Redis的分布式锁超时怎么办?

其实就跟释放锁一样。由于业务逻辑时间太长,锁超时了可能此时是别人持有锁。我们只需要释放锁的时候判断这个锁是不是自己占用的锁就行了。判断的话设置锁的时候由于key相同,我们可以把value设置为随机的uuid判断。
需要注意的是判断是不是自己的锁和释放锁要把这两个操作合并为一个原子性操作,防止判断成功后,还没来得及删除锁过期了,此时又把别人的锁给删了。

7.Redis事务保证原子性吗,支持隔离性吗?支持回滚吗?

Redis中事务不保证原子性,且没有回滚,但是redis的单条命令都是原子性的。

  • 存在语法错误的情况下,所有命令都不会执行(入队错误)
  • 存在运行错误的情况下,除执行中出现错误的命令外,其他命令都能正常执行(执行错误)

原子性要求所有命令执行成功或者所有命令不起作用。通过分析我们知道了redis中的事务是不满足原子性的,在运行错误的情况下,并没有提供类似数据库中的回滚功能。

Redis通过谨慎的错误检测(入队错误,执行错误)和简单的设计来保证事务的一致性, 从而确保事务的一致性的

Redis使用单线程串行的方式来执行事务(以及事务队列中的命令),事务之间不可能产生影响,因此redis总是具有隔离性。
(Redis的事务总是具有ACID中的隔离性和一致性)

8.Redis的并发竞争问题如何解决,了解Redis事务的CAS操作吗?

Redis是单进程单线程模式,采用队列模式将并发访问转换成串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成的。对此有两种解决办法
1.客户端角度,为保证每个客户端正常有序的与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部所synchronized.
2.服务器角度,利用setnx实现锁

MULTI ,EXEC,DISCARD,WATCH这四个命令是Redis事务的4个基础命令。
MULTI 告诉redis服务器开启一个事务,只是开启,而不是执行。
EXEC 开始执行事务
DISCARD 取消事务
WATCH 监视某一个键值对,它的作用是在事务执行之前如果监视的键值被修改,事务会被取消。 可以利用WATCH实现CAS乐观锁。
在这里插入图片描述

9.redis的默认内存是多少?在哪里查看?如何设置修改最大内存?一般生产上如何配置?如果redis使用超出最大值会怎样?

redis的默认内存就是最大物理内存
可以使用info memory查看内存的使用情况

# Memory
used_memory:873088
used_memory_human:852.62K
used_memory_rss:9617408
used_memory_rss_human:9.17M
used_memory_peak:1429856
used_memory_peak_human:1.36M
used_memory_peak_perc:61.06%
used_memory_overhead:830624
used_memory_startup:809928
used_memory_dataset:42464
used_memory_dataset_perc:67.23%
allocator_allocated:920720
allocator_active:1171456
allocator_resident:3477504
total_system_memory:1927172096
total_system_memory_human:1.79G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction

可以通过配置文件设置maxmemory参数
在这里插入图片描述
或者使用config set maxmemory命令设置

127.0.0.1:6379> config set maxmemory 104857600
OK
127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "104857600"

一般推荐redis设置最大物理内存的四分之三,或者就使用默认的。
超出最大值会报OOM的错误
在这里插入图片描述

10.redis的过期键的删除策略

redis中key过期了并不是立刻删除的,redis过期键的删除策略就是指当Redis中的key过期了,Redis如何处理
过期策略通常有以下三种

  • 定时删除:每个设置过期时间的key都会创建一个定时器,到过期时间立刻清除。该策略可以立刻清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性删除:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略最大化地节省CPU资源,但是对内存很不友好。有可能出现大量的过期的key没有再次被访问,从而不会被清除,占用大量内存
  • 定期删除:每隔一段时间随机抽取一定数量的可key执行一次删除过期键的操作,并通过限制删除时间的时长和频率使得CPU资源和内存达到最优的平衡效果。

Redis中同时使用了惰性删除和定期删除两种过期键的删除策略。

11.Redis的内存淘汰策略有哪些?怎么设置内存淘汰策略?

如果使用惰性删除时,从来没有被点到过,定期删除从来没有被抽查到,就会有漏网之鱼。可能会有大量过期的key占用着大量内存。

redis的内存淘汰策略是指在Redis的使用的内存快达到最大内存时如何处理腾出内存空间的

noeviction:默认的内存淘汰策略,不会驱逐任何key,当内存不足以容纳新写入数据时,新写入操作会报错
volatile-ttl: 当内存不足以容纳新写入的数据时,设置了过期时间的key中,有更早过期的key优先移除。

allkeys-lru: 对所有的key使用LRU算法进行删除
volatile-lru:对所有设置了过期时间的key使用Iru算法进行删除

allkeys-random:对所有的key随机删除
volatile-random: 对所有设置了过期时间的key随机删除

allkeys-lfu:对所有的key使用Ifu算法进行删除
volatile-lfu:对所有设置了过期时间的key使用Ifu算法进行删除

lru算法是根据最近最少使用进行淘汰 注重的时间
lfu算法是根据最近最不经常使用进行淘汰 注重的频率
使用配置文件的形式,配置maxmemory-policy参数
在这里插入图片描述
或者使用config set maxmemory-policy命令

127.0.0.1:6379> config set maxmemory-policy allkeys-lru
OK

12.Redis的内存如何优化?

尽可能的使用散列表也就是hash数据类型,因为散列表使用的内存非常小,所以我们尽量将数据模型抽象到散列表里面,比如说一个用户对象,我们不应该把这个用户的用户名,邮箱,密码等单独设置key,而是应该把这个用户的信息存储到一张散列表里面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值