一文了解Node.js中的多进程模型

本篇文章给大家介绍一下node.js中的多进程,了解cluster 模块、egg.js 多进程模型,希望对大家有所帮助!

一文了解Node.js中的多进程模型

众所周知,JS是单线程执行的,所有的异步都是靠事件循环完成的,如果一个 Web 服务仅有一个线程,那么如何充分利用机器或容器的闲置资源呢?同时,当代码 Crash 之后没有进行捕获异常,那么线程就会退出,那么基于 Node.js 的 Web 服务是如何保证整个应用的健壮性的呢?

Cluster 模块

Node.js 提供了 Cluster 模块解决上述问题,通过该模块,开发者可以通过创建子进程的模式创建一个集群,充分利用机器或容器的资源,同时该模块允许多个子进程监听同一个端口。【推荐学习:《nodejs 教程》】

示例

const cluster = require('cluster');const http = require('http');const numCPUs = require('os').cpus().length;if (cluster.isMaster) {  // Fork workers.  for (let i = 0; i < numCPUs; i++) {    cluster.fork();  }  cluster.on('exit', function(worker, code, signal) {    console.log('worker ' + worker.process.pid + ' died');  });} else {  // Workers can share any TCP connection  // In this case it is an HTTP server  http.createServer(function(req, res) {    res.writeHead(200);    res.end("hello worldn");  }).listen(8000);}

通过代码解析创建子进程的过程

首先从 const cluster = require('cluster') 说起,这行代码导入了 Node 的 Cluster 模块,而在 Node 内部,Master 进程与 Worker 进程引入的文件却不一样,详情见如下代码:

'use strict';const childOrPrimary = 'NODE_UNIQUE_ID' in process.env ? 'child' : 'master';module.exports = require(`internal/cluster/${childOrPrimary}`);

不同的文件意味着两种进程在执行中的表现也不一样,例如:

// internal/cluster/master.jscluster.isWorker = false;cluster.isMaster = true;// internal/cluster/child.jscluster.isWorker = true;cluster.isMaster = false;

这也是为什么 Cluster 模块到处的变量能区分不同类型进程的原因,接下来让我们分别从主、子进程两个方向去了解具体的过程

主进程

在上述代码里,Master 进程并没有做太多事情,只是根据 CPU 数量去 fork 子进程,那么我们深入到源代码里大致来看一下,相关描述均在代码的注释内

// lib/internal/cluster/master.js// 初始化clusterconst cluster = new EventEmitter();// 创建监听地址与server对应的mapconst handles = new SafeMap();// 初始化cluster.isWorker = false;cluster.isMaster = true;cluster.workers = {};cluster.settings = {};cluster.SCHED_NONE = SCHED_NONE;  // Leave it to the operating system.cluster.SCHED_RR = SCHED_RR;      // Master distributes connections.// 自增的子进程idlet ids = 0;// 向cluster添加fork方法cluster.fork = function(env) {  // 初始化cluster.settings  cluster.setupMaster();  // 为当前fork的子进程生成当前cluster内的唯一id  const id = ++ids;  // 创建子进程  const workerProcess = createWorkerProcess(id, env);  // 创建对应的worker实例  const worker = new Worker({    id: id,    process: workerProcess  });    // 省略一些worker的事件监听....  // 监听内部消息事件,并交由onmessage处理  worker.process.on('internalMessage', internal(worker, onmessage));  // cluster发出fork事件  process.nextTick(emitForkNT, worker);  // 将worker实例放在cluster.workers中维护  cluster.workers[worker.id] = worker;  // 返回worker  return worker;};// 创建子进程函数function createWorkerProcess(id, env) {  // 将主进程的env、调用cluster.fork时传入的env以及NODE_UNIQUE_ID env构建成一个env对象  const workerEnv = { ...process.env, ...env, NODE_UNIQUE_ID: `${id}` };  // 执行参数  const execArgv = [...cluster.settings.execArgv];  // 省略debug模式相关逻辑...  // 调用child_process模块的fork函数创建子进程并返回,至此子进程实例创建完成  return fork(cluster.settings.exec, cluster.settings.args, {    cwd: cluster.settings.cwd,    env: workerEnv,    serialization: cluster.settings.serialization,    silent: cluster.settings.silent,    windowsHide: cluster.settings.windowsHide,    execArgv: execArgv,    stdio: cluster.settings.stdio,    gid: cluster.settings.gid,    uid: cluster.settings.uid  });}// 内部消息事件处理函数function onmessage(message, handle) {  const worker = this;  if (message.act === 'online')    online(worker);  // 当子进程向主进程发出queryServer消息后,执行queryServer函数,创建server  else if (message.act === 'queryServer')    queryServer(worker, message);  else if (message.act === 'listening')    listening(worker, message);  else if (message.act === 'exitedAfterDisconnect')    exitedAfterDisconnect(worker, message);  else if (message.act === 'close')    close(worker, message);}// 获取serverfunction queryServer(worker, message) {  // Stop processing if worker already disconnecting  if (worker.exitedAfterDisconnect)    return;  // 创建当前子进程监听地址信息的key  const key = `${message.address}:${message.port}:${message.addressType}:` +              `${message.fd}:${message.index}`;  // 在handles map中查询是否有已经创建好的该监听地址的server  let handle = handles.get(key);  // 没有对应的server则进行创建  if (handle === undefined) {    let address = message.address;    // Find shortest path for unix sockets because of the ~100 byte limit    if (message.port < 0 && typeof address === 'string' &&        process.platform !== 'win32') {      address = path.relative(process.cwd(), address);      if (message.address.length 
{ const { data } = handles.get(key); if (errno) handles.delete(key); // Gives other workers a chance to retry. send(worker, { errno, key, ack: message.seq, data, ...reply }, handle); });}
// lib/internal/cluster/round_robin_handle.js// 构造函数,参数为server对应的key,ip地址(对于http(s)来说),监听相关信息function RoundRobinHandle(key, address, { port, fd, flags }) {  // 初始化handle  this.key = key;  this.all = new SafeMap();  this.free = new SafeMap();  this.handles = [];  this.handle = null;  this.server = net.createServer(assert.fail);  // 监听文件描述符,不讨论  if (fd >= 0)    this.server.listen({ fd });  // 监听ip:port  else if (port >= 0) {    this.server.listen({      port,      host: address,      // Currently, net module only supports `ipv6Only` option in `flags`.      ipv6Only: Boolean(flags & constants.UV_TCP_IPV6ONLY),    });  // 监听UNIX socket,不讨论  } else    this.server.listen(address);  // UNIX socket path.  // 注册server发出listening事件的回调函数  this.server.once('listening', () => {    this.handle = this.server._handle;    this.handle.onconnection = (err, handle) => this.distribute(err, handle);    this.server._handle = null;    this.server = null;  });}// 添加worker,server发出listening事件后调用master.js中传入的回调函数RoundRobinHandle.prototype.add = function(worker, send) {  assert(this.all.has(worker.id) === false);  this.all.set(worker.id, worker);  const done = () => {    if (this.handle.getsockname) {      const out = {};      this.handle.getsockname(out);      // TODO(bnoordhuis) Check err.      send(null, { sockname: out }, null);    } else {      send(null, null, null);  // UNIX socket.    }    this.handoff(worker);  // In case there are connections pending.  };  if (this.server === null)    return done();  // Still busy binding.  this.server.once('listening', done);  this.server.once('error', (err) => {    send(err.errno, null);  });};// 删除worker,轮询时不再分配给该workerRoundRobinHandle.prototype.remove = function(worker) {  const existed = this.all.delete(worker.id);  if (!existed)    return false;  this.free.delete(worker.id);  if (this.all.size !== 0)    return false;  for (const handle of this.handles) {    handle.close();  }  this.handles = [];  this.handle.close();  this.handle = null;  return true;};// 轮询调度函数RoundRobinHandle.prototype.distribute = function(err, handle) {  ArrayPrototypePush(this.handles, handle);  const [ workerEntry ] = this.free; // this.free is a SafeMap  if (ArrayIsArray(workerEntry)) {    const { 0: workerId, 1: worker } = workerEntry;    this.free.delete(workerId);    this.handoff(worker);  }};// 将handle交给workerRoundRobinHandle.prototype.handoff = function(worker) {  if (!this.all.has(worker.id)) {    return;  // Worker is closing (or has closed) the server.  }  const handle = ArrayPrototypeShift(this.handles);  if (handle === undefined) {    this.free.set(worker.id, worker);  // Add to ready queue again.    return;  }  // 向该worker发出newconn事件  const message = { act: 'newconn', key: this.key };  sendHelper(worker.process, message, handle, (reply) => {    if (reply.accepted)      handle.close();    else      this.distribute(0, handle);  // Worker is shutting down. Send to another.    this.handoff(worker);  });};

子进程

在每个子进程中,我们都创建了一个 HTTP Server,然后执行 listen 函数监听 8000 端口,而 HTTP Server 实例是由 Net Server 原型链继承得到的,listen 函数即为 Net Server 原型上的 listen 函数,具体如下:

// lib/_http_server.jsfunction Server(options, requestListener) {  ....}ObjectSetPrototypeOf(Server.prototype, net.Server.prototype);ObjectSetPrototypeOf(Server, net.Server);
// lib/net.jsServer.prototype.listen = function(...args) {  // 由于篇幅原因,省略一些参数nomolize和其他监听的处理    // 经过这段逻辑中,会调用listenInCluster函数去真正的监听端口  if (typeof options.port === 'number' || typeof options.port === 'string') {    validatePort(options.port, 'options.port');    backlog = options.backlog || backlogFromArgs;    // start TCP server listening on host:port    if (options.host) {      lookupAndListen(this, options.port | 0, options.host, backlog,                      options.exclusive, flags);    } else { // Undefined host, listens on unspecified address      // Default addressType 4 will be used to search for master server      listenInCluster(this, null, options.port | 0, 4,                      backlog, undefined, options.exclusive);    }    return this;  }    // 省略...};// 集群监听函数function listenInCluster(server, address, port, addressType,                         backlog, fd, exclusive, flags) {  exclusive = !!exclusive;  if (cluster === undefined) cluster = require('cluster');  // 判断是否是master,单进程中cluster.isMaster默认为true,然后进行监听并返回  if (cluster.isMaster || exclusive) {    // Will create a new handle    // _listen2 sets up the listened handle, it is still named like this    // to avoid breaking code that wraps this method    server._listen2(address, port, addressType, backlog, fd, flags);    return;  }  // 在子进程中,会将监听地址信息传入cluster实例中的_getServer函数从而获取一个faux handle  const serverQuery = {    address: address,    port: port,    addressType: addressType,    fd: fd,    flags,  };  // Get the master's server handle, and listen on it  cluster._getServer(server, serverQuery, listenOnMasterHandle);  // 获取net server回调函数,拿到faux handle之后,调用_listen2函数,即setupListenHandle函数  function listenOnMasterHandle(err, handle) {    err = checkBindError(err, port, handle);    if (err) {      const ex = exceptionWithHostPort(err, 'bind', address, port);      return server.emit('error', ex);    }    // Reuse master's server handle    server._handle = handle;    // _listen2 sets up the listened handle, it is still named like this    // to avoid breaking code that wraps this method    server._listen2(address, port, addressType, backlog, fd, flags);  }}// 启用监听handlefunction setupListenHandle(address, port, addressType, backlog, fd, flags) {  debug('setupListenHandle', address, port, addressType, backlog, fd);  // 如同英文注释所说的那样,如果没有监听句柄,则创建,有监听句柄则跳过  // If there is not yet a handle, we need to create one and bind.  // In the case of a server sent via IPC, we don't need to do this.  if (this._handle) {    debug('setupListenHandle: have a handle already');  } else {    debug('setupListenHandle: create a handle');    let rval = null;        // 篇幅原因,创建监听句柄的代码...        this._handle = rval;  }    // 在this上设置的faux handle上设置onconnection函数用于监听连接进入  this._handle.onconnection = onconnection;}

同时,在开始解析的时候我们说过,在引入 Cluster 模块的时候,会根据当前进程的env中是否包含NODE_UNIQUE_ID去判断是否为子进程,若为子进程,则执行 child.js 文件

Tips:IPC 通信中发送的message.cmd的值如果以NODE为前缀,它将响应一个内部事件internalMessage

// lib/internal/cluster/child.js// 初始化const cluster = new EventEmitter();// 存储生成的 faux handleconst handles = new SafeMap();// 存储监听地址与监听地址index的对应关系const indexes = new SafeMap();cluster.isWorker = true;cluster.isMaster = false;cluster.worker = null;cluster.Worker = Worker;// 子进程启动时会执行该函数,进行初始化,同时在执行完毕后,会删除 env 中的 NODE_UNIQUE_ID 环境变量// 详细代码见 lib/internal/bootstrap/pre_excution.js 中的 initializeClusterIPC 函数cluster._setupWorker = function() {  // 初始化worker实例  const worker = new Worker({    id: +process.env.NODE_UNIQUE_ID | 0,    process: process,    state: 'online'  });  cluster.worker = worker;  // 处理断开连接事件  process.once('disconnect', () => {    worker.emit('disconnect');    if (!worker.exitedAfterDisconnect) {      // Unexpected disconnect, master exited, or some such nastiness, so      // worker exits immediately.      process.exit(0);    }  });  // IPC 内部通信事件监听  process.on('internalMessage', internal(worker, onmessage));  send({ act: 'online' });  function onmessage(message, handle) {    // 如果为新连接,则执行 onconnection 函数将得到的句柄传入子进程中启动的HTTP Server    if (message.act === 'newconn')      onconnection(message, handle);    else if (message.act === 'disconnect')      ReflectApply(_disconnect, worker, [true]);  }};// 添加获取server函数,会在net server监听端口时被执行// `obj` is a net#Server or a dgram#Socket object.cluster._getServer = function(obj, options, cb) {  let address = options.address;  // Resolve unix socket paths to absolute paths  if (options.port  {    if (typeof obj._setServerData === 'function')      obj._setServerData(reply.data);    // 根据相应负载均衡handle添加worker时的处理,执行相应的负载均衡代码,并执行 cb 函数    // 轮询是没有传递handle的,对应代码在 RoundRobinHandle.prototype.add 内    if (handle)      shared(reply, handle, indexesKey, cb);  // Shared listen socket.    else      rr(reply, indexesKey, cb);              // Round-robin.  });  obj.once('listening', () => {    cluster.worker.state = 'listening';    const address = obj.address();    message.act = 'listening';    message.port = (address && address.port) || options.port;    send(message);  });};// 创建 faux handle,并保存其对应关系// Round-robin. Master distributes handles across workers.function rr(message, indexesKey, cb) {  if (message.errno)    return cb(message.errno, null);  let key = message.key;  function listen(backlog) {    // TODO(bnoordhuis) Send a message to the master that tells it to    // update the backlog size. The actual backlog should probably be    // the largest requested size by any worker.    return 0;  }  function close() {    // lib/net.js treats server._handle.close() as effectively synchronous.    // That means there is a time window between the call to close() and    // the ack by the master process in which we can still receive handles.    // onconnection() below handles that by sending those handles back to    // the master.    if (key === undefined)      return;    send({ act: 'close', key });    handles.delete(key);    indexes.delete(indexesKey);    key = undefined;  }  function getsockname(out) {    if (key)      ObjectAssign(out, message.sockname);    return 0;  }  // 创建Faux handle  // Faux handle. Mimics a TCPWrap with just enough fidelity to get away  // with it. Fools net.Server into thinking that it's backed by a real  // handle. Use a noop function for ref() and unref() because the control  // channel is going to keep the worker alive anyway.  const handle = { close, listen, ref: noop, unref: noop };  if (message.sockname) {    handle.getsockname = getsockname;  // TCP handles only.  }  assert(handles.has(key) === false);  // 保存faux handle  handles.set(key, handle);  // 执行 net 模块调用 cluster._getServer 函数传进来的回调函数  cb(0, handle);}// 处理请求// Round-robin connection.function onconnection(message, handle) {  // 获取faux handle的key  const key = message.key;  // 获取faux hadle  const server = handles.get(key);  const accepted = server !== undefined;  send({ ack: message.seq, accepted });  // 调用在 net 模块中 setupListenHandle 函数里为该 faux handle 设置的连接处理函数处理请求  if (accepted)    server.onconnection(0, handle);}

至此,所有的内容都联系起来了。

文心大模型 文心大模型

百度飞桨-文心大模型 ERNIE 3.0 文本理解与创作

文心大模型 56 查看详情 文心大模型

为什么多个子进程可以监听同一个端口

在之前的代码分析中我们可以知道,Cluster 集群会在 Master 进程中创建 Net Server,在 Worker 进程运行创建 HTTP Server 的时候,会将监听地址的信息传入 cluster._getServer 函数创建一个 faux handle 并设置到子进程的 Net Server 上,在 Worker 进程初始化的时候会注册 IPC 通信回调函数,在回调函数内 ,调用在子进程中 Net Server 模块初始化后的 {faux handle}.onconnection 函数,并将传过来的连接的 handle 传入完成请求响应。

如何保证集群工作的健壮性

我们可以在 Master 进程中监听 Worker 进程的 errordisconntectexit 事件,在这些事件中去做对应的处理,例如清理退出的进程并重新 fork,或者使用已经封装好的 npm 包,例如 cfork

Egg.js 多进程模型

在 Egg.js 的多进程模型中,多了另外一个进程类型,即 Agent 进程,该进程主要用于处理多进程不好处理的一些事情还有减少长链接的数量,具体关系如下:

+---------+           +---------+          +---------+|  Master |           |  Agent  |          |  Worker |+---------+           +----+----+          +----+----+     |      fork agent     |                    |     +-------------------->|                    |     |      agent ready    |                    |     ||     |     worker ready    |                    |     ||                    |     |      Egg ready      |                    |     +----------------------------------------->|

egg-cluster 包内,使用了 cfork 包去保证 Worker 进程挂掉后自动重启

问题记录

在我们的一个 Egg 应用内,日志系统并没有使用 Egg 原生的日志,使用了一个内部基于 log4js 包的日志库,在使用的时候,将需要用到的 Logger 扩展至 Application 对象上,这样的话每个 Worker 进程在初始化的时候都会创建新的 Logger,也就是会存在多进程写日志的问题,但是并没有出现多进程写日志的错误问题

在追踪源码的过程中发现,log4js 虽然提供了 Cluster 模式,但是在上层封装中并没有开启 log4js 的 Cluster 模式,所以每个 Logger 的 appender 都使用 flag a 打开一个写入流,到这里并没有得到答案

后来在 CNode 中找到了答案,在 unix 下使用 flag a 打开的可写流对应的 libuv 文件池实现是 UV_FS_O_APPEND,即 O_APPEND,而 O_APPEND 本身在 man 手册里就定义为原子操作,内核保证了对这个可写流的并发写是安全的不需要在应用层额外加锁(除了在 NFS 类的文件系统上并发写会造成文件信息丢失或者损坏),NFS 类的网络挂载的文件系统主要是靠模拟掉底层的 api 来实现的类本地操作,显然无法在竞争条件下完美还原这类的原子操作 api,所以如果你的日志要写到类似 oss 云盘挂载本地的这种就不能这么干,多进程写的话必须在应用层自己手动加锁

结语

宝剑锋从磨砺出,梅花香自苦寒来,冲鸭~

更多编程相关知识,请访问:编程入门!!

以上就是一文了解Node.js中的多进程模型的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/559272.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月10日 00:14:38
下一篇 2025年11月10日 00:15:48

相关推荐

  • Daeler Node (DNODE) 币是什么?如何运作?2026–2030年价格预测

    加密货币和人工智能的世界正在快速演变,dealer node (dnode) 已成为最令人瞩目的新基础设施解决方案之一。 Binance币安 欧易OKX ️ Huobi火币️ 与其提供另一个区块链、代币或模糊承诺的L2,DNODE更专注于一些非常实用的东西:去中心化运算,这开发者和自主代理可以立即使…

    2025年12月12日
    000
  • Near Protocol (NEAR)币生态解析_未来五年投资价值预测

    Near Protocol凭借Nightshade分片技术实现高吞吐量,支持大规模DApp运行;通过OmniBridge跨链桥接提升互操作性;基金会设立专项基金激励开发者;采用PoS机制支持质押与治理;倡导使用硬件账户等安全实践保障资产安全。 Near Protocol是一个高性能的Layer 1区…

    2025年12月11日
    200
  • 怎么学习链上开发_开发者应该怎么从HelloWorld开始

    1、配置Node.js与npm后,安装Hardhat并初始化项目;2、用Solidity编写HelloWorld合约,定义返回“Hello, World!”的只读函数;3、配置编译器版本并编译合约,编写测试脚本验证逻辑正确性;4、启动本地节点部署合约,记录地址用于交互;5、通过控制台连接网络,调用合…

    2025年12月11日
    000
  • 怎么学习ZK领域_用户应该怎么从零建立密码学认知

    学习ZK需先掌握密码学基础,理解零知识证明的三大特性,熟悉SNARKs与STARKs架构,通过circom和snarkjs实践电路设计与链上验证,并参与开源项目以深化工程认知。 Binance币安 欧易OKX ️ Huobi火币️ 学习ZK领域需要系统性地建立密码学基础,理解零知识证明的核心逻辑与应…

    2025年12月11日
    000
  • ETH智能合约,赋能DeFi新生态

    以太坊(ethereum),这个名字在加密货币世界中早已如雷贯耳,不仅仅是因为其市值仅次于比特币,更是因为它开创性地引入了智能合约(smart contract)这一颠覆性概念。智能合约的出现,让区块链技术从单纯的价值存储和转移,跃升为能够承载复杂逻辑和自动化执行的平台,为去中心化应用(dapp)的…

    好文分享 2025年12月11日
    000
  • 自动化交易:解放双手,实现智能投资

    在瞬息万变的金融市场中,自动化交易正逐渐成为投资者追逐高效与便捷的利器。它不仅仅是一种技术手段,更是一种投资理念的升华,旨在将交易策略的执行从繁琐的人工操作中解放出来,转变为由程序驱动的精准行动。想象一下,当市场波动剧烈,您却无需时刻盯盘,您的投资组合依然能按照预设的规则进行买卖,把握稍纵即逝的交易…

    好文分享 2025年12月11日
    000
  • 通过PayPal订单详情API获取Payer信息与交易详情

    本文旨在指导开发者如何通过PayPal的订单详情API(Order Details API)获取完整的交易数据和付款人(Payer)信息,特别是当PayPal仅返回Payer ID时。教程将详细阐述如何利用订单ID(而非Payer ID)构建API请求、处理响应,并提取包括电子邮件地址在内的关键付款…

    2025年12月11日
    000
  • 开源PHP开发工具 PHP开发必备实用工具合集

    答案:开源PHP开发工具能显著提升开发效率与代码质量。推荐工具包括VS Code和PhpStorm作为代码编辑器,Xdebug与Kint用于调试,Composer管理依赖,Laravel和Symfony作为主流框架,%ignore_a_1%MyAdmin和Sequel Pro管理数据库,PHPSta…

    2025年12月11日
    000
  • 在CPanel/共享主机上部署PHP WebSocket服务的挑战与替代方案

    本文探讨了在CPanel共享主机环境中部署PHP WebSocket服务的固有挑战。由于共享主机通常不提供专用端口或高级服务器配置,直接运行WebSocket守护进程几乎不可行。文章将详细解释这些技术限制,并推荐使用虚拟专用服务器(VPS)作为实现WebSocket功能的有效替代方案,确保实时应用稳…

    2025年12月11日
    000
  • PHP如何处理多线程?通过pthreads扩展实现并发

    PHP本身是单线程的,但可通过pthreads扩展在CLI下实现多线程,需ZTS支持,其核心为共享内存的并发模型,适用于CPU密集任务;相比多进程(隔离性好但开销大)和异步IO(适合IO密集场景),pthreads虽高效但存在数据同步、竞态、死锁等难题,且自PHP 7.3起不再维护,社区转向Swoo…

    2025年12月11日
    000
  • PHP如何处理多数据库连接?通过PDO切换不同数据库

    通过实例化多个PDO对象可实现PHP多数据库连接管理,核心是为每个数据库创建独立连接实例并集中配置、按需使用。 PHP通过实例化多个PDO对象来处理多数据库连接,每个对象代表一个独立的数据库会话。这意味着你可以在同一脚本中同时连接到不同的数据库,并在需要时通过选择对应的PDO对象来执行操作。核心在于…

    2025年12月11日
    000
  • 开源免费PHP工具 PHP开发效率提升利器

    推荐开源免费PHP开发工具以提升效率:VS Code、Sublime Text轻量高效,PhpStorm专业强大;调试用Xdebug、Kint、Ray;依赖管理选Composer;代码质量工具包括PHPStan、Psalm、PHP_CodeSniffer;数据库管理可用%ignore_a_1%MyA…

    2025年12月11日
    000
  • Bitfinex:专业交易

    在加密货币交易的浩瀚宇宙中,bitfinex无疑是其中一颗耀眼的星辰。它不仅仅是一个简单的交易所,更是一个为专业交易者量身定制的复杂生态系统。踏入bitfinex的大门,你将发现一个集高流动性、先进交易工具、深度市场数据以及强大安全保障于一体的交易殿堂。这里汇聚了全球顶级的机构投资者、资深交易员以及…

    好文分享 2025年12月10日
    000
  • php如何实现一个基于令牌的认证系统 php Token-Based认证流程与实现

    PHP基于JWT实现无状态认证,通过生成、传输和验证自包含令牌完成用户身份验证。用户登录后服务器生成带签名的JWT,客户端存储并将其放入Authorization头发送,服务端验证签名及有效期后授权访问。JWT由Header、Payload、Signature三部分组成,具备无状态、自包含、安全性和…

    2025年12月10日
    000
  • PHP如何使用Composer来管理项目依赖_PHP Composer依赖管理教程

    Composer通过composer.json管理PHP项目依赖,实现自动加载与版本控制,解决手动管理混乱、版本冲突等问题。安装后使用composer init初始化,composer require添加依赖,composer install/composer update管理安装与更新,配合aut…

    2025年12月10日
    000
  • 理解PHP Web应用中的静态变量、请求生命周期与数据持久化策略

    在PHP Web环境中,每次HTTP请求都会创建一个全新的、独立的执行环境,因此静态变量的值不会在不同请求之间保持。本文深入探讨了PHP中静态变量和方法的行为,解释了Web应用中数据持久化的必要性,并提供了通过数据库和会话管理实现数据持久化的策略,同时对比了Node.js等其他环境的差异。 1. P…

    2025年12月10日
    000
  • PHP 中的静态方法和变量:Web 开发中的持久化问题

    本文旨在阐明 PHP Web 开发中静态方法和变量的特性,以及它们在多用户环境下的适用性。重点强调了 PHP 请求的生命周期,解释了为何静态变量无法在不同请求之间保持状态。同时,介绍了如何在 Web 应用中持久化数据,并对比了 Node.js 等其他环境下的行为差异,为开发者提供了清晰的数据持久化方…

    2025年12月10日
    000
  • PHP Web环境中静态变量的行为、陷阱与数据持久化

    在PHP Web环境中,每次HTTP请求都会创建一个全新的、短生命周期的执行环境。这意味着静态变量的值不会在不同请求或不同用户之间共享或持久化。因此,将敏感信息如支付数据存储在静态变量中不会导致多用户冲突,但也不会实现数据持久化。为实现跨请求或跨用户的数据持久性,应采用数据库或会话机制。本文将深入探…

    2025年12月10日
    000
  • 深入理解Web环境中PHP静态变量与数据持久化策略

    本文深入探讨了PHP在Web环境下静态变量的工作机制,指出其值不会跨HTTP请求持久化,因此不会对多用户平台造成数据混淆。文章强调了对于需要持久化存储的数据,应采用数据库或会话(Session)等外部存储方案,并简要对比了不同编程环境下的差异,提供了数据持久化的通用指导原则。 PHP Web环境下的…

    2025年12月10日
    000
  • 深入理解Web环境中静态变量的工作原理与数据持久化策略

    本文深入探讨了PHP等Web环境中静态变量的运作机制,解释了为何它们不适用于多用户平台的数据持久化。文章详细阐述了Web请求的无状态特性,并提供了数据库和会话数据作为实现数据持久化的核心策略,同时简要提及了Node.js等不同环境下的行为差异及其通用解决方案。 Web环境的无状态本质与静态变量 在理…

    2025年12月10日
    000

发表回复

登录后才能评论
关注微信