面试题一:有序集合在日常工作中的使用场景有哪些?
有序集合在工作中的应用场景有很多,例如“
排行榜:可以将用户的得分作为有序集合的分支,用户的ID作为成员,通过有序集合的排名功能可以得到用户的排名信息。例如:
zadd hot 1e aid 1 99 aid 2 95 aid 3 9 aid 4
用户关注集合:可以将用户的关注数作为有序集合的分值,用户ID作为成员,通过分值的排序可以得到关注数最多的用户。例如:
zadd user_1_friends 17 zhangsan 18 lisi
商品价格排序:可以将商品价格作为有序集合的分值,商品ID作为成员,通过有序集合的排序功能可以得到价格从高到低的商品列表
zadd prize 10 apple 20 banana
面试题二:使用Redis实现分布式锁存在什么问题?如何解决这些问题?
默认情况,使用setnx lock true实现分布式锁会存在以下问题:
- 死锁问题:setnx如果未设置过期时间,并且锁忘记删除或加锁线程当即都会导致死锁,也就是分布式锁一直被占用的情况
- 锁误删问题:setnx设置了过期时间,达到了过期时间,锁被其他线程占用,但是上一次线程又进行unlock操作,导致解锁别人的锁
- 不可重入问题:也就是说同一个线程在已经获取某个锁的情况下,如果再次请求获取该锁,则会请求失败(因为只有第一次加锁会成功)。也就是说,一个线程不能对自己持有的锁进行重复锁定
- 无法自动续期:线程持有锁的时间到了,但任务未完成,锁会因为超时自动释放。setnx无法根据任务执行情况,设置新的超时时间,以延长锁的时间
解决方法:
解决死锁问题:设置超时时间
set lock true ex 10 nx
解决锁误删问题:锁误删可以通过将锁标识存储到 Redis 中来解决,删除之前先判断锁归属(也就是将线程 id 存储到分布式的value 值内,删除之前先判断锁 value 值是否等于当前线程 id),如果属于你的锁再删除,否则不删除就可以,这就解决了锁误删的问题
通用解决方案:以上问题有一个通用的解决方案,那就是使用 Redisson 架来实现 Redis 分布式锁,这样可以解决死锁问题,也可以解决锁误删、不可重入和无法自动续期的问题了
面试题三:说一下什么是Redisson?使用它如何实现分布式锁?它实现的分布式锁有什么优点?
Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格客户端(In-Memory Data Grid)。它不仅提供了一系列的 redis 常用数据结构命令服务,还提供了许多分布式服务,例如分布式锁、分布式对象、分布式集合、分布式远程服务、分布式调度任务服务等等。
使用 Redisson 实现分布式锁的步骤如下:
- 获取需要加锁的资源:`RLock lock = redisson.getLock("lockKey");`
- 加锁,并且设置锁过期时间,防止死锁的产生:`lock.lock();`
- 执行具体业务逻辑
- 释放锁:`lock.unlock();`
Redisson 实现的分布式锁有以下优点:
- 多个 redis 操作乱脚本整体提交,保证性能同时,保证整体原子性。
- 看门狗自动延续锁生命周期,防止未处理完锁过期问题,但是同时造成了阻塞,甚至锁死。
- 提供了使用 Redis 的最简单和最便捷的方法,使得使用者能够将精力更集中地放在处理业务逻辑上。
- 在分布式锁的基础上还提供了联锁(MultiLock),读写锁(ReadWriteLock),公平锁(Fair Lock),红锁(RedLock),信号量(Semaphore),可过期性信号量(PermitExpirableSemaphore)和闭锁(CountDownLatch)等,这些都是实际当中对多线程高并发应用至关重要的基本部件。
- 通过实现基于 Redis 的高阶应用方案,使 Redisson 成为构建分布式系统的重要工具。
面试题四:说一下Redisson中的看门狗机制?
Redisson 中的看门狗机制是一种自动延长锁有效期的机制。当一个线程获取了一个锁,但是还没有执行完毕,看门狗会自动帮助线程延长锁的超时时间,防止锁因为超时而被释放
具体来说,Redisson 的看门狗机制工作流程如下:
-
当一个线程成功获取锁后,如果没有指定锁的租赁时间(lease time),那么看门狗会启动一个定时任务,每隔 lockWatchdogTimeout / 3 的时间,就会自动将锁的有效期延长到 lockWatchdogTimeout。
-
如果线程在执行过程中一直没有释放锁,那么看门狗会一直帮助线程续期,直到线程执行完毕并释放锁。
-
如果线程在获取锁时指定了租赁时间,那么看门狗就不会启动,锁会在租赁时间结束后自动释放。
这种机制可以有效防止因线程执行时间过长而导致的锁提前释放,同时也避免了因线程突然宕机导致的死锁。默认情况下,看门狗的续期时间是 30 秒,但是可以通过修改 Config.lockWatchdogTimeout 来另行指定
面试题五:什么是RedLock吗?它有什么优缺点?推荐使用RedLock吗?为什么?
RedLock 是一种基于 Redis 实现的分布式锁算法。它的主要优点和缺点,以及是否推荐使用,如下:
优点:
-
互斥性:在任何时候,只能有一个客户端能够持有
-
避免死锁:当客户端拿到锁后,即使客户端崩溃或者因为其他原因无法释放锁,锁也会因为设置了失效时间而自动被释放
-
高可靠性:相比于普通的基于单个 Redis 实例的分布式锁,RedLock 算法在多个 Redis 实例之间实现分布式锁,从而提高了锁的可靠性
缺点:
-
时钟偏移:RedLock 算法依赖于各个 Redis 节点的系统时钟是准确同步的,如果节点之间的时钟存在较大的偏移,可能导致锁的获取和释放出现问题
-
资源需求:需要部署多台 Redis 机器
-
极端情况下的问题:在极端情况下,可能会出现两个线程同时获得锁的情况
关于是否推荐使用 RedLock,这取决于具体的使用场景。RedLock 提供了一种在多个 Redis 实例之间实现分布式锁的可靠方法。然而,如果你的应用不需要一个高度可靠的分布式锁,或者你的 Redis 环境只有一个实例,那么使用 RedLock 可能就没有必要了。此外,还需要考虑到 RedLock 的资源需求和可能的时钟偏移问题。总的来说,如果你的应用需要一个高度可靠的分布式锁,并且你能够管理多个 Redis 实例,那么 RedLock 是一个值得考虑的选项。但是,有一些观点认为 Redisson 的 RedLock 实现并不推荐使用,因此在选择使用 RedLock 之前,建议详细研究其可能的问题和限制
面试题六:什么是布隆过滤器?它有什么特点?说一下它的底层实现原理?
布隆过滤器(Bloom Filter)是一种数据结构,它实际上是由一个很长的二进制向量(位数组)和一系列随机映射函数(哈希函数)组成
特点:
高效地插入和查询:布隆过滤器的增加和查询元素的时间复杂度为O(K),其中K为哈希函数的个数
占用内存小:布隆过滤器的空间复杂度为O(m),不会随着元素增加而增加
有一定的误识别率:布隆过滤器返回的结果是概率性的,而不是非常准确的理论情况下添加到集合中的元素越多,误报的可能性就越大
删除困难:存放在布隆过滤器的数据不容易删除
底层实现原理:
当一个元素加入布隆过滤器中的时候,会使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。根据得到的哈希值,在位数组中把对应下标的值置为1
当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:对给定元素再次进行相同的哈希计算;得到值之后判断位数组中的每个元素是否都为1,如果值都为1,那么说明这个值在布隆过滤器中,如果存在一个值不为1,说明该元素不在布隆过滤器中
布隆过滤器的底层实现原理是基于位数组和哈希函数的,因此,它的底层实现原理也可以看作是位数组和哈希函数的应用
面试题七:在Redis中如何实现布隆过滤器?
在 Redis 中实现布隆过滤器主要有以下两种方式:
-
使用 Bitmaps:Redis 提供了一套 Bitmaps 命令,可以用来操作位数组,这是布隆过滤器的基础。例如,setbit 命令可以用来设置位数组中的某一位,getbit 命令可以用来获取位数组中的某一位,bitcount 命令可以用来获取位数组中值为 1 的位的个数
-
使用 Redisson:Redisson 是一个在 Java 程序中操作 Redis 的库,它提供了布隆过滤器的实现。例如,可以使用 redisson.getBloomFilter("filterName") 来获取一个布隆过滤器
具体的实现步骤如下:
-
首先,需要在 Redis 中创建一个位数组,这可以通过 Redis 的 setbit 命令来实现
-
然后,当需要添加一个元素到布隆过滤器时,可以通过多个哈希函数,将元素哈希到位数组的不同位置,然后将这些位置的值设置为 1
-
当需要查询一个元素是否存在于布隆过滤器时,可以通过相同的哈希函数,将元素哈希到位数组的相同位置,然后检查这些位置的值是否都为 1。如果都为 1,则认为元素可能存在于布隆过滤器;如果有任何一个位置的值为 0,则认为元素一定不存在于布隆过滤器
面试题八:除了Redis还有其他实现布隆过滤器的手段吗?它们和Redis有什么区别?
除了Redis,还有其他一些方法可以实现布隆过滤器:
-
使用Bitmaps:Bitmaps是一种可以用来操作位数组的数据结构,这是布隆过滤器的基础。例如,setbit命令可以用来设置位数组中的某一位,getbit命令可以用来获取位数组中的某一位,bitcount命令可以用来获取位数组中值为1的位的个数
-
使用Java库Redisson:Redisson是一个在Java程序中操作Redis的库,它提供了布隆过滤器的实现。例如,可以使用redisson.getBloomFilter("filterName")来获取一个布隆过滤器
-
使用Guava库:Guava是Google提供的一个Java库,它也提供了布隆过滤器的实现
这些方法和Redis实现布隆过滤器的主要区别在于底层实现和使用的技术栈。例如,Redis使用的是内存数据存储和处理,而Guava则是在Java应用程序的JVM内存中实现布隆过滤器。此外,Redis提供的布隆过滤器可以在分布式环境中使用,而Guava提供的布隆过滤器则主要用于单机环境
面试题九:Redis中存储的数据会丢失吗?为什么?
Redis存储的数据不会丢失,保证数据不丢失的手段主要有以下两个
- 持久化:持久化是指将内存中的数据保存到硬盘上,以防止在服务器重启、宕机等意外情况下导致数据丢失由于 Redis 是一个基于内存的数据库系统,默认情况下所有的数据都存储在内存中,这意味着一旦服务进程终止或硬件故障,内存中的所有数据都将消失。而 Redis 提供了 3 种持久化的手段,以保证 Redis 可以将内存中的数据保存到磁盘上,这样无论是服务器重启,还是宕机,掉电等问题,Redis 的数据都不会丢失
- 多机部署:多机部署是指在多个独立的服务器或虚拟机上部署和运行 Redis 服务实例,以实现数据几余、高可2 .用性、可扩展性和负载均衡等目标。Redis 多机运行部署的实现总共有三种:主从模式、哨兵模式和集群模式
面试题十:Redis会怎么处理过期之后的键值对?它为什么要这样设计?
Redis 中过期的键值对不会立即删除,而是使用以下手段来删除过期键的
惰性删除(Lazy Expire): Redis 不会主动地、周期性地检查和除所有过期的键。惰性删除是指在 Redis 访问某个键值时,才会检查该键是否已经过期,如果已过期,则返回 NULL,并同时删除它
定期删除(Periodic Expire): 每隔一段时间检查一次数据库,随机删除一些过期键。定期删除在2redis.conf 配置文件中配置,如下图所示:
hz 等于 10 表示每秒钟删除 10 次,也就是每 100 毫秒执行一次定期删除
之所以要采用这两种方式来删除是为了保证清除过期数据,不影响 Redis 整体的执行效率