Redis command timed out 两种异常情况

Redis command timed out

SpringBoot项目引入Redis后发现偶尔会出现连接会超时Redis command timed out,看了博客上写的很多文章,都说可以通过设置超时时间解决问题,尝试的一下还是会出现这个问题,其实不管你设置多久都还是会超时。
原因是springboot2.x之后,springboot默认使用的Redis的客户端是lettuce,而不是jedis,lettuce连接池。

org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed
 out after 5 second(s)
    at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70)
    at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
    at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
    at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
    at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:273)
    at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:799)
    at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get(LettuceStringCommands.java:68)
    at org.springframework.data.redis.connection.DefaultedRedisConnection.get(DefaultedRedisConnection.java:266)
    at org.springframework.data.redis.core.DefaultValueOperations$1.inRedis(DefaultValueOperations.java:57)
    at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:60)

解决:
 引入spring-boot-starter-data-redis包,这个包会默认使用 lettuce ,这个问题就lettuce引起的,我们只需要把io.lettuce包移除,换成jedis就可以了

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <!-- 过滤lettuce,使用jedis作为redis客户端 -->
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

二、keys命令会遍历redis集合,是一个非常耗时的操作,线上应该禁止使用此操作,通过业务排查发现有一个业务还使用了keys 命令来过滤数据,线上代码中使用了keys 命令来过滤key keys命令类似与数据库的全表扫描,会遍历redis中的所有数据,而redis的work线程又是单线程的,这个命令执行时间过长会阻塞其他正常命令的执行,导致其他命令执行超时,出现前面问题中的timed out 异常。
在这里插入图片描述
解决:
了解到keys 命令的影响,禁止使用keys 命令,特别是线上环境,禁止使用redis desktop manager 这样的redis 界面工具连接线上环境(因为这类工具会通过keys * 来加载全量数据到本地),排查代码中使用keys 命令的情况,必须在redis 服务器上禁止keys 这样不安全的命令的使用,还有flushdb flushall等操作。

三、spring-boot-starter-data-redis有两种实现方式:lettuce 和 jedis 区别

1.Jedis:
Jedis是同步的,不支持异步,Jedis客户端实例不是线程安全的,需要每个线程一个Jedis实例,所以一般通过连接池来使用Jedis。

优点:

  • 提供了比较全面的 Redis 操作特性的 API
  • API 基本与 Redis 的指令一一对应,使用简单易理解

缺点:

  • 同步阻塞 IO
  • 不支持异步
  • 线程不安全

springboot链接Redis客户端Jedis的pom.xml配置

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <!-- 过滤lettuce,使用jedis作为redis客户端 -->
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

jedis的yml文件配置如下:

#Redis哨兵模式
spring:
  redis:
    database: 1
    password: 123456
    jedis:
      pool:
        max-active: 8
        min-idle: 0
        max-idle: 8
    sentinel:
      master: mymaster
      nodes: 192.168.111.10:26379,192.168.111.11:26379,192.168.111.12:26379
     

2.Lettuce:
Lettuce是基于Netty框架的事件驱动的Redis客户端,其方法调用是异步的,Lettuce的API也是线程安全的,所以多个线程可以操作单个Lettuce连接来完成各种操作,同时Lettuce也支持连接池.

优点:

  • 线程安全
  • 基于 Netty 框架的事件驱动的通信,可异步调用
  • 适用于分布式缓存

缺点:

  • API 更抽象,学习使用成本高
    springboot链接Redis客户端Jedis的pom.xml配置
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
		<!-- spring2.X集成redis需要common-pool2依赖,如果使用Lettuce作为连接池,
		需要引入commons-pool2依赖,否则会报错bean注入失败 -->
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-pool2</artifactId>
		</dependency>

jedis的yml文件配置如下:

#Redis哨兵模式
spring: 
  redis:
    database: 1
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        max-wait: 10000
        min-idle: 0
      shutdown-timeout: 100
    password: 123456
    sentinel:
      master: mymaster
      nodes: 192.168.111.10:26379,192.168.111.11:26379,192.168.111.12:26379
    timeout: 3000
     
