直接同步调用 $this->email->send() 发大量邮件会导致请求阻塞、SMTP 频繁连接、失败不可重试;Redis 队列通过 CLI 守护进程 + LPUSH/BRPOP 实现异步、可重试、易监控的轻量级邮件分发,是生产环境的底线要求。

为什么不能直接在控制器里调用 $this->email->send() 发大量邮件
直接同步发送会导致请求阻塞,用户等十几秒甚至超时;SMTP 连接频繁建立/断开,服务器负载飙升;失败邮件无法重试,错误日志分散难追踪。队列不是“锦上添花”,而是生产环境发批量邮件的底线要求。
用 CLI 命令 + Redis 实现轻量级队列的核心三步
CodeIgniter 本身不带消息队列组件,但用原生 PHP CLI + Redis 就能搭出稳定可用的队列层,无需引入 RabbitMQ 或 Redis Streams 等重型方案。
- 在
application/views/cli/下新建Email_queue.php,继承CI_Controller(注意不是BASE_Controller,除非你项目已自定义) - 用
$this->redis->lpop('email:queue')拉取待发邮件(JSON 字符串),避免轮询;失败时用$this->redis->lpush('email:queue:failed', $data)入错队列 - 守护进程启动命令必须加
nohup和重定向,例如:nohup /usr/bin/php index.php cli email_queue worker >> /var/log/ci-email-worker.log 2>&1 &;漏掉2>&1会导致 stderr 错误丢失
$this->email->initialize() 在队列中必须每次重置
复用同一个 $this->email 实例发多封邮件,会因 header 缓存、from 地址残留、附件未清空等问题导致后续邮件发错或失败。每次处理队列项前,必须显式调用:
$config = [
'protocol' => 'smtp',
'smtp_host' => 'smtp.qq.com',
'smtp_user' => 'xxx@qq.com',
'smtp_pass' => 'xxxxxx',
'smtp_port' => 587,
'smtp_crypto' => 'tls',
'charset' => 'utf-8',
'mailtype' => 'html',
'wordwrap' => true
];
$this->email->initialize($config);
不要依赖 config/email.php 的自动加载——队列进程是 CLI 环境,autoload['libraries'] 中的 email 不会自动带配置初始化。
Redis stop signal 和 heartbeat 必须手动维护
没人告诉你的是:CLI 进程一旦启动,就脱离 Web 请求生命周期,没法靠 HTTP 超时或 Nginx kill 掉。必须自己管“生死”。
- 用
$this->redis->setex($this->stop_signal, 300, '0')设置 5 分钟过期的开关,管理后台点“停止”就改值为'1' - 每处理完一批(比如 10 封),执行
$this->redis->setex($this->last_time, 600, time()),监控脚本查这个 key 是否超时更新,就能发现进程卡死 - 别用
sleep(1)空转等任务——它会让进程无法响应 stop signal;改用usleep(100000)(100ms)+ 每次循环开头检查$this->redis->get($this->stop_signal) === '1'
没做这一步,队列进程可能跑着跑着就僵住,而你还在后台页面点“启动”——其实早有另一个实例在偷偷发重复邮件。











