欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Redis常问面试题整理

程序员文章站 2022-04-13 20:50:23
...

文章出处:Redis常问面试题整理

介绍
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API的非关系型数据库。
传统数据库遵循 ACID 规则。而 Nosql(Not Only SQL 的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称) 一般为分布式而分布式一般遵循 CAP 定理

1.Redis支持哪几种数据类型?

1.string:最基本的数据类型,二进制安全的字符串,最大512M。
2.list:按照添加顺序保持顺序的字符串列表。
3.set:无序的字符串集合,不存在重复的元素。
4.sorted set:已排序的字符串集合。
5.hash:key-value对的一种集合。

Redis常问面试题整理
另外还有四种特殊的 数据类型
1.BloomFilter
2.HyperLogLog
3.BitMap
4.Geo

这几种数据类型的具体介绍。
立即了解redis其他的数据类型 : 点击了解

注意 关于数据类型多说两句:

redis里存的都是二进制数据,其实就是字节数组(byte[]),这些字节数据是没有数据类型的,只有把它们按照合理的格式解码后,可以变成一个字符串,整数或对象,此时才具有数据类型。

这一点必须要记住。
所以任何东西只要能转化成字节数组(byte[])的,都可以存到redis里。管你是字符串、数字、对象、图片、声音、视频、还是文件,只要变成byte数组。

  • 关键字(Keys)是用于标识一段数据的一个字符串
  • 值(Values)是一段任意的字节序列,Redis不会关注它们实质上是什么

关于key

  • key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率
  • key也不要太短,太短的话,key的可读性会降低
  • 在一个项目中,key最好使用统一的命名模式,例如 user:10000:passwd

2.Redis是单进程单线程的吗?

Redis是单进程单线程的,Redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。

3.Redis为什么是单线程的?

多线程处理会涉及到锁,而且多线程处理会涉及到线程切换而消耗CPU。
因为CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。
单线程无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来解决。

补充额外知识点 :其它开源软件采用的模型
Nginx:多进程单线程模型
Memcached:单进程多线程模型

4.Memcache与Redis的区别都有哪些?

Memcache (MC)

看看 MC 的特点:
MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀;
MC 功能简单,使用内存存储数据;
MC 的内存结构以及钙化问题我就不细说了,大家可以查看官网了解下;
MC 对缓存的数据可以设置失效期,过期后的数据会被清除;
失效的策略采用延迟失效,就是当再次使用数据时检查是否失效;
当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key 进行清理,还会按 LRU 策略对数据进行剔除。

另外,使用 MC 有一些限制,这些限制在现在的互联网场景下很致命,成为大家选择Redis、MongoDB的重要原因:
key 不能超过 250 个字节;
value 不能超过 1M 字节;
key 的最大失效时间是 30 天;
只支持 K-V 结构,不提供持久化和主从同步功能。

Redis

先简单说一下 Redis 的特点,方便和 MC 比较。

与 MC 不同的是,Redis 采用单线程模式处理请求。
这样做的原因有 2 个:
一个是因为采用了非阻塞的异步事件处理机制;
另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
Redis 支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库。
相比 MC,Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等。
Redis 提供主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务。

5.什么是Redis数据持久?方式都有哪些,各自的优缺点是什么?

1.redis持久化就是把内存的数据写到磁盘中去,防止服务器宕机了内存数据丢失。
2.数据持久化的方式有2种: RDB(默认) 和 AOF 。

RDB (redis database)
核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数
Redis常问面试题整理
AOF (append-only file)
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作
AOF 写入保存。

存储的内容是 redis通讯协议格式命令的命令文本存储。点击立即了解 —> https://www.cnblogs.com/nele/p/8908298.html

WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
Redis常问面试题整理

区别:
1、AOF 文件比 RDB 更新频率高,优先使用 AOF 还原数据。
2、AOF 比 RDB 更安全也更大
3、RDB 性能比 AOF 好
4、如果两个都配了优先加载AOF

6.说说 Redis 的淘汰策略?

Redis提供了6中淘汰策略。

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据

使用策略规则:
1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

7. 什么是缓存穿透,缓存击穿,缓存雪崩?

缓存穿透
描述: 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案:

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

缓存击穿

描述:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方案:

设置热点数据永远不过期。
加互斥锁,互斥锁参考代码如下:
Redis常问面试题整理
1)缓存中有数据,直接走上述代码13行后就返回结果了

2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。

3)当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据,上面代码明显做不到这点。

缓存雪崩

描述: 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
设置热点数据永远不过期。

8. Redis 架构模式有哪些?讲讲各自的特点?

Redis常问面试题整理
Redis常问面试题整理
Redis常问面试题整理
Redis常问面试题整理

9. redis 集群方案都有哪些?

1.twemproxy,大概概念是,它类似于一个代理方式, 使用时在本需要连接 redis 的地方改为连接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。
缺点: twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。

2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点

3.redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。

10. 在Redis中如何保证只存20W的热点数据?

先计算出20W数据需要占用数据的空间,然后设置数据淘汰策略为 allkey-lru 回收最少使用策略。

11.Redis支持的Java客户端都有哪些?官方推荐使用哪个?

Redisson、Jedis、lettuce等等,官方推荐使用Redisson。

