本文介绍访问 云原生内存数据库Tair 与 云数据库Redis版 时的常见报错与解决方法。
报错概览
分类 |
报错项 |
Redis通用异常 |
|
Proxy(代理模式)通用异常 |
|
Lua脚本与事务(Transaction) |
|
Jedis客户端 |
|
Lettuce客户端 |
|
Redisson客户端 |
|
Spring Data Redis客户端 |
|
StackExchange.Redis客户端 |
|
Predis客户端 |
|
phpredis客户端 |
|
Go-redis客户端 |
|
node-redis客户端 |
Redis通用异常
ERR illegal address
可能原因:未将客户端的IP地址添加至 Redis 实例的白名单中。
解决方法:将客户端的IP地址添加入至 Redis 实例的白名单中,具体操作请参见 连接诊断 。
ERR sentinel compatibility mode is disabled
可能原因:未开启 Redis 实例的Sentinel兼容。
解决方法:在控制台开启Sentinel兼容,具体操作请参见 开启Sentinel兼容 。
ERR max number of clients reached
可能原因:客户端的连接数超过了 Redis 实例的最大连接数。
NOAUTH Authentication required
可能原因: Redis 实例设置了密码鉴权,但客户端没有提供密码或提供了错误的密码。
解决方法:请使用正确的账号密码进行访问,更多信息请参见 Redis实例登录方式 。
若实例使用Sentinel模式访问,请参见 Sentinel兼容模式连接 。
WRONGPASS invalid username-password pair
可能原因:密码错误。
解决方法:请使用正确的账号密码进行访问,更多信息请参见 Redis实例登录方式 。
若实例使用Sentinel模式访问,请参见 Sentinel兼容模式连接 。
ERR invalid password
可能原因:密码错误。
解决方法:请使用正确的账号密码进行访问,更多信息请参见 Redis实例登录方式 。
若实例使用Sentinel模式访问,请参见 Sentinel兼容模式连接 。
Connection reset by peer
可能原因:客户端连接被关闭,通常是由于客户端缓冲区异常而关闭客户端连接。
解决方法:检查应用侧代码或调整客户端Buffer的大小,更多信息请参见 Unexpected end of stream 章节。
UnknownHostException
或
failed to connect: xxx.redis.rds.aliyuncs.com could not be resolved
。
可能原因:客户端无法正常解析 Redis 实例的域名地址。
解决方案:请设置正确的DNS服务器地址,更多信息请参见 解决因域名解析失败导致的连接问题 。
OOM command not allowed when used memory > 'maxmemory'
可能原因: Redis 实例已使用的内存超过该实例的最大配置(maxmemory)。
如果是 Redis 集群实例,可能是其中一个节点已使用的内存已超过该节点的最大配置。
WRONGTYPE Operation against a key holding the wrong kind of value
可能原因:命令使用错误,例如对String数据类型执行
HASH
命令。
解决方法:修改错误代码或命令,更多信息请参 Redis Commands 。
ERR unknown command 'xxx'
可能原因: Redis 不存在您调用的命令。
解决方法:检查当前实例版本的命令支持情况,更多信息请参见 Redis社区版命令支持 。
最新小版本将提供更丰富的功能与稳定的服务,建议将实例的小版本升级到最新,具体操作请参见 升级小版本 。
ERR command 'xxx' not support for your account
可能原因:阿里云禁止用户执行某些 Redis 命令,或您手动在 #no_loose_disabled-commands 参数中配置了禁止执行的命令。更多信息请参见 Redis社区版命令支持 和 禁用高风险命令 。
解决方法:若需执行您禁用的命令,您可以在 #no_loose_disabled-commands 参数中删除对应命令。
NOPERM this user has no permissions to run the 'xxx'
可能原因:阿里云禁止用户执行某些 Redis 命令,或您手动在 #no_loose_disabled-commands 参数中配置了禁止执行的命令。更多信息请参见 Redis社区版命令支持 和 禁用高风险命令 。
解决方法:若需执行您禁用的命令,您可以在 #no_loose_disabled-commands 参数中删除对应命令。
ERR FLUSHDB is not allowed in migrating mode
可能原因:
Redis
云盘集群架构实例在执行增加或减少数据节点时,禁止使用
FLUSHDB
或
FLUSHALL
命令。
解决方法:等待 Redis 云盘集群架构实例执行增加或减少数据节点结束,更多信息请参见 调整实例的分片数量 。
CROSSSLOT Keys in request don't hash to the same slot
可能原因: Redis 集群架构直连模式不支持跨Slot执行涉及多Key的命令,例如 DEL 、 MSET 、 MGET 等。
解决方法:
-
在执行操作命令前增加确认Key Slot的逻辑(例如通过 CLUSTER KEYSLOT 命令),确保单个命令执行的所有Key在一个Slot中。
-
通过改造Key名称,增加Hash tags使其保证在同一个Slot,该方案在使用过程中需避免数据倾斜,更多信息请参见 Hash tags 。
-
改造实例为集群架构代理(Proxy)模式,Proxy模式支持跨Slot执行 DEL 、 MGET 、 MSET 等涉及多Key的命令,更多信息请参见 Redis Proxy特性说明 。
ERR READONLY you can't write against a read only instance
可能原因: Redis 实例在主备切换、升降配或小版本升级时,将出现秒级的连接闪断和30秒以内的只读状态。
解决方法:属于正常现象,实例会自动恢复,您无需进行任何操作。请提前为您的应用设计重连机制和异常处理的能力,更多信息请参见 升级实例配置 。
Proxy(代理模式)通用异常
ERR client ip is not in whitelist
可能原因:未将客户端的IP地址添加至 Redis 实例的白名单中。
解决方法:将客户端的IP地址添加入至 Redis 实例的白名单中,具体操作请参见 连接诊断 。
NOWRITE You can't write against a non-write redis
或
NOREAD You can't read against a non-read redis
。
可能原因:实例处于欠费或到期状态,实例状态显示为 已锁定 。
解决方法:对账号进行充值,或对到期的实例进行续费,更多信息请参见 到期与欠费 。
ERR syntax error
可能原因:命令语法错误,例如需要传入4个参数,实际仅传入3个参数。
解决方法:检查命令格式是否正确,更多信息请参 Redis Commands 。
ERR no such db node
可能原因:使用阿里云自研的
Redis
命令时,传入的
db node
错误。
解决方法:传入正确的
db node
,
db node
需小于分片数量,更多信息请参见
阿里云自研的Proxy命令
。
ERR 'xxx' command keys must in same slot
可能原因:在 Redis 集群架构实例中,通过事务或脚本执行命令时要求所有Key必须在同一个Slot中,否则将返回上述错误。
解决方法:改造事务或脚本,您可以通过
CLUSTER KEYSLOT
命令获取目标Key的Hash Slot。
Redis 集群架构实例会根据CRC算法将Key均匀的写入不同Slot中。若您希望将多个Key写入到一个Slot中,您可以使用Hash Tags,但该方法若使用不当容易造成数据倾斜,请谨慎使用,更多信息请参见 Hash tag 。
ERR for redis cluster, eval/evalsha number of keys can't be negative or zero
可能原因:执行
EVAL
和
EVALSHA
命令未传入Key或
numkeys
参数的值未大于0。
解决方法:执行
EVAL
和
EVALSHA
命令时,至少需要传入一个Key且
numkeys
参数的值大于0,更多信息请参见
Lua脚本基本语法
。
ERR request refused, too many pending request, now count xxx, beyond threshold xxx
可能原因:由于客户端使用了不合理的Pipeline, Redis 后端堆积了过多未处理的Request,新请求被拒绝。
解决方法:减少Pipeline的请求数量。
ERR redis temporary failure
可能原因:部分 Redis 子实例访问超时,可能是网络抖动、连接数到达上限导致断链、实例正在进行主备切换或执行慢查询等导致。
解决方法:属于正常现象,实例会自动恢复,您无需进行任何操作。请提前为您的应用设计重连机制和异常处理的能力。
ERR redis temporary failure (ErrorCode 7002)
可能原因:部分 Redis 子实例访问超时,可能是实例正在变配或正在进行主备切换导致。
解决方法:属于正常现象,实例会自动恢复,您无需进行任何操作。请提前为您的应用设计重连机制和异常处理的能力。
Lua脚本与事务(Transaction)
NOSCRIPT No matching script. Please use EVAL.
可能原因:使用
EVALSHA
命令时,若SHA1值对应的脚本未缓存至
Redis
中。
解决方法:通过
EVAL
命令或
SCRIPT LOAD
命令将目标脚本缓存至
Redis
中后进行重试,更多信息请参见
处理NOSCRIPT错误
。
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
可能原因:处理Lua脚本超时。
解决方法:通过SCRIPT KILL命令终止Lua脚本或等待Lua脚本执行结束,更多信息请参见 处理Lua脚本超时 。
ERR command eval not support for normal user
可能原因:无法执行
EVAL
的相关命令。
解决方法:请将实例的小版本升级至最新,具体操作请参见 升级小版本 。
ERR eval/evalsha command keys must be in same slot
解决方法:改造Lua脚本,您可以通过
CLUSTER KEYSLOT
命令获取目标Key的Hash Slot,更多信息请参见
集群中Lua脚本的限制
。
ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array
可能原因:Proxy(代理)节点的Lua脚本限制。
解决方法:所有Key都应该由KEYS数组来传递,例如
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar
,不能使用Lua变量替换KEYS,更多信息请参见
集群中Lua脚本的限制
。
EXECABORT Transaction discarded because of previous errors
可能原因:事务中的命令执行失败,可能是命令语法错误或运行错误等。
解决方法:检查代码逻辑,修复错误命令。
UNKILLABLE Sorry the script already executed write commands against the dataset.
可能原因:当前Lua脚本已执行写命令,此时 SCRIPT KILL 命令无法生效。
解决方法:在控制台的实例列表页面,找到对应实例,单击 操作 列的 重启 ,更多信息请参见 重启实例 。
UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.
可能原因:当前Lua脚本已经被Master节点发给自己的replica节点,此时 SCRIPT KILL 命令无法生效。
解决方法:在控制台的实例列表页面,找到对应实例,单击 操作 列的 重启 ,更多信息请参见 重启实例 。
NOTBUSY No scripts in execution right now.
可能原因:当前没有正在运行Lua脚本。
解决方法:无需处理,不要调用 SCRIPT KILL 命令。
Jedis客户端
Could not get a resource from the pool
可能原因:无法从连接池获取到Jedis连接。
-
当 blockWhenExhausted 参数为 true (默认)时,若连接池没有可用的Jedis连接,客户端通常会等待一段时间(等待时间由 maxWaitMillis 参数决定,单位为毫秒),若长时间没有获取到可用的Jedis连接,会出现如下异常:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool Caused by: java.util.NoSuchElementException: Timeout waiting for idle object at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
-
当 blockWhenExhausted 参数为 false 时,若连接池没有可用的Jedis连接,则会立即出现如下异常:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool Caused by: java.util.NoSuchElementException: Timeout waiting for idle object at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
解决方法:您可以从如下几个方面进行排查。
-
连接泄露
JedisPool默认 maxTotal 值为8,从如下代码得知,从JedisPool中获取了8个Jedis资源,但没有归还资源。因此,在第9次尝试获取Jedis资源时,无法调用
jedisPool.getResource().ping()
。GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379); // 向JedisPool借用8次连接,但是没有执行归还操作。 for (int i = 0; i < 8; i++) { Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.ping(); } catch (Exception e) { logger.error(e.getMessage(), e); jedisPool.getResource().ping();
推荐使用如下规范代码。
Jedis jedis = null; try { jedis = jedisPool.getResource(); // 具体的命令。 jedis.executeCommand() } catch (Exception e) { // 如果命令有Key,建议在错误日志中把Key打印出来,对于集群架构来说,可通过Key定位到具体节点。 logger.error(e.getMessage(), e); } finally { // 注意:这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。 if (jedis != null) jedis.close(); }
-
maxTotal 值设置得过小
当业务并发量大时,可能会由于 maxTotal 值设置的过小导致异常。例如,一次命令运行时间的平均耗时约为1ms(
Borrow|Return resource
+ Jedis执行命令 + 网络时间),一个连接的QPS大约为1000,业务期望的QPS为50000,则理论上需要的 maxTotal 值为50000 / 1000 = 50。在该情况下,您可以在客户端所在的机器上执行下述命令,该命令返回的结果为连接客户端的连接数,您可以根据该数值对 maxTotal 值进行调整。
netstat -an | grep 6379 | grep EST | wc -l
-
Jedis连接阻塞
当 Redis 实例发生阻塞时(例如慢查询等原因),所有连接会在超时时间范围内等待,当并发量较大时,会造成连接池资源不足,更多信息请参见 connect timed out 。
-
Jedis连接被拒绝
从JedisPool中获取连接时,由于没有空闲连接,需要重新生成一个Jedis连接,但是连接被拒绝,异常示例如下:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool at redis.clients.util.Pool.getResource(Pool.java:50) at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99) at TestAdmin.main(TestAdmin.java:14) Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused at redis.clients.jedis.Connection.connect(Connection.java:164) at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:80) at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1676) at redis.clients.jedis.JedisFactory.makeObject(JedisFactory.java:87) at org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:861) at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:435) at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363) at redis.clients.util.Pool.getResource(Pool.java:48) ... 2 more Caused by: java.net.ConnectException: Connection refused at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:579) at redis.clients.jedis.Connection.connect(Connection.java:158) ... 9 more
可以从
at redis.clients.jedis.Connection.connect(Connection.java:158)
中看出,实际是在创建一个Socket连接,并调用connect
函数,但是被拒绝连接,如下为Jedis源码。socket.setSoLinger(true, 0); 158: socket.connect(new InetSocketAddress(host, port), connectionTimeout);
通常情况下,该问题需要排查 Redis 的域名配置是否正确、该段时间网络是否正常。
java.net.SocketTimeoutException: connect timed out
可能原因:客户端连接 Redis 实例超时。
解决方法:更多信息请参见 连接问题排查流程 。
java.net.SocketTimeoutException: Read timed out
可能原因:网络不稳定、读写超时时间过短、存在慢查询或发生阻塞等原因造成Jedis API调用超时。
解决方法:适当增大超时时间,或进行 实例诊断 ,查看实例在对应时间点是否存在性能问题或异常。
No reachable node in cluster
可能原因:JedisCluster地址无法访问。
解决方法:若是首次访问 Redis 实例,请检查是否将客户端的IP地址添加至 Redis 白名单中或客户端的网络情况;若不是首次访问 Redis 实例,可以进行 实例诊断 ,进行问题定位。
Caused by: java.lang.NumberFormatException: For input string: "6379@13028"
可能原因:Jedis 2.8.0及以下版本引入了
ClusterNodeInformationParser
来解析
cluster slots
返回值,但Redis后续更改了此命令返回值类型,所以报错
NumberFormatException
。
解决方法:将Jedis升级至2.9.0及以上版本。
No more cluster attempts left
可能原因:JedisCluster在API超时之后会默认重试5次(MaxAttempts,默认为5),并且在均失败之后抛出此错误。
解决方法:适当增大超时时间或进行 实例诊断 。
Unexpected end of stream
可能原因:Jedis缓冲区异常,您可以从如下几个方面进行排查。
-
多个线程使用一个Jedis连接
通常情况下,一个线程使用一个Jedis连接。例如下面代码就是两个线程共用了一个Jedis连接:
new Thread(new Runnable() { public void run() { for (int i = 0; i < 100; i++) { jedis.get("hello"); }).start(); new Thread(new Runnable() { public void run() { for (int i = 0; i < 100; i++) { jedis.hget("haskey", "f"); }).start();
为避免出现这种情况,您可以使用JedisPool管理Jedis连接,实现线程安全。
-
长时间闲置连接
长时间闲置连接会被服务端主动断开,请查询实例的 timeout 参数配置、Jedis连接池配置,确定是否需要进行空闲超时检测。
说明默认设置下,即使某个客户端已经空闲了很长时间, Redis 也不会主动断开与该客户端的连接,若您调整过 timeout 参数,则可能会遇到该问题。更多信息请参见 设置客户端的超时时间 。
解决方法:检查是否有多线程共用Jedis代码或由于长时间闲置连接造成服务端断开连接。
java.lang.Long cannot be cast to java.util.List
可能原因:若多个线程操作同一个Jedis连接就会返回该报错,Jedis本身存在线程安全问题。
解决方法:Jedis的正确使用方法是一个线程操作一个Jedis。您可以使用JedisPool(非Jedis)避免该问题。
Broken pipe (Write failed)
可能原因:在Jedis单连接模式(未使用JedisPool)下超时后,客户端关闭了Socket,此时若您继续调用读写接口写入数据,会返回该报错。
解决方法:Jedis的正确使用方法是一个线程操作一个Jedis。您可以使用JedisPool(非Jedis)避免该问题。
No way to dispatch this command to Redis Cluster because keys have different slots
可能原因:JedisCluster操作的Key不在同一个Slot(槽)中。
解决方法:通过Hash tags对Key进行改造,更多信息请参见 Hash tags 。
您也可以使用Proxy(代理)模式屏蔽集群的限制。
Lettuce客户端
Connection to xxx not allowed. This Partition is not known in the cluster view.
可能原因:Lettuce客户端在默认情况下,配置为
refreshOption = null , validateClusterNodeMembership = true
,表示开启
validateClusterNodeMembership
检测。在
Redis
实例地址发生路由变化后,由于没有开启
refreshOption
,即不会更新路由表,此时
validateClusterNodeMembership
检测就会返回该报错。
解决方法:配置
refreshOption
选项,并且设置
validateClusterNodeMembership
为
false
,更多信息请参见
Lettuce
。
io.lettuce.core.RedisConnectionException: Unable to connect xxx
可能原因:客户端连接 Redis 实例超时。
解决方法:更多信息请参见 连接问题排查流程 。
java.nio.channels.UnresolvedAddressException
可能原因:大概率是Netty版本冲突。
解决方法:检查Netty依赖,建议选择较高的版本,更多信息请参见 Spring-boot issues 。
ERR Unknown sentinel subcommand 'master'
可能原因:Lettuce在
master-replica
Sentinel模式下会向Redis实例发送
Sentinel master/slave
命令,而
Redis
实例在Sentinel兼容模式下仅支持
Sentinel get-master-addr-by-name
命令,故产生该报错。
解决方法:修改代码为普通模式(非Sentinel), Redis 采用自研的高可用服务HA组件,无需Sentinel。
部分实例版本不支持RESP3协议,报错unknown command
可能原因:Redis 6.0及以上版本支持了RESP3协议,可通过 HELLO 命令切换RESP协议。但部分低版本实例不支持 HELLO 命令,可能会存在兼容性问题。
解决方法:您可以直接在程序中指定以RESP2协议访问 Redis 实例,示例如下:
client.setOptions(ClientOptions.builder()
.protocolVersion(ProtocolVersion.RESP2)
.build());
若使用Spring-data-redis with Lettuce,示例如下:
LettuceClientConfiguration lettuceClientConfiguration = LettuceClientConfiguration.builder().
clientOptions(ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).build()).build();
return new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
Redisson客户端
org.redisson.client.RedisConnectionException: Unable to connect to Redis server xxx
可能原因:客户端连接 Redis 实例超时。
解决方法:更多信息请参见 连接问题排查流程 。
No enum constant org.redisson.cluster.ClusterNodeInfo.Flag.NOFAILOVER
可能原因:Redisson低版本Bug,更多信息请参见 Redisson issue 。
解决方法:将Redisson升级至3.11.6及以上版本。
Spring Data Redis客户端
NOPERM this user has no permissions to run the 'config|get' command
可能原因:Spring Data Redis的KeyspaceEventMessageListener功能在服务端不支持
CONFIG
命令,通常在启动客户端时会产生该报错。
解决方法:可通过如下方法,设置keyspaceNotificationsConfigParameter为空,绕过该问题,更多信息请参见 Spring Data Redis 。
@EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP, keyspaceNotificationsConfigParameter = "")
StackExchange.Redis客户端
Multiple databases are not supported on this server; cannot switch to database
可能原因:集群架构不支持执行
SELECT
命令。
解决方法:将
cluster_compat_enable
参数设置为0(即关闭原生Redis Cluster语法兼容),具体操作请参见
设置实例参数
,然后重启客户端应用后重试。
Predis客户端
Error while reading line from the server.
可能原因:读取超时,您可能正在执行一个慢查询。
解决方法:适当增大超时时间或将客户端的
read_write_timeout
参数改为
0
或
-1
,更多信息请参见
Predis questions
。
phpredis客户端
Cannot assign requested address
可能原因:客户端通过短连接访问 Redis 实例时,产生该报错。
解决方法:使用
pconnect
替换
connect
的连接方式,或修改客户端所在ECS实例的
tcp_max_tw_buckets
内核参数,更多信息请参见
Cannot assign requested address
。
redis protocol error, got ' ' as reply type byte
可能原因:phpredis低版本Bug,更多信息请参见 phpredis issues 。
解决方法:将phpredis升级至最新版。
php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution
解决方案:请设置正确的DNS服务器地址,更多信息请参见 解决因域名解析失败导致的连接问题 。
可能原因:客户端无法正常解析 Redis 实例的域名地址。
Go-redis客户端
panic: got 4 elements in cluster info address, expected 2 or 3
可能原因:使用的Go-redis客户端版本与 Redis 版本不匹配,更多信息请参见 Go-redis issues 。
解决方案:使用对应版本的Go-redis客户端访问 Redis 实例。
-
Redis 6.0及以下版本:选择Go-redis v8.0及以下版本。
-
Redis 7.0及以上版本:选择Go-redis v9.0及以上版本。
node-redis客户端
SCAN命令死循环或者返回数据为空
可能原因: SCAN 命令返回的Cursor值可能超过了JavaScript最大可精确表达的数值 Number.MAX_SAFE_INTEGER ,导致Cursor不准确,引发死循环,更多信息请参见 GitHub #2561 。
解决方案:将node-redis客户端升级至5.0.0及以上版本。