You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 23:54:51