<think>好的,我现在需要帮助用户解决在C++中实现带超时功能的锁的问题。用户提到参考了之前关于Redis分布式锁的引用,但不确定他们是否真的需要使用Redis还是更通用的解决方案。首先,我需要明确用户的需求:他们想要在C++中实现一个带有超时功能的锁,可能是在多线程或者分布式环境中使用。 首先,我应该考虑用户可能的场景。如果是多线程环境,可以使用C++标准库中的互斥量和条件变量来实现超时锁。如果是分布式系统,可能需要用到Redis这样的外部存储来实现跨进程的锁。但用户提供的引用是关于Redis分布式锁的,所以可能他们更关注分布式场景下的超时锁。 接下来,我需要提供两种可能的解决方案:一种是基于标准库的多线程超时锁,另一种是基于Redis的分布式超时锁。这样用户可以根据实际需求选择适合的方法。 对于多线程情况,C++11之后的版本提供了std::timed_mutex,可以尝试在指定时间内获取锁。如果超时,则返回失败。示例代码需要展示如何使用try_lock_for方法,并处理超时情况。 对于分布式场景,使用Redis的SET命令带有NX和PX选项可以实现带有超时的锁。需要确保锁的获取和释放是原子的,使用Lua脚本来保证原子性,避免竞态条件。示例代码可能需要依赖hiredis库,或者用户使用的其他Redis客户端库。同时,需要注意处理网络错误和异常,确保锁的可靠性。 此外,用户可能关心的点包括锁的自动释放、避免死锁、锁续租机制以及容错性。需要提到Redlock算法或者锁续租的方法,但可能在示例代码中不深入展开,而是给出提示或参考。 需要检查示例代码是否正确,比如在Redis分布式锁的实现中,获取锁时使用SET命令,释放锁时检查当前锁的值是否匹配,防止误删其他客户端的锁。同时,超时时间的设置要合理,避免锁过早释放导致的问题。 另外,用户可能希望代码示例简洁明了,所以需要确保代码结构清晰,注释详细。同时,在解释代码时,指出关键点,比如原子操作、错误处理等。 最后,生成相关问题,帮助用户进一步探索,比如性能优化、锁续租、与其他锁机制对比等。</think>[用户可见层] ### C++实现带超时功能的锁(两种典型方案) #### 方案1:基于标准库的多线程超时锁 ```cpp #include <iostream> #include <mutex> #include <chrono> #include <thread> class TimeoutMutex { private: std::timed_mutex mtx; public: bool try_lock(int milliseconds) { return mtx.try_lock_for(std::chrono::milliseconds(milliseconds)); } void unlock() { mtx.unlock(); } }; int main() { TimeoutMutex tm; if (tm.try_lock(500)) { // 尝试500毫秒内获取锁 std::cout << "Lock acquired" << std::endl; // ...执行临界区操作 std::this_thread::sleep_for(std::chrono::seconds(1)); tm.unlock(); } else { std::cout << "Failed to acquire lock within timeout" << std::endl; } return 0; } ``` *特点*:使用$std::timed\_mutex$实现本地线程锁,通过$try\_lock\_for$实现非阻塞式获取锁[^1] #### 方案2:基于Redis的分布式超时锁 ```cpp #include <hiredis/hiredis.h> #include <string> #include <uuid/uuid.h> class RedisTimeoutLock { private: redisContext* redis; std::string lock_key; std::string lock_value; int timeout_ms; public: RedisTimeoutLock(const std::string& key, int timeout) : lock_key(key), timeout_ms(timeout) { redis = redisConnect("127.0.0.1", 6379); uuid_t uuid; uuid_generate(uuid); char uuid_str[37]; uuid_unparse(uuid, uuid_str); lock_value = uuid_str; } bool acquire() { // SET lock_key uuid NX PX timeout_ms redisReply* reply = (redisReply*)redisCommand(redis, "SET %s %s NX PX %d", lock_key.c_str(), lock_value.c_str(), timeout_ms); bool success = (reply && reply->type == REDIS_REPLY_STATUS); freeReplyObject(reply); return success; } bool release() { // Lua脚本保证原子操作 const char* lua_script = "if redis.call('get',KEYS[1]) == ARGV[1] then " " return redis.call('del',KEYS[1]) " "else " " return 0 " "end"; redisReply* reply = (redisReply*)redisCommand(redis, "EVAL %s 1 %s %s", lua_script, lock_key.c_str(), lock_value.c_str()); bool success = (reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1); freeReplyObject(reply); return success; } ~RedisTimeoutLock() { redisFree(redis); } }; ``` *关键机制*: 1. 使用$NX$参数保证互斥性,$PX$设置毫秒级过期时间 2. 通过唯一UUID标识锁持有者,防止误删其他客户端的锁 3. 使用Lua脚本实现原子化释放操作[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值