“短连接访问Redis报错Cannot assign requested address”,出现这种错误的应用程序使用的架构基本都是php-fpm+phpredis。并发较大的情况下,处于TIME-WAIT状态下的TCP连接较多,客户端无法分配出新的端口,报错Cannot assign requested address。下面针对这种情况给解决方案,有两种解决方案,适用于不同的场景:
一劳永逸-使用pconnect替换connect
这种方案的思路是用长连接替代短连接,减少TCP连接,同时可以避免每次请求建连,减少延时。
之前连接Redis的代码是:
$redis->connect(\'inst-name.redis.rds.aliyuncs.com\', 6379);$redis->auth(\'inst-password\');
修改为pconnect,使用persistent connection:
// phpredis >= 5.3.0, 强烈建议这种pconnect初始化方式,避免断连时出现no auth// timeout,persistent_id,retry_interval,read_timeout等参数根据业务实现情况修改// 官方文档:https://github.com/phpredis/phpredis#pconnect-popen// r e d i s − > c o n n e c t ( i ˊ n s t − n a m e . r e d i s . r d s . a l i y u n c s . c o m , ˊ 6379 ) ; redis->connect(\'inst-name.redis.rds.aliyuncs.com\', 6379); redis−>connect(iˊnst−name.redis.rds.aliyuncs.com,ˊ6379);redis->pconnect(‘inst-name.redis.rds.aliyuncs.com’, 6379, 0, NULL, 0, 0, [‘auth’ => [‘inst-password’]]);
无奈之选-修改客户端所在ECS内核参数tcp_max_tw_buckets
这种方案的思路是直接复用处于TIME-WAIT状态的端口,但是如果服务端因为重传对应五元组仍然处于LAST-ACK状态时,建连会失败,所以强烈建议pconnect的方案。
对于一些场景(比如说业务代码牵涉过多组件不易变更等),需要更快的方式来满足高并发的场景,可以选择修改内核参数tcp_max_tw_buckets,避免出现Cannot assign requested address错误。
查看ip_local_port_range和tcp_max_tw_buckets
$sysctl net.ipv4.tcp_max_tw_buckets net.ipv4.ip_local_port_range
net.ipv4.tcp_max_tw_buckets = 262144net.ipv4.ip_local_port_range = 32768 61000
修改tcp_max_tw_buckets,保证tcp_max_tw_buckets比ip_local_port_range小
sysctl -w net.ipv4.tcp_max_tw_buckets=10000
请忽略所有修改tcp_tw_reuse、tcp_tw_recycle的方法,这些方法对于使用了nat/lvs的服务均不适用(tcp_tw_recycle在Linux 4.12上已经被弃用)。