Redis如何实现异步消息队列?
List配合LPUSH和RPOP。
另外就是用 Redis 的Pub/Sub来实现简单的消息广播和订阅。
但是这两种方式都是不可靠的,因为没有 ACK 机制所以不能保证订阅者一定能收到消息,也不支持消息持久化。
Redis如何实现延时消息队列?
延时消息队列在实际业务中很常见,比如订单超时取消、定时提醒等场景。
可以使用ZSet有序集合,将消息作为 member,把消息的需要执行时的时间戳作为 score。
这样消息就会按照执行时间自动排序,消费者只需要定期扫描当前时间之前的消息进行处理就可以了。
具体来讲,生产者向ZSet中发送消息时计算其应该执行的时间戳,然后通过ZADD命令将消息和时间戳添加到Zset;消费者用ZREMRANGEBYSCORE命令获取当前时间戳之前的消息进行处理。
Redis支持事务吗?
Redis支持简单的事务,用MULTI开启事务,将一系列命令放入执行队列,中间可以用DISCARD取消事务队列,放弃事务执行,最后用EXEC按照先进先出的顺序执行队列里的命令。
WATCH命令用于监视一个或多个key,如果这个key在事务最后执行之前被其它命令改动,那么事务将会被放弃执行
不支持回滚,只有执行和不执行的区别,也不支持多种隔离级别
说一下Redis事务的原理?
MULTI会将客户端打一个事务的标记,表示先把命令存到队列里,EXEC的时候再看情况执行(如果中间有DISCARD或者WATCH监控的key被其它命令修改则放弃执行事务),命令队列按照FIFO执行,由于Redis是单线程的,天然支持原子性,不会被其它命令打断
Redis事务为什么不支持回滚?
没有类似于MySQL那样的undo log机制,设计理念是简单高效而不是完整的ACID特性,引入回滚机制会增加复杂性和性能开销
Redis事务满足原子性吗?要怎么改进?
Redis命令执行的实际过程中虽然不会被其它命令打断,但是如果执行的过程中有报错,后续的命令仍然会执行,事务不会被回滚,不符合传统的原子性的定义。
使用Lua脚本代替事务实现报错时回滚,可以在脚本中处理整个业务逻辑,包括条件检查和错误处理,保证要么执行成功,要么回滚到初始状态。
Redis事务的ACID特性如何实现?
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务在执行过程中如果某个命令失败了,其他命令还是会继续执行,不会回滚。
一致性指的是,如果数据在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据也应该是一致的。但 Redis 事务并不保证一致性,因为如果事务中的某个命令失败了,其他命令仍然会执行,就会出现数据不一致的情况。
Redis 是单线程执行事务的,并且不会中断,直到执行完所有事务队列中的命令为止。因此,我认为 Redis 的事务具有隔离性的特征。
Redis 事务的持久性完全依赖于 Redis 本身的持久化机制,如果开启了 AOF,那么事务中的命令会作为一个整体记录到 AOF 文件中,当然也要看 AOF 的 fsync 策略。
如果只开启了 RDB,事务中的命令可能会在下次快照前丢失。如果两个都没有开启,肯定是不满足持久性的。
有Lua脚本操作Redis的经验吗?
用Lua脚本实现过分布式锁,因为发现就算一个线程成功获取了锁,在设置过期时间的时候如果进程崩溃,由于Redis本身没有回滚机制,这个锁将永远不会被释放造成死锁。其次,Lua脚本可以实现只有加锁者才能释放锁的逻辑。
java
// 解锁脚本特别重要,必须验证是自己的锁才能删
private final String UNLOCK_SCRIPT =
"if redis.call('GET', KEYS[1]) == ARGV[1] then " +
" return redis.call('DEL', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Redis的管道Pipeline了解吗?
Pipeline允许客户端一次性向Redis服务器发送多个命令,不必等待一个命令执行完毕后再发送下一个。Redis服务器按照FIFO的顺序执行命令,将所有结果打包发回客户端。
什么场景下适合使用Pipeline?
需要批量插入、删除、更新数据时,或者需要执行大量相似命令的时候,比如批量加载热点数据,统计数据的批量更新、大批量数据的导入导出、批量删除过期或无效的缓存
了解过Pipeline的底层原理吗?
底层是缓冲的思想,我在RedisClient类中封装了一个PipelineAction 内部类,用来缓存命令。
PipelineAction的add方法将命令封装成Runnable对象,放入List中。execute方法调用RedisTemplate的executePipeline方法开启管道模式将多个命令发送给Redis客户端。
Redis从输入缓冲区中读取到命令后,依次执行这些命令,将执行结果写入输出缓冲区,最后再将结果一次性打包返回给客户端。
Redis分布式锁如何解决锁过期问题
具体场景是,A获取到了分布式锁并设置了过期时间,但是由于执行的时间过长,任务还没执行完毕锁就被释放了,其他线程就获取到了锁,但此时如果A执行完毕,释放掉的其实是B的锁。
可以通过锁的自动续期机制来解决锁过期的问题,比如Redission的看门狗机制,后台启动一个定时任务,隔一段时间就检查锁是否还被当前线程持有,如果是则延迟锁的持有时间,这样避免了锁被提前释放
Redisson了解多少?
Redission是基于Redis的java客户端,它不只是对Redis进行简单封装,还提供了很多分布式的数据结构和服务,比如最常用的分布式锁。
Redisson 的分布式锁比 SETNX 完善的得多,它的看门狗机制可以让我们在获取锁的时候省去手动设置过期时间的步骤,它在内部封装了一个定时任务,每隔 10 秒会检查一次,如果当前线程还持有锁就自动续期 30 秒。
另外,Redisson 还提供了分布式限流器 RRateLimiter,基于令牌桶算法实现,用于控制分布式环境下的访问频率。
详细说说Redission的看门狗机制?
Redisson 的看门狗机制是一种自动续期机制,用于解决分布式锁的过期问题。
基本原理是这样的:当调用 lock()
方法加锁时,如果没有显式设置过期时间,Redisson 会默认给锁加一个30 秒的过期时间,同时启用一个名为"看门狗"的定时任务,每隔 10 秒(默认是过期时间的 1/3),去检查一次锁是否还被当前线程持有,如果是,就自动续期,将过期时间延长到 30 秒。
续期时,RedissionLua 脚本 会检查锁的 value 是否匹配当前线程,如果匹配就延长过期时间。这样就能保证只有锁的真正持有者才能续期。
当调用unlock()时,看门狗任务结束,但是如果执行完任务忘记unlock了,看门狗也会自动检查锁,如果当前线程已经退出了或者锁已经不属于当前线程了,也会停止自动续期
看门狗机制中的锁检查和续期过程是原子操作吗?
在Redis层面是原子的,通过Lua脚本实现原子的批量操作,不会被其他命令打断,hexists命令和expire操作放在同一个Lua脚本中,是不可分割的,是原子的,要么都成功,要么回滚,确保续期的一定是当前线程持有的锁
RedLock你了解多少?
是一种分布式锁算法,用于解决单个Redis实例用作分布式锁的时候存在的单点故障问题。
通过在多个完全独立的Redis实例上同时获取锁来实现容错,只要成功的实例数量超过一半就认为获取锁成功。
RedLock能不能保证百分百上锁?
不能,由于网络分区,因为客户端不能保证和集群中所有的Redis实例建立连接,比如集群中有五个节点,但是客户端只能和两个建立连接,永远也无法成功获取半数以上的锁,从而一定失败。
如果各个 Redis 实例之间存在明显的时钟漂移,或者客户端在获取锁的过程中耗时过长,比如网络延迟、GC 停顿等,都可能会导致锁在获取完成前就过期,从而获取失败。
项目中有用到Redis分布式锁吗?
有使用到Redis分布式锁防止用户多端登陆同时大量调用人脸识别攻击操作绕过阈值检查