redis报protocol error的真正原凶

简介:

前段时间写了个文章详细描述了在什么场景下会出现redis的protocol error错误,但是手抽筋, 不小心点错给删了,而且还原不了,没办法了,只能重写一下,但是没上次那么详细了,如果不太明白就看源代码吧!首先呢,这种错误是基于使用了phpredis的的长连接和multi功能才会出现!这里有两个问题

1、当你开了事务,做了N次写操作,然后又discard之后又做了M次操作(M小于N),这样请求就会被阻塞住(这个操作无论使用短连接还是长连接,都能复现),具体看代码:

$redis = new Redis();
$redis->connect('localhost', 6379);
$redis->multi();
$redis->set('test', 10);
$redis->zIncrBy('test2', 1, 'bbb');
$redis->discard();
$redis->multi();
$redis->zIncrBy('test2', 2, 'bbb');
$redis->exec();//操作会阻塞在这里

因为phpredis在discard成功后,没有清理callback list,所以卡住了。

2、开事务,做N次操作,discard之后再做M次操作(但这里M大于N),多刷几次就会出现protocol error(这个必须使用长连接才会复现)!

$redis = new Redis();
$redis->pconnect('localhost', 6379);
$redis->multi();
$redis->set('test', 10);
$redis->discard();
$redis->multi();
$redis->zIncrBy('test2', 2, 'bbb');
$redis->zIncrBy('test2', 1, 'bbb');
$redis->exec();

跟上面原因一样,discard没的清理callback list, 就会出现stream里面的数据没读完!协议就完全乱掉了,

那么上面说的callback list又是什么东西呢?在redis里面,当你使用了multi,在执行exec之前的请求基本都是返回+QUEUED(如果需要了解更详细redis协议,请见redis.io),而真正返回数据是等exec执行之后,才去解析返回数据。所以phpredis针对不同的请求处理方式是不一样的,所以在开启了multi之后,phpredis会维护一个处理函数列表,比如set(k,v)这需要绑定一个bool值处理函数,而zincrBy需要绑定一个double值处理函数,执行exec之后,去遍历这个列表处理返回数据就即可。

由于redis针对multi之后的请求都是队列并没有执行,所以客户端可以使用discard命令来清空这个队列,同时客户端也应该将之前绑定的函数列表一并清除,可是phpredis对于discard的处理仅仅是发送了discard命令到redis服务端,却没有清空处理函数列表。只是在下一次执行multi的时候,他仅仅是将这个处理函数列表中一个叫current的指针值为NULL(这个列表是一个单向连表,head表示头元素,current表示尾元素),可是他忽略了head,因为函数列表一旦进入处理是从head开始,只有需要新加函数到列表的时候才会用到current。所以在phpredis里面,执行一条命令,再discard,再执行两条命令之后,这个处理函数列表里只有一个函数(正确应该是两个,而且还是最开始加的那一个,后面加的两个,不翼而飞了。。),处理函数与返回数据不配对,协议自然也就乱了,这就是protocol error报错的由来....

相关文章
|
缓存 NoSQL Redis
【Azure Redis 缓存】使用StackExchange.Redis,偶发ERROR - Timeout performing HSET (15000ms)
【Azure Redis 缓存】使用StackExchange.Redis,偶发ERROR - Timeout performing HSET (15000ms)
186 0
|
NoSQL 网络安全 Redis
redis.exceptions.ConnectionError: Error 111 connecting to 127.0.0.1:6379. Connection refused.
当使用Python连接Redis遇到"ConnectionError: Error 111"时,可能的原因包括Redis未启动、非默认端口监听、防火墙阻拦、配置错误或Redis模块安装不正确。解决方法包括启动Redis、检查端口与防火墙设置、修正配置文件、确保模块正确安装及测试服务器功能。提供了一个Python连接Redis的示例代码,根据实际情况调整IP和端口,以诊断连接问题。
1290 0
|
存储 NoSQL Redis
redis-(error)-MISCONF。Redis。is。configuredto。save。RDBsnapshots
redis-(error)-MISCONF。Redis。is。configuredto。save。RDBsnapshots
2731 0
|
NoSQL Java Redis
redis.clients.jedis.exceptions.JedisDataException: ERR Syntax error, try CLIENT (LIST | KILL ip:port
redis.clients.jedis.exceptions.JedisDataException: ERR Syntax error, try CLIENT (LIST | KILL ip:port
|
NoSQL Linux 网络安全
【Azure Redis】Redis-CLI连接Redis 6380端口始终遇见 I/O Error
【Azure Redis】Redis-CLI连接Redis 6380端口始终遇见 I/O Error
261 0
|
NoSQL 安全 Linux
Another Redis Desktop Manager远程连接Redis报错:Client On Error: Error: connect ETIMEDOUT
在尝试使用Another Redis Desktop Manager连接远程Redis时遇到持续Timeout的问题,检查并执行了常规教程中的所有步骤,包括修改Redis配置文件以允许远程访问,开放本地防火墙的6379端口,以及确保网络连通性。
1511 0
|
NoSQL Redis
进行主从复制时出现的异常FATAL CONFIG FILE ERROR (Redis 6.2.6)Reading the configuration file
进行主从复制时出现的异常FATAL CONFIG FILE ERROR (Redis 6.2.6)Reading the configuration file
800 0
|
前端开发 Java 数据库连接
ERROR o.s.d.redis.listener.RedisMessageListenerContainer
ERROR o.s.d.redis.listener.RedisMessageListenerContainer
601 0
|
NoSQL Redis 数据安全/隐私保护
Docker进入redis容器连接redis-cli 报错:(error) NOAUTH Authentication required.
Docker进入redis容器连接redis-cli 报错:(error) NOAUTH Authentication required.
|
Linux C语言
Centos7安装redis6.0.7出错:make[1]: *** [server.o] Error 1 make[1]: Leaving directory `/opt/redis-6.0.7/s
Centos7安装redis6.0.7出错:make[1]: *** [server.o] Error 1 make[1]: Leaving directory `/opt/redis-6.0.7/s
Centos7安装redis6.0.7出错:make[1]: *** [server.o] Error 1 make[1]: Leaving directory `/opt/redis-6.0.7/s