人人都能看懂的全栈开发教程——NGINX

人人都能看懂的全栈开发教程——NGINX

Chris Yue No Comment
Posts

在开发环境,我们一直使用 PHP 自带的 Web 服务,但 PHP 自带的 Web 服务同时只能支持处理一个请求。在互联网上,很多网站接受着千万用户同时访问的考验。显然,Minetodo 项目发布到互联网上,PHP 自带 Web 服务肯定是不能考虑的。

专业的工具做专业的事情,PHP 内置 Web 服务本来就只是为了方便开发和测试的,要想让项目在互联网上运行,我们需要更加专业的 Web 服务器。

类似数据库服务,Web 服务也有好多选择,这里我就不一一列举,只介绍一款目前用得比较多的 NGINX

一个网站的运行需要『环境』,比如开发的时候,项目就运行在『开发环境』上,而在互联网上运行的环境,一般叫做『生产环境』或者『投产环境』,后面我也会使用『生产』,『开发』这样的字眼来称呼网站运行的环境。

为了熟悉 NGINX 的使用,先把自己的开发机当成生产机,还是以 Ubuntu 为例,我们先安装 NGINX,在命令行下运行:

sudo apt install nginx

安装成功之后,Ubuntu 应当会自动启动 NGINX 服务,也可以通过下面的命令来检查服务状态:

sudo systemctl status nginx

如果 NGINX 启动成功,当使用浏览器访问 http://localhost 也应该能看见成功提示。

除了查看 NGINX 的状态,我们还可以通过 systemctl 来停止、开始、重启 NGINX 服务:

sudo systemctl stop nginx
sudo systemctl start nginx
sudo systemctl restart nginx

systemctl 是很多 Linux 发行版默认提供的用于管理系统服务的工具(即 system controller,系统控制器),所以,几乎所有的服务都可以这么操作。把上面命令里的 nginx 换成 mysql 就是控制 MySQL 服务的方式。

接下来为我们的 minetodo 项目创建一个新的站点。假如我们的站点域名是 minetodo.local,那我们先找到 NGINX 配置站点的地方。在 Ubuntu 里应该在 /etc/nginx 目录里。在 Linux 发行版里一般来说如果你安装了一个软件叫做 xxx,那它的配置文件一般都在 /etc/xxx。如果我们进入到 /etc 目录,我们还能看到 MySQL 和 PHP 的配置文件目录。

我们先进入 NGINX 的配置文件目录

cd /etc/nginx

可以看到里面有两个目录看着就像是放配置站点的地方,一个叫做 sites-available 一个叫做 sites-enabled。我们再进入到 sites-enabled 目录下,使用 ls -l 命令查看配置文件,可以发现里面的默认站点配置文件实际上是『指向』 sites-available 里的同名文件的。『指向』的意思是,当前你看的到文件,其实只是一个『连接』,当你对这样一个连接到别的文件的『连接』做读取文件内容的操作的时候,跟实际读取被连接的文件其实是一样的。知道这个概念之后我们再来看 NGINX 的配置文件,其实使用连接,是一种巧妙维护站点的方式。我们可以将想创建的站点,先在 sites-available 目录创建(available 的意思即是『可用的』),确认要开启,我们再在 sites-enabled 目录创建对应的连接(enable 即『开启』);反过来,如果我们打算把某个站点暂时关闭了,只用将 sites-enabled 里对应站点的连接删除就行了,而不用真的把配置删除掉。

需要注意的是,上面所说的规则,并不是 NGINX 规定这么做的,而是 Ubuntu 的 APT 帮我们初始化好的使用方式,我提这一点第一是想告诉读者,APT 可以在安装软件之后做一些事情,这些事情都是 Ubuntu 社区成员帮我们做的;第二是想说,并不是所有操作系统安装 NGINX 之后都是这样的行为,所以如果你用的不是 Ubuntu,安装完之后跟我说得不一样,也不用奇怪。

