Download the PHP package kode/process without Composer
On this page you can find all versions of the php package kode/process. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Package process
Short Description PHP 8.4+ 进程编排内核:默认自研 Native 运行时(零扩展、master-worker 多进程),兼容 Swoole/Workerman,内置分布式集群能力(服务发现、分布式锁、Leader 选举、负载均衡、分布式 ID、限流、集群 RPC)
License Apache-2.0
Informations about the package process
Kode Process
PHP 8.4+ 多进程编排内核 · 自研 Native 默认运行时 · Swoole / Workerman 可插拔 · 内置分布式集群能力
简介
Kode Process 是一个「进程编排内核 + 多运行时兼容层」:你只写一套 API,底层运行时由环境自动择优,也可显式切换,业务代码零改动——
- 默认自研 Native 运行时(纯 PHP、零扩展依赖、master-worker 多进程),装好即用,无需任何 C 扩展
- 装了 Swoole → 可显式接入(最快,全 C 实现,原生协程),业务代码一行不改
- 装了 Workerman → 纯 PHP 依赖,已写入
require,开箱即用,Linux 上ext-event加速
切换运行时(native / swoole / workerman)只需改一个参数,协议、连接 API、编排原语全部一致。
关于「自研 vs 兼容层」:v5.0.0 起自研 Native 运行时成为默认形态,并补齐分布式协调能力(服务发现、分布式锁、Leader 选举、负载均衡、分布式 ID、限流、集群 RPC), 同时保留 Swoole / Workerman 作为可插拔的高性能运行时。详见 docs/cluster.md。
最低要求 PHP 8.4。若仍在 PHP 8.1 / 8.2 上运行,请使用旧版
^2.9。
特性
| 特性 | 说明 |
|---|---|
| 🔌 一套 API,三种运行时 | 默认自研 Native(纯 PHP、零扩展),Swoole / Workerman 自动择优或显式选用,业务代码零改动 |
| 🧱 进程编排内核 | 复用宿主的 master-worker 模型、监督重启、平滑重载、优雅停机、信号管理 |
| 🔁 统一事件循环 | Kode::loop() 基于 ext-event / ext-ev 加速,stream_select 零扩展兜底 |
| 🌐 多协议 | HTTP、WebSocket、TCP、Text、Unix Socket、SSL(UDP 取决于宿主运行时) |
| 🚀 HTTP/2(h2c) | Native 内置,默认开启、与 HTTP/1.1 同端口自动协商;多路复用 + HPACK + 流控,业务 handler 零改动 |
| 🗜️ HTTP gzip 压缩 | 依据 Accept-Encoding 透明压缩(阈值 1KB),业务零改动;也支持显式 gzip() API |
| 🌊 HTTP chunked 流式响应 | Transfer-Encoding: chunked 跨运行时统一 API;HTTP/2 下由 DATA 帧天然承载 |
| 🛡️ HTTP/2 DoS 四层防护 | Rapid Reset(CVE-2023-44487)预算抵扣 + CONTINUATION 洪泛 + MAX_HEADER_LIST_SIZE 解压后体积上限 + PING/SETTINGS 控制帧洪泛;流级拒绝不拖垮连接,水位可由 stats() 观测 |
| ⚡ HTTP/2 纯函数缓存提速 | HPACK 字面量编码缓存 5.7× + 响应头整块缓存 ≈2.2× + Huffman 解码缓存 ≈1.93× + 解码主循环内联 ≈1.47×(v5.2.6)+ 整头编码缓存 ≈3.4×(v5.2.7)+ 解码字符串读取内联 ≈1.22×(v5.2.9),线格式完全不变;Hpack::decode 较 v5.2.7 基线再快约 21%,请求热路径自 v5.2.3 起累计快约 2.0× |
| 🚀 HTTP/2 大响应线性发送 | 切帧由平方复杂度改为游标推进 + 待发流索引(v5.2.8):1MB 响应端到端吞吐 2.73×、延迟中位数 −67%,WINDOW_UPDATE 耗时与并发流数解耦;线格式逐字节不变 |
| 🔐 协议层严格校验 | HTTP 请求走私(CL/TE 冲突)拒绝 + 响应头 CRLF 注入过滤 + WebSocket 掩码/控制帧/RSV 按 RFC 6455 校验 + HPACK 变长整数与 Huffman 截断收敛为协议错误(不再打死 worker) |
| 🗄️ 共享数据(零安装兜底) | 同主机多进程共享表,apcu → sysvshm 自动择优;也可复用 Swoole/Workerman 表 |
| 🕸️ 分布式集群 | 服务发现、分布式锁、Leader 选举、负载均衡(5 策略)、分布式 ID(Snowflake)、限流、集群 RPC;可零依赖(包内 GlobalData)或基于 Redis |
| 🧵 多线程并行 | 真正的 CPU 多线程(需 ZTS + ext-parallel),与协程桥接 |
| 🪶 协程 | 委托 kode/fibers,单线程 I/O 并发 |
| ⏱️ 定时器 | 一次性、周期、Cron |
| 🏃 常驻进程运行器 (Daemon) | 基于 Process::fork() + Timer 的轻量多进程周期任务运行器,自带监督 / 异常重生 / 优雅退出,避开官方 worker 池回调空转陷阱 |
| 📨 队列 | 委托 kode/queue,内存 / 同步 / Redis / 数据库多后端 |
| 🔒 SSL/TLS | 通过监听选项配置,随宿主运行时生效 |
| 🩺 部署自检 | Kode::diagnose() 一键列出可用运行时 / 事件循环 / 共享表后端 / 并行(ZTS) / 集群后端 能力 |
安装
workerman/workerman 是纯 PHP 依赖、已写入 require,安装后即开箱可用;需要更高吞吐时再装 ext-swoole 即可自动择优到它。
快速开始
HTTP 服务器
WebSocket 服务器
TCP 服务器
Kode::serve() 内部调用 Runtime::auto() 选择最优运行时。若想显式指定:
事件与连接
运行时通过事件回调驱动业务,连接对象统一为 Kode\Process\Runtime\ConnectionInterface:
| 事件 | 回调签名 | 说明 |
|---|---|---|
workerStart |
(int $workerId) |
worker 就绪 |
workerStop |
(int $workerId) |
worker 退出 |
connect |
(ConnectionInterface $conn) |
新连接建立 |
message |
(ConnectionInterface $conn, mixed $data) |
收到一条完整报文 |
close |
(ConnectionInterface $conn) |
连接关闭 |
error |
(?ConnectionInterface $conn, \Throwable $e) |
连接或运行时错误 |
ConnectionInterface 提供与底层无关的操作:
能力探测
不同运行时的能力不同,请用 supports() 做优雅降级:
能力枚举见 Kode\Process\Runtime\Capability:协程、共享表、Task 工作进程、UDP、Unix Socket、
SSL、平滑重载、SO_REUSEPORT、WebSocket、定时器、异步 I/O。
运行时择优与自检
Kode::diagnose() 返回可用运行时、各自版本与优先级、当前事件循环驱动、共享表后端、并行能力
(是否 ZTS、是否可用、后端类型),以及集群后端(可用协调存储、已加入节点);
在 Linux 且未安装 ext-event / ext-ev 时会给出安装建议
(Workerman 官方也推荐 Linux 用 event 循环)。
共享数据(零安装兜底)
跨进程共享数据,无需引入 Swoole / Workerman:
若应用已运行在 Swoole / Workerman 之上,可复用其共享表做兼容共存:
选型建议:能上 Swoole / Workerman 就优先用它们的表(成熟稳定、免维护)。 本库
SharedTable::auto()是「不引入这两者时」的零安装、零依赖兜底。 详见 docs/global-data.md。
并发原语
分布式集群
v5.0.0 内置轻量分布式协调能力,覆盖单主机到多主机场景。所有原语通过统一的协调存储抽象(StoreInterface)驱动,
可零依赖使用包内 GlobalData 后端,或对接 Redis 实现跨主机协调:
集群后端如需跨主机复用 Redis:
详见 docs/cluster.md。
定时器
通过 Kode 门面一行注册定时器,底层复用 Kode\Process\Timer:
也支持 setTimeout / setInterval(JS 风格别名)与 pause / resume / getStatus 等,
详见 docs/timer.md。
多进程定时任务的重复执行问题与选型
⚠️ 关键认知:进程内的
Kode::cron()/Timer::cron()/Crontab是「每进程一份」的静态注册表。 在 master-worker 多进程(或水平扩展多机)下,每个 worker 都会独立注册并触发同一表达式 —— 每个调度时刻会被 N 个 worker 重复执行 N 次,且某个 worker 崩溃即丢失其定时器。 多进程本身并不能解决这个问题,反而把它放大了:要让定时任务在集群内「至多执行一次」, 必须引入协调权威(分布式锁 / Leader 选举),或把活儿交给持久队列。
解法一:每任务分布式锁(推荐起步,零改造)
Kode::cronCluster() / ClusterCron::create() 用 Cluster::lock() 给每个表达式加一把互斥锁,
只有抢到锁的 worker 才执行 —— 多进程不再重复。存储后端自动择优(同机 file、跨机 redis),
同机多进程开箱即可协调,无需任何外部依赖;跨主机先 Cluster::make('redis', ...) 即可。
协调存储不可用时守卫 fail-soft 退化为本地执行并告警(极端情况可能重复,与无协调同款行为)。
每次触发多出一次锁往返(file 后端约 0.07ms,见 benchmarks/cluster-cron-bench.php)。
解法二:Leader 选举(长任务 / 强 exactly-once)
任务耗时可能超过锁 TTL、或要求强一致时,用 Kode::tickCronOnLeader() 让整套 cron 只在选举胜出的
Leader 进程推进,天然 exactly-once:
解法三:用 Redis 队列承接「持久 / 可重试」的活儿
若任务是持久、可重试、崩溃不丢、需要背压/限速/失败重放的(发邮件、对账、下发),
不要依赖进程内 cron/timer —— 它既不持久、崩溃即丢、多进程还重复。正确做法是让 cron 只负责
「产生消息」,真正的执行交给 Kode::queue()(Redis 后端),由队列保证投递语义:
一句话:定时「触发」用集群锁/选举去重;业务「执行」用 Redis 队列保持久。 两者互补,而非替代。
常驻进程运行器 (Daemon)
⚠️ 为什么不直接用
Kode\Process::start($config, $cb)跑周期任务? 官方 worker 池(WorkerProcess)的事件循环里processTasks()从不调用用户回调—— 回调只在外部assignTask()推任务时触发。所以Process::start($config, fn()=>...)传入的回调在 worker 里实际是空转,并不适合「每进程周期执行自定义任务」。要可靠地常驻跑任务,请用本运行器。
Kode::daemon() 只依赖两个原语——Process::fork()(多进程)+ Timer(周期/定时调度)——
自建「监督进程 + N 个 worker 子进程」:worker 在独立事件循环里真正执行你的任务,监督进程负责
信号、回收僵尸、异常退出重生(带上限防 fork bomb)、优雅退出。完全不碰 Master/Worker 池。
daemon 文件(return 一个 Daemon 实例)配合 CLI 子命令管理生命周期:
与「多进程定时任务重复执行」的关系:Daemon 解决的是单机上 N 个 worker 各自可靠地周期跑任务; 若还要跨进程/跨机「同一时刻只跑一次」,仍用
Kode::cronCluster()(集群锁)或Kode::tickCronOnLeader()(Leader 选举)去重,二者可叠加。详见 docs/daemon.md。pid 文件是互斥占位,不是日志。
run()在守护化之前先读它:里面写着一个存活且非本进程的 pid 就抛ProcessException拒绝启动,并且一个字节都不动那个文件。此前是无条件覆盖, 于是第二次kode process start会把第一代的 pid 改成自己的 —— 而 pid 文件是「谁在跑」的唯一来源, 结果第一代查不到、收不到停止/重载信号,却还在跑同一份资源。内容读不出 pid(空/非数字)或 pid 已死, 按未占用接管;退出清理只删写着自己 pid 的文件。 自 v5.5.1 起「判归属 + 落盘」跑在同一把非阻塞排他flock里(锁加在 pid 文件本身,open 模式'c'不截断,写盘用持锁的那只手写):先后两次启动由「文件里写着存活的 pid」拒绝,同时敲两次由 「另一进程正握着锁」拒绝 —— 只判活挡不住后者,因为判定那一刻对方本来就还没写。
队列系统
队列由 kode/queue 提供,支持内存 / 同步 / Redis / 数据库多后端。详见 docs/queue.md。
信号、监控与进程内事件
信号处理器为单例,详见 docs/signal.md。
SSL/TLS
通过监听选项配置,随宿主运行时生效(需 ext-openssl):
命令行工具
bin/kode 是便捷启动器,运行你提供的服务脚本(脚本内调用 Kode::serve(...)->start()):
运行中的服务也可直接用信号控制:
| 信号 | 作用 |
|---|---|
SIGTERM |
优雅停机 |
SIGUSR1 |
平滑重载(Workerman / Swoole,不中断连接) |
SIGINT / SIGQUIT |
停机 |
文档
- 安装
- 快速开始
- 运行时兼容层(架构与 API)
- 事件循环(Reactor)
- 协议系统
- HTTP/2
- 共享数据
- 并行(多线程)
- 定时器
- 常驻进程运行器 (Daemon)
- 队列系统
- 信号管理
- 监控(进程 / 心跳 / 文件)
- 生产部署
- 分布式集群
- 压测数据
项目结构
测试
版本要点
v5.5.1
- pid 文件互斥现在真的锁得住「同时敲两次」。 v5.5.0 的
claimPidFile()只做「读 → 判 → 返回」,写盘发生在它返回之后、由file_put_contents()另开一个 fd 完成 —— 两步之间没有任何互斥。实测在同一台机器上同时敲两次php kode process:start:两边都在对方落盘之前读到「没人占用」,于是真起出两套守护进程(两个 master 各带 worker),而 pid 文件里只留下其中一个 —— 另一套从状态表上永久隐形,stop/reload都摸不到它。判活也挡不住这种交错,因为判定那一刻对方本来就还没写。 - 现在「判归属 + 落盘」跑在同一把非阻塞排他 flock 里(新增
withPidLock(),判定本体拆成assertPidFileUnowned()):锁加在 pid 文件本身而不是旁边的*.lock(路径只有一份,读侧与测试拿getPidFile()就能占到同一个锁),open 用'c'(不存在就建、存在不截断 —— 截断会把别人的占位抹成空文件,而空文件按「未占用」处理,等于用一次 open 把活着的实例赶走),写盘用持锁的那只手写(ftruncate+fwrite;另开 fd 等于绕过自己的锁)。抢不到锁立刻抛「另一个进程正在启动并占用 …(并发启动)」,不阻塞 supervise 入口;打不开该路径同样抛(别让「写不下去」伪装成「没人占用」)。 - 新增两例回归:
testRefusesToClaimWhileAnotherProcessHoldsThePidFile(别人握着锁时 claim 必须拒绝、锁放掉后必须能照常认领、成功路径不得把锁留在自己手里)、testWriteRefusesWhileAnotherProcessHoldsThePidFile(落盘那一步也必须在锁内,别人握着锁时一个字节都不许动)。三条腿各配变异验证:去掉 flock 判定、把落盘挪出锁、open 改成'w'—— 每一种都必须有对应用例变红。
v5.5.0
Daemon的 pid 文件从此是互斥占位。(并发启动除外:这一版只挡住「先后两次」,同时敲两次仍会叠套,v5.5.1 才补上锁 —— 见上。)run()此前无条件file_put_contents(),第二次启动会把第一代的 pid 覆盖成自己的 —— 而 pid 文件是「谁在跑」的唯一来源,于是第一代守护进程(连同它的 worker 子进程)永久查不到、收不到停止/重载信号,却还在跑同一份资源;kode process start每跑一次就多叠一套。现在新增claimPidFile()一份判据:文件里写着存活且非本进程的 pid 就抛ProcessException拒绝启动并原样保留文件,失效 pid/内容读不出 pid/文件不存在一律接管。判定排在daemonize()之前(守护化后父进程已 exit,在那之后失败等于「静默启动失败」)。cleanup()不再无条件unlink:只删写着自己 pid 的文件。此前第二个实例退出时会把第一个实例的登记抹掉,与「覆盖」是同一个后果。- pid 落盘失败现在抛错而不是静默裸跑:写不下去 = 这一代在状态表里查不到,互斥也随之失效。
- 新增
tests/DaemonPidFileTest10 例(存活占用/自己重入/失效接管/垃圾内容/文件缺失/退出清理不抢删/run() 在派生 worker 前拒绝/占位排在守护化之前)。
v5.4.0
- HTTP 请求终于带来路 IP 了。
Http\Request::ip()读的是attributes['remote_addr'],而这个键此前没有任何运行时写过 —— 三个运行时交付给 handler 的请求里ip()恒为空串。后果不止缺一列:所有「按来路 IP 记账」的能力(限流、访问日志、审计、登录防爆破)会把全站客户端塌进同一个桶,等于一个共享预算,谁都能把别人锁在门外。现在由运行时在交付请求时统一盖戳:Native 取 accept 时的 peer 名(HTTP/2 子流继承父连接),Workerman 取getRemoteIp(),Swoole 从swoole_dispatch的server.remote_addr带出。 - 新增
Request::setRemoteAddress(string):运行时/自建服务盖来路 IP 的唯一入口;Request::ipOf(string):从 peer 串(1.2.3.4:52341、[::1]:9527、unix 套接字路径)里取出地址段,纯函数、不抛错,拿不准就原样返回。 NativeConnection::remoteIp():解析一次并缓存,避免每个请求重切字符串。ip()语义写进文档:空串表示「运行时没给」,不要当作本地回环处理;反代场景仍走ip(true)+ 可信代理白名单。
v5.3.1
- 修正版本常量漂移:
Version::MAJOR/MINOR/PATCH/VERSION/VERSION_ID停在 5.2.36,而包已发布到 5.3.0,运行时信息(NativeRuntime::version()等)对外报旧版本。新增tests/VersionGuardTest双向守卫:常量组 == composer.json 的 version,且VERSION_ID == MAJOR*10000 + MINOR*100 + PATCH。行为无变化。
v5.3.0
- Master 侧
WorkerProcess代理对象的stop()此前直接空转(running仅子进程内置位),池停止/缩容时对真实子进程不发信号不回收,留下孤儿进程;现在会发 TERM/KILL、限时等待并只回收自家子进程(pcntl_waitpid(pid, WNOHANG)),不与MasterProcess::reapChildren抢收。 MasterProcess::start()监听失败现在回滚 PID 文件并复位状态(否则残留 PID 文件让下次启动误判「已在运行」);max_restart_attempts配置此前从未生效,现已接入。serverSocket属性与getServerSocket()返回类型改为?\Socket(socket_create()返回对象,原?int标注使一切 port 配置启动即 TypeError);bind/listen 失败异常携带 strerror 且不再双打 PHP 告警。- 守护化重挂的 0/1/2 号流现在持有引用(局部变量出作用域即被 GC 关闭),
rotateLog()轮转后重开日志文件,避免继续写入已改名的旧 inode。 Process::wait()按终止类别取退出状态:被信号杀死的子进程不再把信号编号当退出码返回。ProcessManager:max_requests_per_worker / max_memory_per_worker / worker_timeout / restart_delay / max_restart_attempts现在真正传递到 WorkerFactory 与 MasterProcess(此前静默走内置默认);restart()现在用保存的回调真正重新拉起,而不是只停不起。Timer::cron()文档明确:5 字段纯 AND 匹配,未实现 Vixie 的 day-of-month/day-of-week OR 规则(需该语义用 kode/scheduling 的 Cron)。WorkerProcess::heartbeat()/assignTask()文档明确各自陷阱:master 侧心跳是 fork 时冻结副本(超时后overdue恒真);assignTask()的回调在调用方进程同步执行,不会投递给子进程。- 测试:
TimerTest硬编码月份的断言改为按「下一个 4 日 03:17」动态推导(此前跨月即假失败);新增ProcessFixesTest6 例回归。
许可证
Apache License 2.0
All versions of process with dependencies
ext-pcntl Version *
ext-posix Version *
ext-sockets Version *
kode/context Version ^4
kode/fibers Version ^5
kode/queue Version ^3
psr/log Version ^2.0|^3.0
psr/http-message Version ^1.0|^2.0
workerman/workerman Version ^5.0