Redis的Key和Value的设计原则有哪些?

典型回答 在设计 Redis 的 Key 和 Value 时,需要考虑一些原则,以确保数据存储和检索的效率,以及满足特定用例的需求。以下是一些设计 Redis Key 和 Value 的原则: Key 的设计原则 可读性:一个Key应该具有比较好的可读性,让人能看得懂是什么意思,而不是含糊不清。key 名称以 key 所代表的 value 类型结尾,以提高可读性。例如:user:basic.info:userid:string。 简洁性:Key 应该保持简洁,避免过长的命名,以节省内存和提高性能。一个好的做法是使用短、有意义的 Key,但也不要过于简单以避免与其他 Key 冲突。 避免特殊字符:避免在 Key 中使用特殊字符,以确保 Key 的可读性和可操作性。命名中尽量只包含:大小写字母、数字、竖线、下划线、英文点号(.)和英文半角冒号(:)。 命名空间:使用命名空间来区分不同部分的 Key。例如,可以为用户数据使用 "user:" 前缀,为缓存数据使用 "cache:" 前缀。 长度限制:避免在 Key 的长度过长,会占用内存空间。 Value 的设计原则 数据类型选择:根据数据的特性选择合适的数据格式。Redis 支持字符串、列表、哈希、集合和有序集合等多种数据类型,选择合适的数据格式可以提高操作效率。 避免大Key:如果Value很大,那么对应的Key就称之为大Key,大Key会带来很多问题应该尽量避免。可以尝试将大数据分割为多个小 Value,以提高性能和降低内存使用。 ✅什么是大Key问题,如何解决? 过期时间:为 Value 设置适当的过期时间以自动清理不再需要的数据,以减少内存占用。 压缩:如果数据具有可压缩性,可以在存储之前进行压缩,以减少内存使用。 合理控制和使用数据结构内存编码优化配置:例如 ziplist 是一种特殊的数据结构,它可以将小型列表、哈希表和有序集合存储在一个连续的内存块中,从而节省了内存空间。但由于 ziplist 没有索引,因此在对 ziplist 进行查找、插入或删除操作时,需要进行线性扫描,这可能会导致性能下降。在实际应用中,应该根据具体情况来决定是否使用 ziplist。如果数据量较小且需要频繁进行遍历操作,那么使用 ziplist 可能是一个不错的选择。但是,如果数据量较大且需要频繁进行插入、删除或查找操作,那么使用 ziplist 可能会影响性能,应该考虑使用其他数据结构来代替。(本条来自腾讯云数据库规范)

March 22, 2026 · 1 min · santu

Redisson的lock和tryLock有什么区别?

典型回答 当我们在使用Redisson实现分布式锁的时候,会经常用到lock和tryLock两个方法,那么他们有啥区别吗? 先来看看代码中是如何解释这两个方法的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /** * Tries to acquire the lock with defined <code>leaseTime</code>. * Waits up to defined <code>waitTime</code> if necessary until the lock became available. * * Lock will be released automatically after defined <code>leaseTime</code> interval. * * @param waitTime the maximum time to acquire the lock * @param leaseTime lease time * @param unit time unit * @return <code>true</code> if lock is successfully acquired, * otherwise <code>false</code> if lock is already set. * @throws InterruptedException - if the thread is interrupted */ boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException; /** * Acquires the lock with defined <code>leaseTime</code>. * Waits if necessary until lock became available. * * Lock will be released automatically after defined <code>leaseTime</code> interval. * * @param leaseTime the maximum time to hold the lock after it's acquisition, * if it hasn't already been released by invoking <code>unlock</code>. * If leaseTime is -1, hold the lock until explicitly unlocked. * @param unit the time unit * */ void lock(long leaseTime, TimeUnit unit); 这两个方法声明和注释上,主要由以下3个区别: ...

March 22, 2026 · 2 min · santu

Redis的事务和Lua之间有哪些区别?