总之,只有 sites-enabled 里面提到的站点配置才是真正管用的。这点其实也可以通过查看 nginx.conf 配置文件看得出来。大家不用去看所有的配置,目前只用关注 include sites-enabled/* 那一句就行,大家都见过了 PHP 的 include,这句配置的意思我觉得就不用解释了吧。

大家有兴趣可以先看看 sites-enabled 下的 default 配置文件,我们访问 localhost 的时候看到的站点就是由它配置的。我这里就直接将我们站点的配置列出来了:

server {
    # 这是 NGINX 配置文件里的注释
    # 文件名可以随便起,但为了好识别,建议跟域名起一样的名字
    # 即 /etc/nginx/sites-available/minetodo.local
    # server_name 定义了站点的域名是什么
    server_name minetodo.local;
    # root 定义了站点的根目录的位置
    root /path/to/minetodo/public;
    # listen 是监听的端口号的配置,不配置就是默认的 80 端口,这里我们先不配置
    # listen 80;
}

还记得 /etc/hosts 文件吗?为了让我们的 minetodo.local 能够被访问到,我们应当在 hosts 文件里面将 minetodo.local 域名绑定到 127.0.0.1

...
# 这是 hosts 文件的注释
# 在文件的最后绑定 minetodo 域名
127.0.0.1 minetodo.local

前面一章说浏览器访问网站的时候,我少说了一个步骤,其实浏览器在尝试将域名让 DNS 解析之前,是会先查询 /etc/hosts 文件的,此文件的域名映射规则优先级最高,只要这里面能找到某个域名的 IP,浏览器就不会再让 DNS 服务去查询了。

保存好我们的配置文件,并使用下面命令将配置文件连接到 sites-enabled 目录

cd /etc/nginx/sites-enabled
ln -s ../sites-available/minetodo.local

通过 systemctl 重启 NGINX 服务,我们的站点就创建成功了。一个好习惯是,在我们创建或者修改了配置之后,可以使用下面的命令先检查一下配置是否有错误:

nginx -t

另外,由于 NGINX 是在生产环境运行的,意味着无时无刻有互联网用户在访问站点。如果有用户在访问网站,正好赶上你在重启 NGINX,那这个用户便会访问网站失败,给人操成不好的印象。为了避免这种情况,NGINX 支持一种叫做『热重启』的方式。重启其实就是先停止,再启动,而『热重启』则没有停止的过程而直接重新载入配置,避免遇到无法访问的情况。『热重启』的命令为

sudo systemctl reload nginx

注意不是所有的服务都支持 reload,是否支持尝试一下就知道了。

可能有比较细心的读者会有疑问,NGINX 本来就运行着一个默认网站在 80 端口上,刚刚新创建的网站端口号也是 80,那用户访问网站的时候不是会有冲突吗?

还记得我们之前看过的 HTTP 头信息吗?其中有一个头信息是 Host xxx,后面的 xxx 就是我们访问的网站的域名。作为专业的 Web 服务,NGINX 当然是能读到这个信息的,它会根据这个信息来判断用户访问的是哪一个站点,即配置文件里的 server_name 选项。

如果我们现在就用浏览器访问 http://minetodo.local 站点,网站是会报 404 的,这也很正常,如果我们不指定站点要访问的文件路径,NGINX 默认是会去找目录里的 index.html 文件的,毕竟 NGINX 不是专门给 PHP 用的,所以不会像 PHP 内置 Web 服务会自动去找 index.php 文件。不过我们现在先不纠结 PHP 相关的问题,先在项目的 public 目录里创建一个 index.html 文件,里面随便写点东西,这个时候再访问 http://minetodo.local,不出意外应该就能看到 index.html 文件的内容了。

接下来要解决的问题是如何让 NGINX 和 PHP 配合一起工作。还记得说面向对象的抽象的时候,有提到过接口这个概念,实际上 Web 服务也有类似的概念。很多 Web 服务可以做到只管接受请求,自己并不亲自处理请求,但支持将请求规整为某种固定的数据格式,并转发给其他程序代为处理,其他程序将结果生成后返回给 Web 服务,Web 服务再把结果通过网络返回给浏览器。Web 服务将请求『委托』给另外一个程序处理也需要通讯『协议』,而常用的协议就是 CGI,也就是 Common Gateway Interface。在 CGI 的基础之上,还发展出了其他的扩展协议,比如目前 PHP 最常用的是 FastCGI 协议。NGINX 将请求委托给 PHP 处理也是通过服务的方式传递的,就跟 MySQL 服务一样,而我们现在所用的 PHP 还只是在命令行下运行的工具,我们还需要单独安装实现 FastCGI 的 PHP 服务,也就是 PHP-FPM(FPM 即 FastCGI Process Manager,FastCGI 进程管理器)。

sudo apt install php-fpm

如同我们的 NGINX 服务一样,安装完成之后 Ubuntu 会自动尝试启动 PHP-FPM 服务,我们也可以通过 systemctl 来查看和管理 PHP-FPM 服务,这里我就不列出命令来了,大家可以猜猜是什么。

为了使用 PHP-FPM 服务,我们将 NGINX 的配置文件更新:

server {
    ...

    # location 是对某个路径的配置,location / 则是所有路径的意思
    location / {
        # 下面这条指令意思是,无论任何路径($uri),都交给 /index.php 处理
        # 先说一下『查询参数』,比如百度搜索 http://www.baidu.com/s?wd=chrisyue 这个地址中就有 wd=chrisyue 这个查询参数
        # 下面配置中 $is_args 就是指代 ?
        # 而 $args 就指代查询参数的完整体
        # 假如有一个地址是 http://minetodo.local/login?name=kyo
        # 那么下面这句话的意思就是将这个地址转发到 /index.php?name=kyo 这个地址上
        try_files $uri /index.php$is_args$args;
    }

    # 虽然上面的 location / 已经包含了所有路径,但路径写法不同优先级也不一样
    # 比如 ~ 开头的是正则的方式匹配路径,这种方式就比上面那种写法优先级更高
    # 此处的规则表示只适合 /index.php 这个地址
    # 因为 location 是无视查询参数的,所以 /index.php?name=kyo 这种也适合
    location ~ ^/index\.php$ {
        # 此处表示将请求转发到端口号为 9000 的服务上
        # 9000 就是 PHP-FPM 服务的默认端口号
        fastcgi_pass 127.0.0.1:9000;
        # 此处包含了另外一个跟 fastcgi.conf 有关的配置文件,后面再说
        include fastcgi.conf;

        # 下面这句表示只处理通过 internal (即『内部』)转发来的地址,比如 try_files 转发的地址
        # 假如我们直接用浏览器访问 /index.php,虽然地址匹配 location 指定路径,但因为这一句反而 NGINX 会直接返回 404
        # 如果你想让用户也可以通过 /index.php 访问网站,去掉这一句就行
        internal;
    }
}

可能看到这里,很多读者不明白,如果把地址 /login 转到了 /index.php,那 PHP 还知道用户访问的其实是 /login 吗?这就得看 fastcgi.conf 定义了啥了。如果只是为了查看某个文件的内容,可以通过 cat 命令来查看

cat fastcgi.conf

这里提醒一下,如果是其他操作系统或者发行版,fastcgi.conf 文件不一定有,但 fastcgi_params 文件是一定有的,这两个文件的区别只有是否定义了 SCRIPT_FILENAME 变量,如果你没有这个变量的定义,请将它加在 fastcgi_params 的最前面:

# 此变量定义了 PHP 实际要执行的文件的地址
fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
...

如果以前有仔细看过 PHP 的 $_SERVER 变量,会发现它里面很多项在这个文件都有出现,没错,这些值就是需要 Web 服务生成好并传给 PHP 的,当然,以前我们看到的项都是 PHP 内置 Web 服务生成好的。可能有的读者已经能想到,我们也可以通过 fastcgi_param 指令添加我们自己的参数。

说回到之前的问题。即使使用了 try_files 改变了访问路径,但 fastcgi.conf 设置的参数 REQUEST_URI 还是保留原始的(request uri 即用户实际访问的地址的意思,当然不能变),所以 PHP 依然能知道原始的地址。

修改完了配置文件,我们再次重启 NGINX,刷新主页,大家就可以看到通过 NGINX 来访问的网站了。

不过这个网站依然有点小问题需要解决,我们总不能将页面最下面的调试栏给普通的互联网用户看到吧,这个调试用的工具条只有在项目处于开发环境的时候才会出现,所以我们应当将环境定义为生产环境;另外,当 Symfony 处在开发环境+调试模式状态,会记好多开发日志,但这些日志对生产环境来说毫无用处,只会浪费资源,生产环境一定要关掉它。

Symfony 最早是通过 yaml 文件来配置环境的,后来改进成使用『环境变量』来定义配置环境。环境变量是一个命令行相关的常见术语,其实我们很多命令都可以通过修改环境变量的方式来改变命令一些默认的行为,比如绝大多数的需要联网的工具都可以通过环境变量 http_proxy 来设置代理:

http_proxy=http://some.proxy composer require phpunit/phpunit

Symfony 也一样,比如我们可以通过下面的方式来指定 Symfony 命令运行的环境:

APP_ENV=prod bin/console ...
# 其实和下面的命令是一样的效果
bin/console ... --env=prod

然而我们的网站代码是运行在 NGINX 上的,我们就不能用上面的方式来设置,但 NGINX 依然可以通过 fastcgi_params 来设置 Symfony 项目的环境变量的:

fastcgi_params APP_ENV prod;

另外还有一种更好的方式。Symfony 会先自动尝试将项目根目录的 .env.local 文件和 .env 文件里定义的所有环境变量合并在一起了之后加载,并且 .env.local 文件的优先级比 .env 高,项目目前还没有这个文件,让我们来创建它:

cp .env .env.local

并且修改 .env.local 文件的内容:

APP_ENV=prod

# 其他都删掉

这个时候再刷新首页,就可以观察到调试工具栏消失了。

小提示:在真正的生产环境里,记得给数据库设置一个复杂得多的密码。

由于生产环境的配置是非常敏感的信息,不应该给开发者知道,所以 .env.local 文件是不能提交到代码仓库里的。不过 Symfony 项目生成的时候已经很贴心的在 .gitignore 文件里设置忽略这个文件了。

虽然现在网站已经可以在生产环境运行了,但很多好习惯或者最佳实践还并没有提到,比如应该单独为每个站点设置不同的日志。不过 NGINX 配置的话题很大,一篇文章肯定是说不完的,本篇也就当只是带领大家入门了,本站也有很多跟 NGINX 相关的话题,大家可以点此了解。

最后,说回本篇文章开篇的问题。NGINX 或者别的 Web 服务,都可以支持多个线程同时服务多个请求,再次查看 nginx.conf 配置文件,文件最前面的 work_processes 定义了要用多少个工作进程,如果这个值是 auto,则是当前机器的 CPU 有多少内核就是多少;后面 events 里的 worker_connections 则定义了每一个工作进程可以同时处理多少请求,这个值设置成多少合适,则只能通过自己测试的方式来观察了,不过就现在的机器而言,再烂也至少都是 512 起。想想看,在一个只有两核 CPU 的机器上,每核同时处理 512 个请求,那总共同时处理的请求也有 1024 之多了。不过,光是 NGINX 同时支持多个请求还不够,PHP-FPM 本身也得同时支持同时处理多个请求,这就是下一章的话题了。

人人都能看懂的全栈开发教程——NGINX by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

如果觉得文章还不错,就请扫码鼓励一下作者吧
天使打赏人

发表评论

8 + 2 =