12.Redis和Redisson有什么关系?

Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象
(Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。

13.Jedis与Redisson对比有什么优缺点?

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;

Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。
Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

14.Redis支持事务吗?

支持 , 表现为多条命令,要么都执行,要么都不执行。

redis的事务可以分为两步,定义事务和执行事务。
使用multi命令开启一个事务,然后把要执行的所有命令都依次排上去。
这就定义好了一个事务。此时使用exec命令来执行这个事务,或使用discard命令来放弃这个事务。
你可能希望在你的事务开始前,你关心的key不想被别人操作,那么可以使用watch命令来监视这些key,
如果开始执行前这些key被其它命令操作了则会取消事务的。也可以使用unwatch命令来取消对这些key的监视。

redis事务具有以下特点:
1、如果开始执行事务前出错,则所有命令都不执行
2、一旦开始,则保证所有命令一次性按顺序执行完而不被打断
3、如果执行过程中遇到错误,会继续执行下去,不会停止的
4、对于执行过程中遇到错误,是不会进行回滚的

很显然,这并不是我们通常认为的事务,因为它连原子性都保证不了。保证不了原子性是因为redis不支持回滚,不过它也给出了不支持的理由。

不支持回滚的理由:
1、redis认为,失败都是由命令使用不当造成
2、redis这样做,是为了保持内部实现简单快速
3、redis还认为,回滚并不能解决所有问题
因此 , 使用redis事务的不太多

15. Redis分布式锁如何续期?

https://zhuanlan.zhihu.com/p/71185118

16.Redis分布式锁如何防止死锁

方法1 , 编写2个方法一个加锁,一个解锁

private static final String LOCK_SUCCESS = "OK";
   private static final String SET_IF_NOT_EXIST = "NX";
   private static final String SET_WITH_EXPIRE_TIME = "PX";

   /**
    * 尝试获取分布式锁
    * @param jedis Redis客户端
    * @param lockKey 锁
    * @param requestId 请求标识
    * @param expireTime 超期时间
    * @return 是否获取成功
    */
   public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
           return true;
        }
        return false;
     }

可以看到,我们加锁就一行代码:jedis.set(String key, String value, String nxxx, String expx, int time),这个set()方法一共有五个形参:

  • 第一个为key,我们使用key来当锁,因为key是唯一的。
  • 第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
  • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
  • 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
  • 第五个为time,与第四个参数相呼应,代表key的过期时间。
  • 总的来说,执行上面的set()方法就只会导致两种结果:

当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。
已有锁存在,不做任何操作。

解锁代码

private static final Long RELEASE_SUCCESS = 1L;
    /**
    * 释放分布式锁
    * @param jedis Redis客户端
    * @param lockKey 锁
    * @param requestId 请求标识
    * @return 是否释放成功
    */
   public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
       String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
       Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
       if (RELEASE_SUCCESS.equals(result)) {
           return true;
       }
       return false;
    }

可以看到,我们解锁只需要两行代码就搞定了!第一行代码,我们写了一个简单的Lua脚本代码,没想到这次居然用上了。
第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。
eval()方法是将Lua代码交给Redis服务端执行

Redis常用命令及介绍

字符串:
setnx(key,value) 只在键 key 不存在的情况下, 将键 key 的值设置为 value 。key存在,不做任何操作。
setex(key,seconds,value) 将key设置及生存时间seconds秒,原值存在覆盖。
psetex(key,milliseconds,value) 与setex同样,只是单位是毫秒。
getset(key,value) 设置新值并返回旧值,不存在返回nil
setrange(key,offset,value) 从偏移量开始offset开始
mset 同时给多个key复制

哈希表(map):
hset(hash field value) 将哈希表 hash 中域 field 的值设置为 value
hmset key field value [field value …] 同时将多个 field-value (域-值)对设置到哈希表 key 中。
hget hash field 返回哈希表中给定域的值。
hgetall key 返回哈希表 key 中,所有的域和值。

队列(queue):
lpush key value [value …] 将一个或多个值 value 插入到列表 key 的表头
lpop key 移除并返回列表 key 的头元素,不存在返回nil
lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。
brpop key [key …] timeout 在超时时间内移除列表尾元素,阻塞的。

集合:
sadd key member [member …] 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略
sismember key member 如果 member 元素是集合的成员,返回 1 。 如果 member 元素不是集合的成员,或 key 不存在,返回 0 。
spop key 移除集合key的随机元素
smembers key 返回集合 key 中的所有成员。
sdiff key [key …] 返回给定多个集合之间的差集。

有序集合:
zadd key score member [[score member] [score member] …] 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
zscore key member 返回有序集 key 中,成员 member 的 score 值。
zcount key min max score 值在 min 和 max 之间的成员的数量。
zrange key start stop [withscores] 返回有序集 key 中,指定区间内的成员(从小到大)。
zrank key member 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
zrem key member [member …] 移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。

时效性:
expire(key,seconds) 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
expireat( key,timestamp) 设置过期时间戳,expireatcache1355292000# 这个 key 将在 2012.12.12 过期
ttl(key) 返回剩余时间
persist key 移除key有效期,转换成永久的

数据指令:
keys pattern 符合给定模式的 key 列表。阻塞的
scan 异步的 有重复

相关标签: Java 博文