进程管理

cli_set_process_title() 用于设置PHP-CLI进程的名称,可在进程名之后添加特殊的标记,再使用 ps -FC 、pgrep、pkill 命令指定标记来查询、杀死对应的进程。

使用信号中断阻塞

当cli脚本中使用了一些不支持超时限制的阻塞IO调用时(例如:UDP消息的接收、SysV消息队列的提取),可利用信号机制来中断被阻塞的函数调用,以实现超时功能。

信号的发送,可以写一个shell脚本循环每隔几秒向指定的进程发送对应的信号(while+sleep+kill);也可以在cli脚本中fork一个子进程出来,其中一个进程负责处理任务,另一个负责发送定时发送信号(pcntl_fork+while+sleep+posix_kill)。

对于stream类的阻塞IO接口(例如 stream_socket_recvfrom ),注册信号回调函数时需要将 pcntl_signal 函数的第三个参数 $restart_syscalls 设置为 false,否则信号中断函数调用后会立刻自动恢复函数的执行,就像没有发生信号一样,继续保持阻塞直到IO操作完成;除了修改第三个参数的值之外,也可以通过 stream_select 函数先阻塞的检测IO状态,再根据其返回值决定是否进行IO操作,而 stream_select 函数支持设置超时限制。

使用信号需要开启ticks,即 declare(ticks = 1),如果不使用大括号限制作用范围,默认为该语句之后的部分,但仅限当前文件内,如果后续有include语句,对被引入的文件中的代码不生效,考虑性能影响 declare 应当通过大括号严格控制范围,例如,大括号内只包括会阻塞的语句。

重启功能的实现

cli脚本中可以设置特定的信号用于实现重启,脚本收到信号之后,调用 pcntl_exec 函数创建与当前进程完全相同的进程(命令行完全相同)来替换掉当前的进程,以此实现进程的重启,因为是新启动了一个完全相同的进程,所以所有新的代码变会都会在新进程中生效。需要注意,在此之前应当做好清理工作,如关闭监听的socket服务、关闭文件锁等,另外,新进程ID与旧进程的保持相同。

通过文件锁控制进程数量

需要常驻的进程需要使用锁文件限制只启动一个实例,可以利用 fopen 函数创建一个锁文件,指定文件的操作模式为 c 或 c+ (如果使用 r 或 r+,文件不存在时不能自动创建而是返回失败;使用 w 或 w+ 时如果文件存在打开的同时会将文件内容长度截断为0,保存的数据会丢失;如果使用 a 或 a+,打开后文件句柄不能用于锁操作;而如果使用 x 或 x+,文件存在会导致打开失败),再使用 flock 进行锁定,需要注意句柄变量释放时会自动调用 close 操作导致解锁,需要使用静态变量或全局变量来保存句柄。

PHP-FPM的网络模型

fpm属于多进程阻塞式IO的网络模型。fpm 服务监听的 socket 文件(网络或Unix域文件)由 fpm-master 进程创建,之后其会 fork 出一定数量的 worker 子进程后,每个子进程都会去尝试阻塞式的争夺同一个文件锁(flock),先获得锁的那个子进程会阻塞式的调用 accept 函数,在 accpet 返回后即获得新建立的客户连接,然后马上解除文件锁,再对客户连接进行读操作和写操作。fpm 以这种简单的方式实现了并发支持,同时省去了 master 进程与 worker 进程之间的通信开销。

Redis的一些新知识

  • HyperLogLogs 结构,一种用于统计DAU的结构(需要根据指定的标识如用户ID、设备ID进行去重统计,不同于 set 它不会记录下来每个标识);
  • Stream 结构,新的队列服务,发布订阅的升级版本,功能更类似于Kafaka的队列服务;
  • Memory 命令,可以用于查看各个KEY占用的内存空间(memory usage)、内存使用建议(memory doctor);
  • 哨兵模式,用于实现基于主从的 Redis 高可用方案,支持通过指定回调shell脚本来实现报警、切换请求地址等功能。

TCP相关的理解

  • 服务方有两个队列,半连接队列和已连接队列,操作系统收到客户端的Sync请求后就会将客户连接先保存至半连接队列,三次握手完成之后,系统将客户连接从半连接队列转移至已连接队列,而应用程序通过 accept 函数调用将已就绪的连接从已连接队列移到应用程序空间进行处理;
  • 每个TCP连接会有两个缓冲区,读缓冲区和写缓冲区,阻塞IO时,是否发生阻塞取决于读缓冲区是否有数据可读、写缓冲区是否有空间可写,读操作实际上只是将数据从读缓冲区读走,而写操作也只是将数据复制到写缓冲区中去,因此 write 函数返回成功,数据也存在发送失败的可能性(操作系统发送失败无法直接通知上层应用);
  • 读写缓冲区还受TCP低水位选项值的影响,该组选项决定读缓冲区有多少数据时被判断为可读、写缓冲区有多少空间可写时为可写,默认值都为1字节,且写的低水位选项值不可改;
  • 本方的写缓冲区大小会通过TCP的Window Size选项值告知对端,从而影响对端单次发送的数据大小;
  • 阻塞读操作时,只要有数据可读,read 函数调用便立即返回,因些读到的数据长度会小于指定的值;但阻塞写操作时,如果要写的数据大于写缓冲区可用空间大小,而会一直阻塞直到所有的数据都复制进写缓冲区;
  • TCP协议中的Flags:FIN – 关闭, SYNC – 同步, RST – 重置, PUSH – 推送, ACK – 确认, URG – 紧急;
  • TCP包没有长度字段,只有序列号,TCP是通过IP包对内容进行拆分的,IP包长度受限于链路层协议,公网MTU一般为1500,MSS等于MTU去掉IP包头和TCP包头后的长度,公网一般为1460,当TCP发送超过MSS的内容时需要将数据分多个IP包发送。