内容来源:2018 年 05 月 27 日,Swoole开源项目创始人韩天峰在“【上海】OSC源创会第75期”进行《基于 Swoft 协程框架的 PHP 微服务治理》演讲分享。IT 大咖说(微信id:itdakashuo)作为独家视频合作方,经主办方和讲者审阅授权发布。
阅读字数:2928 | 8分钟阅读
摘要
本次演讲将介绍 Swoole 3.0 全新的 PHP 协程编程模式,以及 Swoft 协程框架的使用,基于 Swoft Cloud 提供的各类组件实现 PHP 微服务架构。
基于swoole 4.0全新的PHP编程模式
上面是一段PHP代码,其中2个函数的执行时间都是1秒,整段代码执行完成需要2秒。要想将这种串行执行方式转换为并行执行,在PHP中可以通过创建多进程来执行每个函数,单个进程执行单个函数,这样在1秒钟能就能执行完上面的代码。虽然在Java中多线程应用很普遍,但是很可惜PHP并不支持多线程。
除开多线程和多进程,还有一种方式也能实现并行编程,那就是协程(Coroutine),这也是GO语言的重要特性。协程的并发量相对多线程和多进程要高出很多,同一个进程内可以创建几十万甚至上百万个协程,且只占用少量的内存空间。线程和进程由操作系统调度,是非常昂贵的系统资源,创建过多的话,在上下文保存和进行切换的开销上会很大。
这里将前面的代码以协程的形式进行了重写,执行的时候会创建两个协程,执行时间为1秒。虽然执行效果和多进程或多线程一样,但实现原理有所不同。协程中这两个函数的执行基于一种自动让出的机制,一旦执行函数遇到IO操作,就会自动让出当前执行栈交由下一个函数执行,在IO完成之后再恢复协程栈。
PHP由于自身的天然缺陷无法支持多线程,所以我们绕开了它直接在swoft 4.0中实现了协程。但由于针对CPU密集操作只能利用到一个核,所以在使用协程的时候还是会利用多进程的方式来复用CPU的多核操作。
协程最大的好处在于能够提供极大的并发,因为它仅占用内存,不存在进程/线程切换开销,单个进程就可开启50w个协程。
我们在swoft 1.0的时候采用的技术方案和node.js的异步回调一样,在2.0的时候开始尝试实现协程,但是存在一些缺陷——协程不能用在所有的函数上,只能用在一些已经预定好的函数上。这是由于PHP有一些动态的特性,比如将URL映射到一个类方法上,这种场景下执行2.0的协程程序就会崩溃。4.0的时候我们对此做了一些优化,基于微信开源的库重新实现了协程方案,这时的协程就达到了在Go语言中的效果。
上面展示的就是PHP中使用协程的三种方式。左上的代码通过循环的方式创建了10个协程,下面这段则是在协程中执行读文件的操作,且内部还嵌套了两个协程,它们之间是相互依赖的关系。右边的代码直接创建了3个协程,每个协程的执行逻辑都不一样。
有了协程之后,就会涉及到如何管理协程或数据通信的问题。在Java多线程中,线程之间的通信可能会使用锁或者数据结构的方式解决,在协程编程中一般使用的chan的方式管理。
协程是一个用户态的线程,同一时间执行的协程只有一个。这一点和多线程不同,创建出来的多个线程都会并行执行。
左边这段代码是协程编程,它会读取一个全局的数组,当协程1读取数组的时候,协程2其实没有运行,直到协程1遇到IO操作释放了控制权,协程2才会恢复再去读全局变量,这样就完全不用加锁了。
右边是线程编程,可以看到如果程序要读取全局临界资源就一定要加锁,要不断的lock、unlock。
Chan有点类似队列,不过它自带了协程调度能力。
多线程读取队列时,会有生产者和消费者。在队列内存占用过多无法再写入的情况下,生产者还是会持续写入,一般的解决方案是进行盲等,比如让生产者sleep一段时间然后再去写入。在队列无数据可返回的情况下,一种方案是让消费者盲等,CPU 死循环去等待,不过这样会占满 CPU。一般的方案是在发现无数据返回的时候 sleep 一段时间,之后再尝试读取。
协程编程中可以通过chan来完成协程调度。当生产者发现容量不足的时候会展示挂起当前协程,直到有消费者拿走一些数据之后才会唤醒这个协程。消费者的读取机制也是一样的,无可用数据时就挂起,一旦生产者push数据后再唤醒。
由于PHP的动态语言特性,所以可以向chan中push任意的PHP变量,无论是对象还是数组。Chan的底层基于引用计数管理,完全没有内存拷贝,除了标量类型是直接复制之外,包括数组、对象这些复杂的数据结构都是用的引用计数管理。像Go语言一样,我们也提供了chan::select用来对多个chan进行读写判读。
协程框架swoft的介绍
Swoft是基于协程实现的web开发框架。它借鉴了spring Cloud做了完全组件化的实现,里面很多功能都是一个小的组件,当然也可以用自定义的组件替换内置的组件。该框架也提供了依赖注入、容器、连接池、AOP,除了应用在web领域之外,还能够用在微服务上。
上面两行命令分别是用来创建swoft工程和引入相关组件。
目前swoft支持3种服务器,swoft-http-srever 、swoft-websocket-server swoft-rpc-server。 第一个用来做主流的web应用程序,第二个是长连接通信服务,最后是微服务领域的RPC服务。
通过命令行脚本能够直接启用以上3种服务,这里也提供了一些常用的脚本工具。
Swoft参考Java的Spring框架,用了很多注解编程的方式。对于Web开发中的URL映射,可以直接通过注解的方式写Route。能够自动将URL映射到当前Controller方法中,URL中的参数也会自动带入类方法中。
基于swoft协程框架进行PHP微服务治理
Swoft自带了一些微服务常用的组件,包括服务注册、熔断、降级、负载均衡、接口多版本等。
Swoft的服务注册与发现是基于Google开源的consul,要使用consul需要添加一些配置,定义服务提供方和接入方的key。然后在前面提到的命令行脚本调用RPC start就会自动将我们的服务器节点注册到consul服务器中。
Swoft的接口声明也是基于注解的方式,如上图所示通过注解定义了service指向的服务以及调用的接口,调用的时候会映射到对应的方法。
swoft的熔断机制中失败超过一定次数,服务就关闭,成功的话,服务重新连接。
这段代码是关于熔断器的调用,首先确定熔断器的名词,正常情况下调用handler,失败的话就调用fallback进行一些处理。
这里关于微服务的介绍可能比较简要,其实是因为做服务治理更多的还是要用成熟的框架。PHP方面我们推荐使用Tars,这是腾讯开源的微服务治理框架,基于WUP结构定义文件,可以自动生成接口骨架代码,有着完整的服务治理方案,自带发布、运维、监控、弹性伸缩体系。
以上为今天的全部分享内容,谢谢大家!