Docker中PHP连接Redis压测1.5万请求后失败求助
Docker PHP+Redis压测端口耗尽问题解决
问题背景
基于Docker部署PHP+MySQL+Redis应用,单请求流程为:访问PHP接口→打开Redis连接→读写操作→关闭连接。应用正常运行,但用k6压测(QPS 500-800)时,约1.4-1.5万次请求后出现Uncaught RedisException: Cannot assign requested address错误,Redis CPU使用率从30%降至0。尝试使用twemproxy代理未解决问题。
相关配置如下:
Dockerfile
FROM unit:1.34.1-php8.4 ENV PHP_OPCACHE_VALIDATE_TIMESTAMPS="0" \ PHP_OPCACHE_MAX_ACCELERATED_FILES="10000" \ PHP_OPCACHE_MEMORY_CONSUMPTION="192" \ PHP_OPCACHE_MAX_WASTED_PERCENTAGE="10" RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli # ...处理opcache、supervisor及其他依赖... EXPOSE 80 CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
Docker Compose(未用twemproxy)
services: php: build: context: . dockerfile: Dockerfile working_dir: /var/www/html/ container_name: phpApp ports: - '80:80' volumes: - '.:/var/www/html/' depends_on: - mysql_db - redis networks: - default redis: container_name: redis image: 'redis:alpine3.21' ports: - '6379:6379' networks: - default
Docker Compose(添加twemproxy)
twemproxy: build: context: . dockerfile: Dockerfile.twemproxy container_name: twemproxy ports: - '6380:6379' volumes: - ./config/twemproxy.yml:/etc/twemproxy/nutcracker.yml depends_on: - redis networks: - default
twemproxy配置
alpha: listen: 0.0.0.0:6380 hash: fnv1a_64 distribution: ketama redis: true auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 3 servers: - redis:6379:1
压测目标PHP代码
<?php $redis = new Redis(); $redis->connect('redis', 6379,1); // $redis->connect('twemproxy', 6380); // 使用twemproxy时启用 if($redis->exists($serial)){ // 业务逻辑 } $redis->close(); ?>
问题根源
错误本质是PHP容器本地端口耗尽:每次请求新建Redis连接,调用close()后TCP连接进入TIME_WAIT状态(默认持续60秒),短时间内大量请求会占满所有可用本地端口,导致无法创建新连接。Redis CPU降为0是因为没有新连接进入,并非Redis自身故障。
解决方案
1. 改用Redis持久化连接+连接池(最优方案)
放弃每次请求创建/销毁连接的模式,用持久化连接复用连接:
- 使用
pconnect()替代connect(),建立持久化连接 - 将连接初始化逻辑放在全局(如框架启动脚本),而非单请求内
- 示例代码:
<?php // 全局复用连接 static $redis; if (!isset($redis)) { $redis = new Redis(); // 持久化连接,超时时间1秒 $redis->pconnect('redis', 6379, 1); } if($redis->exists($serial)){ // 业务逻辑 } // 不要调用close(),让连接被后续请求复用 ?>
- 注意:配合Unit服务器的Worker进程数配置,避免连接数超过Redis的
maxclients(默认10000)
2. 调整TCP内核参数(缓解端口耗尽)
修改PHP容器的TCP参数,加速TIME_WAIT端口回收:
- 在Docker Compose的php服务中添加
sysctls配置:
services: php: # 其他配置... sysctls: - net.ipv4.tcp_tw_reuse=1 # 允许复用TIME_WAIT状态的端口 - net.ipv4.tcp_fin_timeout=30 # 缩短TIME_WAIT超时至30秒 - net.ipv4.ip_local_port_range="1024 65535" # 扩大本地端口范围
- 或在PHP容器启动脚本中执行:
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf echo "net.ipv4.tcp_fin_timeout = 30" >> /etc/sysctl.conf echo "net.ipv4.ip_local_port_range = 1024 65535" >> /etc/sysctl.conf sysctl -p
3. 优化Redis配置
- 调整
tcp-keepalive参数,及时清理无效连接(在redis.conf中添加):
tcp-keepalive 60
- 若需要,增大
maxclients参数(需确保服务器资源充足)
4. 优化twemproxy配置(若继续使用)
开启twemproxy的连接复用功能,在配置中添加:
alpha: # 其他配置... server_connections: 1000 # 每个后端Redis的复用连接数,根据业务量调整
- 同时PHP端需使用
pconnect()连接twemproxy,才能发挥代理的连接复用作用
内容的提问来源于stack exchange,提问作者aVC
相关产品推荐
相关产品推荐