典型回答 Redis中,事务和Lua都是保证原子性的手段,当我们有多个命令要执行,希望他们以原子性方式执行的时候,就会考虑使用事务或者Lua脚本,那么他们之间有哪些区别呢?(看了很多资料,包括redis官网文档,几乎都没有提,以下内容是我基于自己的理解整理的,欢迎大家补充) 原子性保证 事务和Lua都是可以保证原子性操作的,但是,这里说的原子性我们提过很多次,指的是不可拆分,不可中断的原子性操作。所以,需要注意的是,不管是Redis的事务还是Lua,都没办法回滚,一旦执行过程中有命令失败了,都是不支持回滚的。 但是,Redis的事务在执行过程中,如果有某一个命令失败了,是不影响后续命令的执行的,而Lua脚本中,如果执行过程中某个命令执行失败了,是会影响后续命令执行的。 ✅为什么Lua脚本可以保证原子性? ✅为什么Redis不支持回滚? 交互次数 在Redis的事务执行时,每一条命令都需要和Redis服务器进行一次交互,我们可以在Redis事务过程中,MULTI 和 EXEC 之间发送多个 Redis 命令给到Redis服务器,这些命令会被服务器缓存起来,但并不会立即执行。但是每一条命令的提交都需要进行一次网络交互。 而Lua脚本则不需要,只需要一次性的把整个脚本提交给Redis即可。网络交互比事务要少。 前后依赖 在 Redis 的事务中,事务内的命令都是独立执行的,并且在没有执行EXEC命令之前,命令是没有被真正执行的,所以后续命令是不会也不能依赖于前一个命令的结果的。 而在Lua 脚本中是可以依赖前一个命令的结果的,Lua 脚本中的多个命令是依次执行的,我们可以利用前一个命令的结果进行后续的处理。 流程编排 借助Lua脚本,我们可以实现非常丰富的各种分支流程控制,以及各种运算相关操作。而Redis的事务本身是不支持这些操作的。

March 22, 2026 · 1 min · santu

watchdog一直续期,那客户端挂了怎么办?

典型回答 ✅Redisson的watch dog机制是怎么样的? 上一篇文章中介绍了关于看门狗的机制,他会在用户没有指定锁的超时时间的时候,给锁做续期,他会有一个后台线程,定时的给锁续期。 因为watch dog的续期,是没有次数和时长的限制的,也就是说,如果没有主动解锁,那么他就会一直续期下去。 那么,就会有人提出这样的问题: 1、一直续期,别人不是拿不到锁了吗? 2、一旦客户端挂了但是锁还没释放怎么办? 3、如果解锁失败了,怎么办? 一直续期,别人不是拿不到锁了吗? 上面这两个问题你可能不知道答案,那下面这几个你一定知道答案: 分布式锁的目的是什么?防止并发。 锁续期的条件是什么?还没解锁。 什么情况下会没解锁?任务没执行完。 那么,如果一个任务没执行完,我就一直给他续期,让他不断地延长锁时长,防止并发,有毛病吗?没有啊! 如果你就是不想一直续期,那你就自己指定一个超时时间就行了。就不要用他的续期机制就好了。 一旦客户端挂了但是锁还没释放怎么办? 如果,应用集群中的一台机器,拿到了分布式锁,但是在执行的过程中,他挂了,还没来得及把锁释放,那么会有问题么? 因为我们知道,锁的续期是Redisson实现的,而Redisson的后台任务是基于JVM运行的,也就是说,如果这台机器挂了,那么Redisson的后台任务也就没办法继续执行了。 那么他也就不会会再继续续期了,那么到了期限之后,锁就会自动解除了。这样就可以避免因为一个实例宕机导致分布式锁的不可用。 如果解锁失败了,怎么办? ✅watchdog解锁失败,会不会导致一起续期下去?

March 22, 2026 · 1 min · santu

如何用Redis实现乐观锁?

