Nginx概述

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
.cn 域名,1个 12个月
简介: 重新认识nginx 在以前的运维过程中,大量环境都使用到了nginx,不管是与keepalived+haproxy做高可用,或者做缓存,或者做web应用服务器用来部署各种各样的环境,都使用到了它。但是真的了解它吗?并不,很多时候都是看一些博客里面的配置文件,或者在以前的配置文件上改改即用。

感悟

在以前的运维过程中,大量环境都使用到了nginx,不管是与keepalived+haproxy做高可用,或者做缓存,或者做web应用服务器用来部署各种各样的环境,都使用到了它。但是真的了解它吗?并不,很多时候都是看一些博客里面的配置文件,或者在以前的配置文件上改改即用。所以对nginx的很多特性并不是很了解的。而作为一个运维,不仅要改善应用的稳定性,提升交付实施的效率,另外一方面就是提高资源的使用率节省成本。而nginx作为外端访问节点,这里面有很多文章可以做,所以有了重新学习nginx的欲望。

一、认识nginx

1.三个主要使用场景

静态资源
API服务
反向代理(缓存加速、负载均衡)

2.为什么会出现nginx?

访问量的增长
摩尔定律:性能提升
低效的apache

3.为什么要用nginx?

高并发、高性能
扩展性好,第三方模块多
BSD许可证
热部署、热升级
系统开销很小很小

4.nginx的组成

二进制文件
访问日志
错误日志
配置文件

5.版本发布情况以及主要特性和使用

官网连接:http://nginx.org/
image

主流版本:
官方版:社区nginx.org、商业版nginx.com
淘宝:Tengine
其它:社区OpenResty.org 商业OpenResty.com

编译安装nginx
————————————————————————————————
为什么选择编译而非apt-get或者yum?
因为直接下载的官方二进制文件不一定包含了我们想要的模块,我们如果需要定制化属于自己的模块,必须要重新编译后安装。基于此,我觉得不管是dockerfile或者本地部署nginx,都尽量使用编译安装。尤其是dockerfile,为符合docker极简思想,我们一定要编译最简单的nginx。为啥咧? 我经常看见很多老哥,经常说 为啥我的k8s服务器老是会磁盘报警?(你一个镜像1个G,每天发10个版本,你不报警谁报警?) 所以咧,如果用成本来考核一个运维,我觉得一个浪费资源的运维是不合格的,一定不是看过老马哥视频的。

————————————————————————————————


