温故而知新,PHP Swoole使用过程中的一些思考
- PHP笔记
- 1小时前
- 10热度
- 0评论
温故而知新
今天突然发现很多以前理解过的概念,再次回顾的时候感觉有点模糊了,翻了翻笔记又有了一些新的理解。
1.场景
- 简单的异步任务执行可以直接通过管道向子进程投递异步任务,然后触发事件执行任务。
- Redis的订阅与发布也可以实现上述的功能,但它的优势在于它可以一对多,PUBLISH之后可以同时触发多个订阅的事件,并且可以在任意进程内进行订阅。
- 专门的消息队列组件,则适用于对消息队列要求比较高的场景(消息确认、消费者组等)
在大多数操作系统中,父进程和子进程通常被分配相同的 CPU 时间片。时间片是操作系统用来调度进程的一种机制,它将 CPU 时间划分为较小的块,并按照某种策略分配给不同的进程。每个进程被分配一个时间片,在该时间片结束后,操作系统会剥夺该进程的 CPU 使用权,并将其分配给其他进程。所以多进程程序获得的CPU时间片比单进程程序多。
为什么不使用管道作为消息队列的通信接口,而是使用Redis List?
处理异步事件的时候,假设只有一个子进程,可以直接使用管理进行通信,然后触发事件。但是一个进程的处理能力是有限的,所以我们往往需要多个进程同时处理异步任务。多个进程同时读取Redis队列时,由于Redis的单线程模型,所以只有一个进程会拿到这个消息
2.问题
- Mysql一个链接的内存占用?(PHP协程创建了100个不进行操作的连接,占用内存7.8m)
- 主从复制,数据同步延时的问题?
- 数据量过大时,避免使用join,通过协程并发查询?
- 将单个查询分解成多个查询,通过协程并发查询?
- 读是共享锁,写是排它锁,考虑读写分离?
- 创建协程,单个协程需要的内存大小(底层默认分配2M(C)虚拟内存+8K(PHP)内存(PHP-7.2或更高版本)。这里的虚拟内存是指操作系统并不会立即分配2M物理内存,系统会根据在内存实际读写时发生缺页中断,再分配实际内存)?
- 测压过程中如何一步步排查问题所在?
- 自定义进程的数量,消费者消费的速度、消费者被阻塞的时长?
- 索引降维和连接池预热?
- 数据量过大时,直接分库,通过封装中间层,来实现现有业务的无缝迁移。
- 多个进程分配到的时间片会更多,通过多进程处理异步任务。多进程封装成一个进程组,然后通过原子计数器将任务平均派发到每一个进程。
- 协程机制的优点之一就是有阻塞就会自动处理别的任务,比如Mysql被阻塞就去处理Redis读取,Redis被阻塞就去Mysql;
3.Swoole协程
- 协程没有IO等待 正常执行PHP代码,不会产生执行流程切换
- 协程遇到IO等待 立即将控制权切,待IO完成后,重新将执行流切回原来协程切出的点
- 协程并行协程依次执行,同上一个逻辑
- 协程嵌套执行流程由外向内逐层进入,直到发生IO,然后切到外层协程,父协程不会等待子协程结束
- Swoole的协程在底层实现上是单线程的,因此同一时间只有一个协程在工作,协程的执行是串行的。这与线程不同,多个线程会被操作系统调度到多个CPU并行执行。
- 一个协程正在运行时,其他协程会停止工作。当前协程执行阻塞IO操作时会挂起,底层调度器会进入事件循环。当有IO完成事件时,底层调度器恢复事件对应的协程的执行。
- 对CPU多核的利用,仍然依赖于Swoole引擎的多进程机制。
- Swoole遇到需要等待的IO才会切换,比如SQL查询、比如网络请求等待响应、比如读取文件等找到那个文件。
- 从Mysql获取返回的结果集,这个需要CPU来计算,需要写内存,所以Swoole协程的单线程模型是串行来的,所以对于结果集数据量级过大的查询,效果不明显。
- 搞明白什么时候会发生协程切换,业务无关的任务可以异步处理
4.关于队列
队列可以通过客户重要性等条件因素,进行优先级划分。优先级越高的越先处理(Redis的zSet类型数据)。
如何及时处理的区别就在于,队列可以进行中间操作,根据任务进行不同的异步处理。
5.协程的调度
Swoole 的协程化主要集中在 I/O 操作上,通过异步 I/O 提升了 I/O 密集型任务的性能。对于 CPU 密集型操作,虽然不会阻塞协程,但可能会占用较多 CPU 时间,影响协程的响应速度。协程的切换主要依赖于以下几种情况:
- I/O 操作:当协程执行异步 I/O 操作时,会自动挂起,让出 CPU 给其他协程运行。
- 显式让出:协程可以通过调用 Swoole\Coroutine::yield 或 Swoole\Coroutine::suspend 显式让出 CPU。
- 超时等待:协程在等待某个操作完成时,如果设置了超时时间,超时后会自动让出 CPU。
CPU 密集型操作不会触发协程的挂起。如果一个协程执行了耗时 5 秒的CPU 密集型操作,它会占用当前协程的 CPU 时间。具体影响如下:
- 当前协程:执行CPU 密集型操作的协程会持续占用 CPU,直到操作完成。
- 其他协程:其他协程仍然可以运行,但会受到当前协程占用 CPU 时间的影响。如果当前协程占用 CPU 时间过长,其他协程的执行时间会相应减少。
swoole.enable_preemptive_scheduler ,可防止某些协程死循环占用 CPU 时间过长 (10ms 的 CPU 时间) 导致其它协程得不到调度
6.一个进程的 CPU 时间由什么决定
一个进程的 CPU 时间主要由以下几个因素决定:
-
进程的优先级:
-
操作系统会根据进程的优先级分配 CPU 时间。高优先级的进程会获得更多的 CPU 时间,而低优先级的进程可能会被推迟执行。
-
-
进程的 CPU 使用情况:
-
如果进程执行的是 CPU 密集型任务(如计算密集型操作),它会占用较多的 CPU 时间。
-
如果进程执行的是 I/O 密集型任务(如文件读写、网络通信),它会在等待 I/O 操作完成时释放 CPU 时间,让其他进程运行。
-
-
操作系统的调度策略:
-
操作系统使用调度算法(如时间片轮转、优先级调度等)来分配 CPU 时间。每个进程会获得一定的时间片(通常几毫秒)来运行,时间片结束后,操作系统会切换到其他进程。
-
在多核处理器系统中,多个进程可以同时在不同的核心上运行,从而提高系统的整体性能。
-
-
系统负载:
-
当系统负载较高(即有多个进程同时竞争 CPU 资源)时,每个进程获得的 CPU 时间会减少。
-
当系统负载较低时,进程可能会获得更多的 CPU 时间。
-
-
进程的阻塞状态:
-
如果进程进入阻塞状态(如等待 I/O 操作完成),它会释放 CPU 时间,让其他进程运行。
-
阻塞状态的进程不会占用 CPU 时间,直到它从阻塞状态中恢复。
-
进程不能无限申请 CPU 时间
操作系统会根据调度算法和进程的优先级来限制进程的 CPU 使用时间。即使进程试图占用更多的 CPU 时间,操作系统也会通过时间片轮转等方式强制切换到其他进程。
7.常见的进程阻塞
I/O 阻塞是指进程在等待输入/输出操作完成时进入的阻塞状态。常见的 I/O 操作包括:
- 文件读写:如打开文件、读取文件内容、写入文件等。
- 网络通信:如发送数据、接收数据等。
- 设备操作:如读取磁盘数据、等待用户输入等。
进程在等待某个时间间隔时会进入阻塞状态。常见的操作包括:
- sleep:使进程休眠指定的时间。
- usleep:使进程休眠指定的微秒数。
......
阻塞卡主,跟CPU占用过高卡主,是两种情况