全面分析golang 为什么比nginx + php-fpm的模式并发高

2,565 阅读3分钟

nginx作为绝大都web服务器的第一层,性能相当出色。整体来说, 对比传统的nginx+php模式,golang性能上非常出色。当然,随着业务逻辑的复杂,这种优势会逐渐缩小,毕竟web服务的主要瓶颈还是在DB上。

语言层面

go编译型语言,直接生成可运行的字节码。

php脚本型语言,执行的时候需要动态解析。php-fpm开启opcache后会缓存解析后的字节码,性能也会相应提升。

线程和协程

线程的切换对应着cpu上下文的切换,浪费资源,php-fpm本身是一个线程级别的东西。

看过go的PMG模型应该了解,P是线程级别的,通过P调用协程G,保证多任务执行的时候是在同一个线程里,避免了cpu的上下文切换。

偷来的MPG关系对照图,水印有出处

异步

php-fpm中单个线程执行某个请求,必须执行完该请求之后才会执行下一个请求。比如你请求一个redis,花费1ms,则这个线程这1ms就只能等着。当然,系统内核不会闲着,内核会切换cpu去执行其他的线程

go协程中遇到这种系统调用,G会让出cpu,然后P去调用别的G,一般不会发生线程级别的切换。

go协程什么时候会让出CPU???

  • system call: 当发生系统调用时,执行当前routine的线程会block这是没得商量的,但go运行时会从它维护的线程池中取出一条空闲线程继续执行其它routine, 这样就做到了即使你block了routine,也不会影响其它routine的执行
  • 网络I/O:调用net包下的网络I/O操作是不会阻塞线程的。当发起网络I/O请求时,go运行时会通过操作系统提供的epoll机制注册I/O事件,不会挂起实际干活的线程,只会切换goroutine而已。
  • 竞争:无论是锁竞争还是读写channel而导致routine被挂起,其背后的线程都是不会有任何block的,在OS看来线程一直在正常运行,从而大大降低了线程上下文切换的开销。
  • 主动让出执行权:同上面的竞争,主动让出执行权时背后的线程同样不会block。

内存

当系统开辟新的线程的时候,对应的会从内存中申请一定的堆栈空间。一个php-fpm线程启动后一般占用20M内存,相当于什么还没干就用了20M

相对于线程,go协程级别的内存成本低的多,初始化一个协程内存是Kb级别的。当然随着协程内内存的占用,会申请更大的内存空间。具体参照go内存管理。

最初写go的时候,就被go的内存占用给吓着了。go 内存gc当初比较渣,stop the world,导致过系统直接停止服务。现在升级最新版本的go,gc已经相当给力。

网络

对于php来说,web访问需要nginx proxypass到php-fpm,虽然nginx使用epoll性能很高,但毕竟有一层网络转发,性能上肯定会有一些损失。

根据以前的测试,nginx转到go相比较于直接访问go会有30%的性能损耗。

缓存

php作为一个脚本型语言,业务中的缓存数据一般存在redis中,当然,部分数据可以缓存到本地文件中。而golang可以在redis之上缓存大量的数据到内存中,具体数据一致性具体业务具体想办法解决。

以上内容只是从表面分析了go相对于php优势,具体原理还请自己学习。