1.下载
http://nginx.org/download
2.编译
nginx-1.14.2
├── auto
├── CHANGES
├── CHANGES.ru
├── conf
├── configure
├── contrib
├── html
├── LICENSE
├── man
├── README
└── src
 配置项的高亮:cp -r contrib/vim/*  ~/.vim 
 复制此模块之后会显示出语法的高亮,便于配置nginx以及检查错误

configure的说明:
--help                             print this message

--prefix=PATH                      set installation prefix
--sbin-path=PATH                   set nginx binary pathname
--modules-path=PATH                set modules path
--conf-path=PATH                   set nginx.conf pathname
--error-log-path=PATH              set error log pathname
--pid-path=PATH                    set nginx.pid pathname
--lock-path=PATH                   set nginx.lock pathname

--user=USER                        set non-privileged user for
                                   worker processes
--group=GROUP                      set non-privileged group for
                                   worker processes

--build=NAME                       set build name
--builddir=DIR                     set build directory

--with  默认不会编译的模块

--without 默认编译的模块

中间模块 objs  主要的是modules 里面会看到里面的各种模块

最后使用make install 即可安装完成

配置nginx
image

1.重载
重载:nginx -s reload
热部署(nginx升级):
初始版本:
curl -I 127.0.0.1 
HTTP/1.1 200 OK
Server: nginx/1.14.2
2.热部署
原理:新版本的nginx编译好后 只需要更换nginx的二进制文件即可
即下载新的安装包,按照上述流程重新编译 编译完成之后拷贝二进制文件(原来的复制为old)  再向原来的nginx的主进程发USR2信令 这个时候就会新起nginx
master以及worker进程来承担请求
curl  -I  127.0.0.1
HTTP/1.1 200 OK
Server: nginx/1.15.8
Date: Tue, 08 Jan 2019 07:42:17 GMT

image

3.日志切割
做日志切割一方面是为了分析日志,另一方面是为了收集日志,我们可以在日志命名的时候再加上一些host信息,这样以后同步到ELK里面我们就能更准确的找到日志了,或者我们直接写到nginx的log模块里面去。也是为了后期维护的方便
A.编写脚本

#!/bin/bash
year=`date +%Y`
month=`date +%m`
day=`date +%d`
logs_backup_path="/usr/local/nginx/logs_backup/$year$month"               #日志存储路径

logs_path="/usr/local/nginx/logs/"                                                             #要切割的日志路径
logs_access="access"                                                                            #要切割的日志
logs_error="error"
pid_path="/usr/local/nginx/logs/nginx.pid"                                                 #nginx的pid

[ -d $logs_backup_path ]||mkdir -p $logs_backup_path
rq=`date +%Y%m%d`
#mv ${logs_path}${logs_access}.log ${logs_backup_path}/${logs_access}_${rq}.log
mv ${logs_path}${logs_error}.log ${logs_backup_path}/${logs_error}_${rq}.log
kill -USR1 $(cat /usr/local/nginx/logs/nginx.pid)

3.做定时任务

crontab –e

59 23 * * * bash /usr/local/nginx/shell/cut_ngnix_log.sh   #每天23:59分开始执行;

二、nginx的简单使用

1.搭建静态资源服务器

以dlib库的帮助文档为例 我们来展示一下如何使用nginx做静态资源服务器
首先提供一下dlib库的链接:

地址:http://dlib.net/files/dlib-19.16.tar.bz2

解压之后的文件结构:
dlib-19.16/
├── CMakeLists.txt
├── dlib
├── docs
├── documentation.html
├── examples
├── ISSUE_TEMPLATE.md
├── MANIFEST.in
├── python_examples
├── README.md
├── setup.py
└── tools

这里啰嗦几句,在nginx里面 最大的是http,接下来在里面可以配置server,location,upstream等模块。

由于一个nginx可能要被多个服务所使用,为了便于管理,那么我们一般要拆分不同服务的配置文件,也就是说一个http模块下面要写多个server,在不同的server下写不同的location来实现模块的管理。

基于此,创建了vhost目录来用作配置文件管理:

    include  ../vhost/static.conf;


static.conf下面的内容:

   server {
        listen       8080;
        server_name  47.244.20.89;
        location / {
            alias /www/docs/;
            index  index.html index.htm;
        }
    }
我们通常使用alias,alias和root的区别?
root会把url一些路径带到我们的文件目录里面来 从而导致一系列的404 所以通常用alias即可,这样就可以严格匹配我们的文件目录格式

设置好后访问即可:

image
发现什么问题没有?

image
文件是多大,那么我们的请求内容就是多大。
请求的资源大意味着什么?

在一个高并发的服务器上,每个请求都是会有内存的slab开销的,在使用公网服务器的时候是会有流量开销的。带宽资源的使用直接影响的就是用户的访问体感。nginx这个是后有个很好的参数 gzip on开启之后就能解决这个问题了。我们试试看:


    gzip  on;  #开启开关
    gzip_min_length 1; #小于1字节不再压缩 节省cpu
    gzip_comp_level 2; #压缩级别是2
    gzip_types tex/plain application/x-javascript text/css application/xml text/javascript applicat
ion/x-httpd-php image/jpeg image/gif image/png image/jpg;#执行压缩的文件类型

压缩后,我们的性能有了改观

image

header信息:

GET / HTTP/1.1
Host: 47.244.20.89:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

下面还有一点点要强调
alias 设置的目录是准确的,可以理解为linux的 ln命令创建软连接,location就是软连接的名字。如上面2.2例子访问 http://域名/down/vpser.txt 是直接访问的/home/wwwroot/lnmp/test/下面的vpser.txt文件。

root 设置的目录是根目录,locatoin里所指定名称的目录,必须在root设定下的目录有相同名字的目录。如果将上面2.2例子里的alias改成root 访问 http://域名/down/vpser.txt 是直接访问的的/home/wwwroot/lnmp/test/down/ 目录下的vpser.txt文件。

需要注意的是alias目录必须要以 / 结尾且alias只能在location中使用。
如果我们想要共享静态资源,可以使用文件浏览模式autoindex
image

如果想希望做出漂亮的目录列表,支持header,footer则可以安装三方插件:
http://wiki.nginx.org/NginxNgxFancyIndex

还有一种场景,nginx做为高并发web服务器,当有很多人在同时使用的时候,有的人在下毛片,有的人在登录等待后端服务响应,有的人正在打开主页,这些人都是在抢占资源的,我们如何保证打开一些必要的文件,比如首页渲染时要加载的css、js等能够优先加载出来呢?
可以考虑一波用set命令配合一些内置变量来实现功能。
比如 set $limit_rate 限速

location /autoindex {
             alias /www/static/dlib/ ; 
             autoindex on;  #做文件浏览
             set $limit_rate 1k;  #限速1k

image

可以明显看到加载了4.5k资源 用了5s 中间那点误差可能是跟服务器建连还有网络传输产生的 不用太纠结。

最后一点 logformat变量来记录日志
可以记录日志的参数列表:http://nginx.org/en/docs/http/ngx_http_core_module.html#http

Embedded Variables

格式定义:
log_format 名称  变量 
引用:
access_log  名称
例如:
log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
 location /autoindex {
             alias /www/static/dlib/ ;
             autoindex on;
             set $limit_rate 1k;
             access_log  /www/logs/access.log  main;
}
验证一下生成的日志:
182.149.165.30 - - [23/Jan/2019:01:20:40 +0800] "GET /autoindex/ HTTP/1.1" 200 4048 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" "-"

2.具备缓存的静态资源反向代理服务器

前置:1.5x 版本的nginx以及把请求代理到了后端的dlib服务上

后置:用另外一个1.4X nginx把请求代理到1.5X上面,并且在1.4X的nginx上建立缓存

nginx1.4---nginx1.5--alias

算法介绍:

一、分配方式

    Nginx的upstream支持5种分配方式,下面将会详细介绍,其中,前三种为Nginx原生支持的分配方式,后两种为第三方支持的分配方式:

1、轮询

    轮询是upstream的默认分配方式,即每个请求按照时间顺序轮流分配到不同的后端服务器,如果某个后端服务器down掉后,能自动剔除。
    upstream backend {
        server 192.168.1.101:8888;
        server 192.168.1.102:8888;
        server 192.168.1.103:8888;
    }

2、weight

    轮询的加强版,即可以指定轮询比率,weight和访问几率成正比,主要应用于后端服务器异质的场景下。
    upstream backend {
        server 192.168.1.101 weight=1;
        server 192.168.1.102 weight=2;
        server 192.168.1.103 weight=3;
    }

3、ip_hash

    每个请求按照访问ip(即Nginx的前置服务器或者客户端IP)的hash结果分配,这样每个访客会固定访问一个后端服务器,可以解决session一致问题。
    upstream backend {
        ip_hash;
        server 192.168.1.101:7777;
        server 192.168.1.102:8888;
        server 192.168.1.103:9999;
    }

4、fair

    fair顾名思义,公平地按照后端服务器的响应时间(rt)来分配请求,响应时间短即rt小的后端服务器优先分配请求。
    upstream backend {
        server 192.168.1.101;
        server 192.168.1.102;
        server 192.168.1.103;
        fair;
    }

5、url_hash

    与ip_hash类似,但是按照访问url的hash结果来分配请求,使得每个url定向到同一个后端服务器,主要应用于后端服务器为缓存时的场景下。
    upstream backend {
        server 192.168.1.101;
        server 192.168.1.102;
        server 192.168.1.103;
        hash $request_uri;
        hash_method crc32;
    }
    其中,hash_method为使用的hash算法,需要注意的是:此时,server语句中不能加weight等参数。

再回到我们的实验 这里不去深刨这些算法的实现以及思路了,不然得搞死去 知道这几种常用的算法的一般原理以及应用场景就OK

upstream test {
     server 127.0.0.1:8080;
             }

    server {
        listen       8888;
        server_name  localhost;


        location / {
            #proxy_set_header Host $host;
            #proxy_set_header X-Real-IP $remote_addr;
            #proxy_set_header X-forward-For $proxy_add_xforwarded_for;

            proxy_pass http://test;
        }

至此,反向代理完成

image
值得注意的是,做了反向代理,无论是waf,7层slb或cdn等,经过反向代理之后请求变成了 客户端--反向代理 反向代理--后端

那么后端拿到的就是反向代理的IP了,那么我现在就要通过重新设置
header变量的方式来解决这个问题了:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-forward-For $proxy_add_xforwarded_for;

所有值得参考的代理参数:

http://nginx.org/en/docs/http/ngx_http_proxy_module.html

因为nginx的特性远远领先于上游服务器,那么我们就可以用它来做静态资源的缓存服务器。
当然 缓存也是在http区块里面做定义 在location里面做引用

http的定义:
proxy_cache_path /tmp/nginxcache levels=1:2 keys_zone=my_cache:10m max_size=10g
                inactive=60m use_temp_path=off;
locaiton的引用:
 proxy_cache my_cache; #定义的zone的名称
            proxy_cache_key $host$uri$is_args$args;
            proxy_cache_valid 200 304 302 1d;

设置好后停止服务:
image

3.日志可视化

这个方案太多了,elk是比较常用的一种,当然,如果机器不多,完全没必要上那么重型的处理方案,有个比较好用的工具可以分析我们的网站的应用情况:
https://goaccess.io/
image

4.ssl(secure sockets layer)的认识

image
用SSL是防止在中间链路劫持请求,因为整个秘钥交换要经历一下几个过程:

image

如何保证在通信之间证书是有公信力的呢?这个就需要证书颁发机构做签发了,这就是为什么我们申请到证书之后要提交域名等信息。因为浏览器请求https的时候是要去证书颁发机构做验证的,如果验证不通过的话,就会拦截请求,当然可以忽略这个拦截继续访问。
那么我们看一波,是如何做校验的。
image
证书的分类:

1 域名验证 DV
只校验域名信息 只要域名OK 浏览器就给通过 会有一把小绿锁
image

2 组织验证 OV

除了一把小绿锁 还有一个感叹号 里面会显示具体信息

3.扩展验证 EV
image

这个是最严的 也是最安全的 一般金融机构会用
image

可以看一波:https://support.google.com/chrome/?p=ui_security_indicator

作为一名阿里云工单支持者,我看过太多太多证书的各种问题导致的不能访问、被拦截、不守信等问题了,大概分几类:
1.客户端的问题导致的无法访问:
比如java客户端、比如安卓端
2.证书和域名不匹配:
比如阿里云SLB扩展域名没加转发策略
比如拿一个不支持通配符的域名当通配域名用
比如拿一个不知道什么玩意儿的证书拿去部署ssl
3.证书链丢失
部署的时候没有补齐证书链,访问的时候被浏览器降级拦截

作为一个曾经的金融狗,我就拿人人贷的证书来说吧,我们要先搞清楚证书是什么鬼:

image

在网络层的验证:
image

好了,何老师的习惯是喜欢结合事实来说话

无论是从网络层,还是浏览器,我们都看到了完整的三层证书,而所有ssl站点的证书都是由这三层构成的:

1.根证书
Certificate: 308205cc308204b4a00302010202106d7d50b3cb5515f024... (id-at-commonName=*.union.360.cn,id-at-stateOrProvinceName=345214227344272254345270202,id-at-localityName=345214227344272254345270202,id-at-organizationName

2.二级证书
Certificate: 308204b53082039da003020102021100ef051a741a1d9409... (id-at-commonName=WoSign OV SSL CA,id-at-organizationName=WoSign CA Limited,id-at-countryName=CN)

3.主证书
Certificate: 308204b43082039ca003020102021100939285400165715f... (id-at-commonName=Certum Trusted Network CA,id-at-organizationalUnitName=Certum Certification Authority,id-at-organizationName=Unizeto Technologies S.A.,id-at-countryName=PL)

根证书,一般微软、安卓或者ios这些操作系统一年才会更新一次他们的根证书库,关于根证书,微软有这样的描述:
使用“证书路径”选项卡,可以查看从所选证书到颁发证书的证书颁发机构 (CA) 的路径。

在证书被信任之前,Windows 必须验证证书是否来自受信任的源。此验证过程称作“路径验证”。

路径验证涉及按层次结构方式处理公钥证书及其颁发者证书,直到证书路径终止于受信任的自签名证书为止。通常这是根 CA 证书。如果路径中的证书之一有问题,或者找不到证书,则认为证书路径是不受信任的证书路径。

典型的证书路径包括根证书和一个或多个中间证书。通过单击“查看证书”,还可以了解有关路径中每个 CA 的证书的详细信息。

下面我们准备来测试部署一个SSL站点:
1.准备域名以及添加解析
image
image

2.配置let's encrypet
https://letsencrypt.readthedocs.io/en/latest/index.html

3.部署ssl证书

拉取安装脚本
git clone https://github.com/letsencrypt/letsencrypt

执行安装
 cd letsencrypt/
./letsencrypt-auto

1、 手动验证 按照提示在申请证书的服务器上使用一个指定的URL提供一个指定的文件内容来进行验证,进行手动验证的服务器IP地址会被 Let's Encrypt 服务端记录在案。
2、 自动验证 在 目标服务器 (指域名解析对应的IP地址的服务器,下同)上运行客户端,并启动一个 80 或 443 端口进行自动验证。包括独立模式和其他web sever验证模式,在 Plugins 中详细解释

./letsencrypt-auto certonly --manual -d 域名

 使用不同的模式来获取证书是因为,在安装的时候 程序需要打开你的服务器的地址 
由于我们的geek.darylhx.cn其实就是目标服务器,所以我们直接执行
 ./letsencrypt-auto certonly --webroot --webroot-path /home/nginx_new/html/ -d geek.darylhx.cn --agree-tos --email hxcloud@gmail.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for geek.darylhx.cn
Using the webroot path /home/nginx_new/html for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/geek.darylhx.cn/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/geek.darylhx.cn/privkey.pem
   Your cert will expire on 2019-04-24. To obtain a new or tweaked
   version of this certificate in the future, simply run
   letsencrypt-auto again. To non-interactively renew *all* of your
   certificates, run "letsencrypt-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le


OJ8K!

证书路径:
   /etc/letsencrypt/live/geek.darylhx.cn/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/geek.darylhx.cn/privkey.pem
lrwxrwxrwx 1 root root 42 Jan 25 01:18 /etc/letsencrypt/live/geek.darylhx.cn/privkey.pem -> ../../archive/geek.darylhx.cn/privkey1.pem

[root@iZj6cc5knv5romns4b9v12Z letsencrypt]# ll  /etc/letsencrypt/live/geek.darylhx.cn/fullchain.pem
lrwxrwxrwx 1 root root 44 Jan 25 01:18 /etc/letsencrypt/live/geek.darylhx.cn/fullchain.pem -> ../../archive/geek.darylhx.cn/fullchain1.pem

同时也告诉了我们自动更新证书的方法:
letsencrypt-auto renew
我们可以把这个加个crontab啊 因为证书90天到期,所以我们可以30天更新一次

最后一步:配置nginx
生成2048位 DH parameters:
$ sudo openssl dhparam -out /etc/ssl/certs/dhparams.pem 2048

时间有点长,这个时候我们可以把配置文件先搞好
image

但是由于我们之前没编译ssl,所以无法使用ssl,这里我们来解锁一下热编译

image

加入需要安装的模块,重新编译,如这里添加–add-module=/data/software/ngx_http_google_filter_module

#./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_sub_module --with-http_gzip_static_module --with-http_stub_status_module --add-module=/data/software/ngx_http_substitutions_filter_module --add-module=/data/software/ngx_http_google_filter_module
# make //千万不要make install,不然就真的覆盖了

image
好了,直接启动nginx吧
image
443端口已经启动,浏览器校验一下:

image
可以看到 我们这个90天的证书
image
关于证书的更新:
image
是基于certbot-auto这个脚本自动更新的,所以我们只需要把这个脚本放到定时任务里面去就好啦。设定个日期,比如30天40天执行一次。由于它更新的是我们的密钥文件,所以不会对站点有任何影响。

5.基于 OpenResty 用 Lua 语言实现简单服务

相关文章
|
4月前
|
缓存 负载均衡 应用服务中间件
Nginx概述
【8月更文挑战第15天】Nginx是一款高性能的HTTP与反向代理服务器,轻量级且高效,首个公开版本发布于2004年,采用C语言编写,跨平台运行。它以内存占用低、支持高并发连接(可达5万)、配置简单及免费开源著称。Nginx内置健康检查机制、支持重写规则与缓存,广泛应用于中国大型网站如百度、京东等。其配置文件结构清晰,包括全局、events、http等区块,并可通过多种模块扩展功能。
52 0
|
5月前
|
前端开发 JavaScript 应用服务中间件
|
应用服务中间件 nginx
65分布式电商项目 - nginx配置虚拟主机概述
65分布式电商项目 - nginx配置虚拟主机概述
70 0
|
缓存 负载均衡 Kubernetes
Nginx概述
Nginx概述
152 0
Nginx概述
|
缓存 负载均衡 网络协议
Nginx基本概述
DNS解析过程、HTTP工作原理 1.用户打开浏览器输入URL地址 2.通过DNS解析url地址找到对应的ip地址 3.通过ip地址向对应的web服务器发送tcp连接请求 1.首先是网络设备去转发数据 2.网站所在平台的私网防火墙(也就是哪个公司写的程序,就会转交给对应公司的防火墙),进行访问控制 3.dns解析的ip地址就是负载均衡调度器的地址,实际上是由网络设备与负载调度器建立tcp三次握手,然后将http请求发给调度器 4.调度器再通过tcp三次握手将http请求发送给web服务器
236 0
Nginx基本概述
|
应用服务中间件 nginx Ubuntu
nginx 的信号控制概述
《nginx 在ubuntu 上的启动,停止,重启》中的停止和重启命令基本都是用信号来控制的。这是一些简单的信号控制。 在Nginx服务器中,通常情况都是通过对其发送控制信号进行控制的,除了以上所说的简单信号控制之外,还有很多的信号控制。
821 0
|
Web App开发 监控 应用服务中间件
Nginx 概述及日常管理
Nginx(发音同 engine x)是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。
1039 0
|
2月前
|
应用服务中间件 BI nginx
Nginx的location配置详解
【10月更文挑战第16天】Nginx的location配置详解
|
2月前
|
缓存 负载均衡 安全
Nginx常用基本配置总结:从入门到实战的全方位指南
Nginx常用基本配置总结:从入门到实战的全方位指南
308 0
|
2月前
|
应用服务中间件 Linux nginx
Jetson 环境安装(四):jetson nano配置ffmpeg和nginx(亲测)之编译错误汇总
这篇文章是关于在Jetson Nano上配置FFmpeg和Nginx时遇到的编译错误及其解决方案的汇总。
97 4