典型回答 所谓乐观锁,其实就是基于CAS的机制,CAS的本质是Compare And Swap,就是需要知道一个key在修改前的值,去进行比较。 在Redis中,想要实现这个功能,我们可以依赖 WATCH 命令。这个命令一旦运行,他会确保只有在 WATCH 监视的键在调用 EXEC 之前没有改变时,后续的事务才会执行。 例如,如果没有 INCRBY,我们可以用下面的方式实现原子的增量操作: 1 2 3 4 5 WATCH counter GET counter MULTI SET counter <从 GET 获得的值 + 任何增量> EXEC WATCH:使用 WATCH 命令监视一个或多个键。这个命令会监视给定键直到事务开始(即执行 MULTI 命令)。 GET:在事务开始之前,查询你需要的数据。 MULTI:使用 MULTI 命令开始事务。 SET:在事务中添加所有需要执行的命令。 EXEC:使用 EXEC 命令执行事务。如果自从事务开始以来监视的键被修改过,EXEC 将返回 nil,这表示事务中的命令没有被执行。 通过这种方式,Redis 保证了只有在监视的数据自事务开始以来没有改变的情况下,事务才会执行,从而实现了乐观锁定。 以下,是在Java中,用Jedis实现的代码,和上述流程是一样的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; public class RedisOptimisticLock { public static void main(String[] args) { // 连接到 Redis Jedis jedis = new Jedis("localhost"); try { // 监视键 String key = "myKey"; jedis.watch(key); // 模拟从数据库读取最新值 String value = jedis.get(key); int intValue = Integer.parseInt(value); // 开始事务 Transaction t = jedis.multi(); // 在事务中执行操作 t.set(key, String.valueOf(intValue + 1)); // 尝试执行事务 if (t.exec() == null) { System.out.println("事务执行失败,数据已被其他客户端修改"); } else { System.out.println("事务执行成功"); } } catch (Exception e) { e.printStackTrace(); } finally { jedis.close(); } } }

March 22, 2026 · 1 min · santu

如何用setnx实现一个可重入锁?

典型回答 可重入锁是一种多线程同步机制,允许同一线程多次获取同一个锁而不会导致死锁。 ✅什么是可重入锁,怎么实现可重入锁? 在Redis中,最简单的方式就是使用setnx来实现一个分布式锁了,但是如果我想要实现一个具有重入功能的锁,那么用setnx如何实现呢? 首先,我们需要有一个标识来识别出一个线程,这里可以是线程ID,分布式的traceId(https://www.yuque.com/hollis666/ec96i7/nnl88aqknhx2v76c ),或者是一个唯一的业务ID都可以。 有了这个唯一标识之后,我们加锁的时候,就可以用这个标识来判断当前持有锁的线程是不是自己,如果是的话,就可以直接重入。否则就无法重入。 为了保证重入几次之后,需要同时解锁几次,那么我们也需要维护一个重入次数的字段。因为每一次重入其实就是一个加锁动作,避免出现加锁2次,但是1次解锁动作就把锁给解了的情况。 有了以上基础之后,加锁和解锁的逻辑如下: 加锁的逻辑: - 当线程尝试获取锁时,它首先检查锁是否已经存在。 - 如果锁不存在(即 SETNX 返回成功),线程设置锁,存储自己的标识符和计数器(初始化为1)。 - 如果锁已存在,线程检查锁中的标识符是否与自己的相同。 * 如果是,线程已经持有锁,只需增加计数器的值。 * 如果不是,获取锁失败,因为锁已被其他线程持有。 解锁的逻辑: - 当线程释放锁时,它会减少计数器的值。 - 如果计数器降至0,这意味着线程已完成对锁的所有获取请求,可以完全释放锁。 - 如果计数器大于0,锁仍被视为被该线程持有。 代码实现如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import redis.clients.jedis.Jedis; public class ReentrantRedisLock { public synchronized boolean tryLock(Jedis jedis,String lockKey) { String currentThreadId = String.valueOf(Thread.currentThread().getId()); // 尝试获取锁 String lockValue = jedis.get(lockKey); if (lockValue == null) { // 锁不存在,尝试设置锁 return "OK".equals(jedis.set(lockKey, currentThreadId + ":1", "NX", "EX", 30)); } // 锁存在,检查是否由当前线程持有 String[] parts = lockValue.split(":"); //加锁线程是当前线程,则增加次数,进行重入加锁 if (parts.length == 2 && parts[0].equals(currentThreadId)) { int count = Integer.parseInt(parts[1]) + 1; return "OK".equals(jedis.set(lockKey, currentThreadId + ":" + count, "XX", "EX", 30)); } //加锁失败 return false; } public synchronized void unlock(Jedis jedis,String lockKey) { String currentThreadId = String.valueOf(Thread.currentThread().getId()); String lockValue = jedis.get(lockKey); if (lockValue != null) { String[] parts = lockValue.split(":"); if (parts.length == 2 && parts[0].equals(currentThreadId)) { int count = Integer.parseInt(parts[1]); //减少重入次数 if (count > 1) { jedis.set(lockKey, currentThreadId + ":" + (count - 1), "XX", "EX", 30); } else { //解锁 jedis.del(lockKey); } } } } } 在这个实现中,锁的值是一个由线程 ID 和锁的获取次数组成的字符串,格式为 线程ID:次数。当一个线程尝试获取锁时,它会检查当前的锁值。 ...

March 22, 2026 · 3 min · santu

Redisson解锁失败,watchdog会不会一直续期下去?

典型回答 不会的,因为在解锁过程中,不管是解锁失败了,还是解锁时抛了异常,都还是会把本地的续期任务停止,避免下次续期。 具体实现如下: 1 2 3 4 5 6 7 8 9 10 11 12 @Override public void unlock() { try { get(unlockAsync(Thread.currentThread().getId())); } catch (RedisException e) { if (e.getCause() instanceof IllegalMonitorStateException) { throw (IllegalMonitorStateException) e.getCause(); } else { throw e; } } } 这是redisson中解锁方法的入口,这里调用了unlockAsync方法,传入了当前线程的ID 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public RFuture<Void> unlockAsync(long threadId) { return getServiceManager().execute(() -> unlockAsync0(threadId)); } private RFuture<Void> unlockAsync0(long threadId) { CompletionStage<Boolean> future = unlockInnerAsync(threadId); CompletionStage<Void> f = future.handle((opStatus, e) -> { cancelExpirationRenewal(threadId); if (e != null) { if (e instanceof CompletionException) { throw (CompletionException) e; } throw new CompletionException(e); } if (opStatus == null) { IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + threadId); throw new CompletionException(cause); } return null; }); return new CompletableFutureWrapper<>(f); } 这里就是unlock的核心逻辑了。主要看两个关键步骤: ...

March 22, 2026 · 2 min · santu

Redis如何高效安全的遍历所有key

典型回答 在Redis中遍历所有的key,有两种办法,分别使用KEYS命令和SCAN命令。 KEYS命令用于查找所有符合给定模式的键,例如KEYS *会返回所有键。它在小数据库中使用时非常快,但在包含大量键的数据库中使用可能会阻塞服务器,因为它一次性检索并返回所有匹配的键。 如使用Jedis的实现方式如下: 1 2 3 4 5 6 7 8 9 10 11 12 import redis.clients.jedis.Jedis; public class RedisKeysExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); Set<String> keys = jedis.keys("*"); // 使用KEYS命令获取所有键 for(String key : keys) { System.out.println(key); } jedis.close(); } } **SCAN命令提供了一种更安全的遍历键的方式,它以游标为基础分批次迭代键集合,每次调用返回一部分匹配的键。**SCAN命令不会一次性加载所有匹配的键,因此不会像KEYS命令那样阻塞服务器,更适合用于生产环境中遍历键集合。 如使用Jedis的是实现方式如下: ...

March 22, 2026 · 1 min · santu

Redis实现分布锁的时候,哪些问题需要考虑?

典型回答 这是一个比较典型的关于Redis分布式锁的综合性问题,其实考察的就是关于分布式锁的熟悉程度,尤其是Redis的分布式锁的熟悉程度。 我总结了一下关于这个问题可以回答的几个方向和关键点。 锁的基本要求 一个分布式锁有很多基本要求,比如说锁的互斥性、可重入性、锁的性能等问题。 对于锁的互斥性,可以借助setnx来保证,因为这个操作本身就是一个原子性操作,并且结合Redis的单线程的机制,就可以保证互斥性。 ✅Redis中的setnx命令为什么是原子性的 因为Redis是基于内存的,所以他的性能也是很高的,这个就没啥好说的了,大家都知道的。 ✅Redis为什么这么快? 至于可重入性,其实就是说一个线程,在锁没有释放的情况下,他是可以反复的拿到同一把锁的。并且需要在锁中记录加锁次数,用来保证重入几次就需要解锁几次。用setnx也是可以实现的。 ✅如何用setnx实现一个可重入锁? 当然,如果我们直接使用Redisson的话,他是支持可重入锁的实现的。可以直接用。 ✅Redisson里面的锁是实现可重入的? 误解锁问题 其实就是要确保只有锁的持有者能释放锁,避免其他客户端误解锁。这个问题其实挺傻的,但是我们实际就发生过,因为有的时候我们是在finally中去释放锁,finally有一定会执行,那么就可能会导致虽然没拿到锁,但是当他执行finally的时候,也可能把锁给解了。 所以,需要解决这个问题,我们就需要在使用setnx加锁时把具体持有锁的owner放进去,和上面一样,线程ID也好,业务单号也好,总之需要做一下判断。 如果用了Redisson是不存在这个问题的。 ✅Redisson里面的锁是怎么来防止误删的? 锁的有效时间 为了避免死锁,我们一般会给一个分布式锁设置一个超时时间,如上面我们用的setnx的方案,其实就是设置了一个超时时间的。 但是有的是,代码如果执行的比较慢的话,比如设置的超时时间是3秒,但是代码执行了5秒,那么就会导致在第三秒的时候,key超时了就自动解锁了,那么其他的线程就可以拿到锁了,这时候就会发生并发的问题了。 所以,我们需要有一个好的办法来解决。一种是设置一个更长的超时时间,避免提前释放,我见过有人把分布式锁设置半个小时。。。 但是这个方案非常不好,因为分布式锁是影响并发的,锁的时间长,意味着加锁时间段内只能有一个线程操作,那么并发度就会大大降低。(因为要考虑到解锁失败的问题) 还有一个好的办法,就是像redisson一样,实现一个watch dog的机制,给锁自动做续期,让锁不会提前释放。 ✅Redisson的watch dog机制是怎么样的? 但是需要注意的是,只有我们没有自己主动设置锁的超时时间的时候,watchdog才会续期,如果自己设置了超时时间,那么就不会给你续期了。具体看上面这个原理解读。 单点故障问题 有了自动续期之后,锁就一定可靠了吗?其实也不是,这里会存在两个单点问题。 首先,在使用单节点Redis实现分布式锁时,如果这个Redis实例挂掉,那么所有使用这个实例的客户端都会出现无法获取锁的情况。 这个问题是有解的,就是引入集群模式,通过哨兵检测redis实例挂掉的情况,提升整个集群的可用性。 ✅介绍一下Redis的集群模式? 但是,这个方案同样存在一个单点故障带来的问题: 当使用集群模式部署的时候,如果master一个客户端在master节点加锁成功了,然后没来得及同步数据到其他节点上,他就挂了, 那么这时候如果选出一个新的节点,再有客户端来加锁的时候,就也能加锁成功,因为数据没来得及同步,新的master会认为这个key是不存在的。 为了解决这个问题,redis的作者提出了一个算法——RedLock,他通过这种算法来保证在半数以上加锁成功才认为成功,这样就可以确保即使master挂了,新选出来的master也会有之前的加锁数据。 具体原理见: ✅什么是RedLock,他解决了什么问题? 网络分区问题 但是,引入红锁就万事大吉了么。也并不是。红锁同样存在问题。首先就是一个网络分区的问题。 在网络分区的情况下,比如集群发生了脑裂,不同的节点可能会获取到相同的锁,这会导致分布式系统的不一致性问题。 ✅介绍下Redis集群的脑裂问题? 但是我们需要注意的是,这个情况虽然会存在节点获取到相同锁,但这种情况只会发生在网络分区发生时,且只会发生在一小部分节点上。而在网络分区恢复后,RedLock 会自动解锁。所以理论上来说是有这个风险,但是实际上来说发生的概率极低。 时间漂移问题 除了脑裂,还有一个时钟飘移的问题,由于不同的机器之间的时间可能存在微小的漂移,这会导致锁的失效时间不一致,也会导致分布式系统的不一致性问题。 那么就会导致有的redis实例已经解锁了,那么就会使得新的客户端可以拿到锁。 这个问题的解决方案是,RedLock 可以使用 NTP 等工具来同步不同机器之间的时间,从而避免时间漂移导致的问题。

March 22, 2026 · 1 min · santu

Redis Cluster 中使用事务和 lua 有什么限制?

典型回答 ✅介绍一下Redis的集群模式? Redis Cluster采用主从复制模式来提高可用性。每个分片都有一个主节点和多个从节点。主节点负责处理写操作,而从节点负责复制主节点的数据并处理读请求。在Redis的Cluster 集群模式中,会对数据进行数据分片,将整个数据集分配给不同节点。 这个思想就和我们在MySQL 中做分库分表是一样的,都是通过一定的分片算法,把数据分散到不同的节点上进行存储。 那么和 MySQL 对跨库事务支持存在限制一样,在 Redis Cluster 中使用事务和 Lua 脚本时,也是有一定的限制的。 在 Redis Cluster 中,事务不能跨多个节点执行。事务中涉及的所有键必须位于同一节点上。如果尝试在一个事务中包含多个分片的键,事务将失败。另外,对 WATCH 命令也用同样的限制,要求他只能监视位于同一分片上的键。 和事务相同,**执行 Lua 脚本时,脚本中访问的所有键也必须位于同一节点。**Redis 不会在节点之间迁移数据来支持跨节点的脚本执行。Lua 脚本执行为原子操作,但是如果脚本因为某些键不在同一节点而失败,整个脚本将终止执行,可能会影响数据的一致性。 当我们要跨节点执行 lua 的时候,会报错提示:command keys must in same slot。(详见:https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/ ) 如何解决? ✅如何在 Redis Cluster 中执行 lua 脚本?

March 22, 2026 · 1 min · santu

留言给博主