1.nginx基础学习笔记

1.nginx基础

一.Nginx介绍

Nginx是十分轻量级的HTTP服务器。Nginx,它的发音为“engine X”,是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器。Nginx是由俄罗斯人 Igor Sysoev为俄罗斯访问量第二的 Rambler.ru站点开发的,它已经在该站点运行超过两年半了。Igor Sysoev在建立的项目时,使用基于BSD许可。

二.安装Nginx

1.常用版本分为四大阵营

  • Nginx开源版 http://nginx.org/ 只提供最基本的功能

  • Nginx plus 商业版https://www.nginx.com 非常丰富的功能,就是比较费钱。

  • Openresty http://openresty.org 集成了lua脚本,可以在这上面开发出商业版的功能。

  • Tengine http://tengine.taobao.org/ 淘宝的,也很牛逼。

2.Docker安装nginx

用docker安装一个开源版本,学习通透之后再看别的版本。

先搜索一下版本

[root@localhost ~]#  docker search nginx

image-20230430211013594

下载镜像

[root@localhost ~]#  docker search nginx

image-20230430211233520

创建容器并运行镜像

[root@localhost ~]# docker run -di --name=nginx -p 80:80 nginx
[root@localhost ~]# docker update --restart=always 93ab9c7bcec8 #设置容器开机自启动

3.编译安装(用这个)

(1)直接打开官网下载

官网地址:https://nginx.org/en/download.html

image-20230501085701761

(2)上传至服务器并解压

[root@localhost ~]# tar -zxvf nginx-1.20.2.tar.gz

解压完可以看到里面的目录

image-20230501090027670

(3)编译安装

进入nginx-1.20.2执行命令。

./configure --prefix=/usr/local/nginx   # 注意/usr/local/nginx这个是安装过程中创建的,原先不能有
make && make install

如果出现警告或报错

提示

checking for OS
+ Linux 3.10.0-693.el7.x86_64 x86_64
checking for C compiler ... not found
./configure: error: C compiler cc is not found

安装gcc

yum install -y gcc

提示:

./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.

安装perl库

yum install -y pcre pcre-devel

提示:

./configure: error: the HTTP gzip module requires the zlib library.

You can either disable the module by using --without-http_gzip_module

option, or install the zlib library into the system, or build the zlib library

statically from the source with nginx by using --with-zlib=<path> option.

安装zlib库

yum install -y zlib zlib-devel

接下来执行

make && make install

启动Nginx

进入安装好的目录 /usr/local/nginx/sbin

./nginx 启动
./nginx -s stop 快速停止
./nginx -s quit 优雅关闭,在退出前完成已经接受的连接请求
./nginx -s reload 重新加载配置

通过ip访问到

4.开机启动

步骤1:先创建个service

vim /usr/lib/systemd/system/nginx.service

步骤2:写入以下命令

[Unit]
Description=nginx -  web server
After=network.target remote-fs.target nss-lookup.target
  
[Service]
Type=forking
PIDFile=/usr/local/nginx/logs/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf
ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s stop
ExecQuit=/usr/local/nginx/sbin/nginx -s quit
PrivateTmp=true
  
[Install]
WantedBy=multi-user.target

步骤3:我们先查找下nginx服务状态

ps -ef | grep nginx

在的话先停止

image-20230501090845713

步骤4:重新加载服务

systemctl daemon-reload 

步骤5:启动服务

systemctl start nginx.service

开机启动

systemctl enable nginx.service

防火墙放行形80端口

firewall-cmd --zone=public --add-port=80/tcp --permanent

重启防火墙

systemctl restart firewalld

三.目录结构与基本运行原理

1.主要目录

image-20230430225920165

*_temp是运行的时候产生的临时目录

conf

顾名思义,这里是用来存放配置文件的目录,这里最为主要的是 nginx.conf 这个文件,这个是Nginx的主配置文件。若以后需要手动添加其它的配置文件,还请一并放在此目录下。

image-20230501094048856

主要关注nginx.conf。内容如下:

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

能看出来格式很像JSON串。

worker_processes
默认为1,表示开启一个业务进程。当然你也可以把他改成10,但需要注意的是这个东西跟cpu的内核数量是有挂钩的。如果说咱们的cpu只有2核,那在这里写个3那可能就没啥意义了。如果把一个cpu内核绑定到多个进程上的话,按照时间片轮转机制来说,这样只会降低cpu的利用率。打个比方:我让A帮我去买份饭回来,但是紧接着B和C也想让A帮他们带饭,这样就延长的A帮我带饭回来的时间,那对于 我 来说A的工作效率其实是降低了的。

worker_connections

单个进程可接受连接数,默认为1024。

include mime.types;

引入http mime类型

这里的 include 命令会把另外一个配置文件引用到我们的当前的这个配置文件当中。 这里的 mime.types 配置文件是**用来申明所返回或发送文件的类型。**咱们的浏览器其实不能根据文件的后缀名来判断文件的格式/类型,它必须由服务器发送的http头里所标注的信息来展示文件的类型。

我们来查看一下mime.types的内容

image-20230501225535199


types {
    text/html                                        html htm shtml;
    text/css                                         css;
    text/xml                                         xml;
    image/gif                                        gif;
    image/jpeg                                       jpeg jpg;
    application/javascript                           js;
    application/atom+xml                             atom;
    application/rss+xml                              rss;

    text/mathml                                      mml;
    text/plain                                       txt;
    text/vnd.sun.j2me.app-descriptor                 jad;
    text/vnd.wap.wml                                 wml;
    text/x-component                                 htc;

    image/png                                        png;
    image/svg+xml                                    svg svgz;
    image/tiff                                       tif tiff;
    image/vnd.wap.wbmp                               wbmp;
    image/webp                                       webp;
    image/x-icon                                     ico;
    image/x-jng                                      jng;
    image/x-ms-bmp                                   bmp;

    font/woff                                        woff;
    font/woff2                                       woff2;

    application/java-archive                         jar war ear;
    application/json                                 json;
    application/mac-binhex40                         hqx;
    application/msword                               doc;
    application/pdf                                  pdf;
    application/postscript                           ps eps ai;
    application/rtf                                  rtf;
    application/vnd.apple.mpegurl                    m3u8;
    application/vnd.google-earth.kml+xml             kml;
    application/vnd.google-earth.kmz                 kmz;
    application/vnd.ms-excel                         xls;
    application/vnd.ms-fontobject                    eot;
    application/vnd.ms-powerpoint                    ppt;
    application/vnd.oasis.opendocument.graphics      odg;
    application/vnd.oasis.opendocument.presentation  odp;
    application/vnd.oasis.opendocument.spreadsheet   ods;
    application/vnd.oasis.opendocument.text          odt;
    application/vnd.openxmlformats-officedocument.presentationml.presentation
                                                     pptx;
    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
                                                     xlsx;
    application/vnd.openxmlformats-officedocument.wordprocessingml.document
                                                     docx;
    application/vnd.wap.wmlc                         wmlc;
    application/x-7z-compressed                      7z;
    application/x-cocoa                              cco;
    application/x-java-archive-diff                  jardiff;
    application/x-java-jnlp-file                     jnlp;
    application/x-makeself                           run;
    application/x-perl                               pl pm;
    application/x-pilot                              prc pdb;
    application/x-rar-compressed                     rar;
    application/x-redhat-package-manager             rpm;
    application/x-sea                                sea;
    application/x-shockwave-flash                    swf;
    application/x-stuffit                            sit;
    application/x-tcl                                tcl tk;
    application/x-x509-ca-cert                       der pem crt;
    application/x-xpinstall                          xpi;
    application/xhtml+xml                            xhtml;
    application/xspf+xml                             xspf;
    application/zip                                  zip;

    application/octet-stream                         bin exe dll;
    application/octet-stream                         deb;
    application/octet-stream                         dmg;
    application/octet-stream                         iso img;
    application/octet-stream                         msi msp msm;

    audio/midi                                       mid midi kar;
    audio/mpeg                                       mp3;
    audio/ogg                                        ogg;
    audio/x-m4a                                      m4a;
    audio/x-realaudio                                ra;

    video/3gpp                                       3gpp 3gp;
    video/mp2t                                       ts;
    video/mp4                                        mp4;
    video/mpeg                                       mpeg mpg;
    video/quicktime                                  mov;
    video/webm                                       webm;
    video/x-flv                                      flv;
    video/x-m4v                                      m4v;
    video/x-mng                                      mng;
    video/x-ms-asf                                   asx asf;
    video/x-ms-wmv                                   wmv;
    video/x-msvideo                                  avi;
}

可以看到这里的格式有点像kv键值对。比如说这里18行的 exe 文件,他所得到的是一个叫做 application/octet-stream 的类型,意思是以数据流的方式让浏览器去加载,并询问用户是否要进行下载操作。那比如说咱们现在有个一个非常奇怪的格式,这个格式是平常根本见不到的格式,那我们就可以将其写在此文件中,让他按照你想要的格式去让浏览器解读。

default_type application/octet-stream;

如果mime类型没匹配上,默认使用二进制流的方式传输。

sendfile

使用linux的 sendfile(socket, file, len) 高效网络传输,也就是数据0拷贝技术。 默认开启sendfile。

打个比方:咱们现在有两台电脑,我们需要从PC1拿点东西去PC2,那我们就得先拿U盘去PC1里面拷贝,然后再用U盘拷贝到PC2中,而如果开启了这个sendfile,那就相当于直接隔空传过去,省去了拷贝的过程。

在这里就是省略了从磁盘拷贝到nginx程序内存等待一系列的拷贝过程。

html

用来存放静态文件的默认目录 html、css等。默认情况下的index.html

image-20230501091926739

index.html 默认访问时候出现的主页。

50x.html 访问出错的时候出现的。

sbin

nginx的主程序

logs

nginx日志文件

image-20230501092054003

access.log 用户的访问日志。其实也就是在用户访问咱们的时候,会把时间、地点、人物、人物所访问的文件、人物所带的参数等信息记录在此。所以这个文件通常都是非常的大(但是它并不会影响到Nginx的性能),为了防止数据写不进来而导致服务出现问题,所以在配置文件中会严格的把控 access.log 文件的大小,一旦它达到了那个指定的大小,那么系统就是重新新建一个文件用来继续写入访问信息日志。

error.log 系统错误的日志。这个日志就比上面的 access.log 要小很多了。说白了就是用户在访问服务时所遇到的错误,都会被记录在这个 error.log 里面。

nginx.pid 记录当前nginx的进程号。 能看出来这个文件跟上面的不一样啊,后缀为pid而不是log。这个文件是用来记录咱们Nginx主进程的ID号的。

image-20230501092204429

image-20230501092227113

2.基本运行原理

Nginx启动后会有两个进程master和worker。master调用fork系统调用创建出子进程。

master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。nginx的进程模型,可以由下图来表示:

img

master进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制nginx,只需要向master进程发送信号就行了。比如kill -HUP pid,则是告诉nginx,从容地重启nginx,我们一般用这个信号来重启nginx(等效于高级命令:nginx -s reload),或重新加载配置,因为是从容地重启,因此服务是不中断的。master进程在接收到HUP信号后是怎么做的呢?首先master进程在接到信号后,会先重新加载配置文件,然后再启动新的worker进程,并向所有老的worker进程发送信号,告诉他们可以光荣退休了。新的worker在启动后,就开始接收新的请求,而老的worker在收到来自master的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。

worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接。首先,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。

所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。

Nginx采用这种进程模型有很多好处。首先,对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。

四.Nginx基础配置

nginx.conf最小配置文件

worker_processes  1;


events {
    worker_connections  1024;
}


http {

    include       mime.types;
    default_type  application/octet-stream;


    sendfile        on;

    keepalive_timeout  65;

	# 虚拟主机
    server {
        listen       80;    # 监听端口号
        server_name  localhost;  # 配置域名或者主机名


        location / {
            root   html;
            index  index.html index.htm;
        }

     
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }


}

worker_processes
默认为1,表示开启一个业务进程。当然你也可以把他改成10,但需要注意的是这个东西跟cpu的内核数量是有挂钩的。如果说咱们的cpu只有2核,那在这里写个3那可能就没啥意义了。如果把一个cpu内核绑定到多个进程上的话,按照时间片轮转机制来说,这样只会降低cpu的利用率。打个比方:我让A帮我去买份饭回来,但是紧接着B和C也想让A帮他们带饭,这样就延长的A帮我带饭回来的时间,那对于 我 来说A的工作效率其实是降低了的。

worker_connections

单个进程可接受连接数,默认为1024。

include mime.types;

引入http mime类型

default_type application/octet-stream;

如果mime类型没匹配上,默认使用二进制流的方式传输。

sendfile

使用linux的 sendfile(socket, file, len) 高效网络传输,也就是数据0拷贝技术。 默认开启sendfile。

打个比方:咱们现在有两台电脑,我们需要从PC1拿点东西去PC2,那我们就得先拿U盘去PC1里面拷贝,然后再用U盘拷贝到PC2中,而如果开启了这个sendfile,那就相当于直接隔空传过去,省去了拷贝的过程。

在这里就是省略了从磁盘拷贝到nginx程序内存等待一系列的拷贝过程。

sendfile off;

直接上图来讲:

image-20230502093546368

sendfile on;

image-20230502093642493

keepalive_timeout 65

连接的超时时间限制。

server {}

这里可以把一个server看成是一个主机,所以一个Nginx可以拥有多台这样的主机,它们通过不同的端口号 listen 来区分。当然端口号也可以相同,那样的话就需要通过 server_name 来分区了。开启多个主机的方式也叫作虚拟主机(vhost)。

location / {}

咱们这回先举例子。 http://abc.com/xxx/index.html 这个叫做url,没啥问题吧。而这其中的 /xxx/index.html 叫做uri。当这个uri被匹配上的时候,那么就会进入到这个 location / {} 中,去找里面的root目录。这里的 root html; 中的html其实是个相对路径,就是咱们下一小节提到的那个html目录。

error_page 500 502 503 504 /50x.html;

简单的来说,如果出现了这四个报错,那么就会进入到这个 50x.html 中,这个文件也会在下一小节中提到。

五.虚拟主机与域名解析

1.域名、dns、ip地址的关系

2.浏览器、Nginx与http协议

3.虚拟主机原理

4.域名解析与泛域名解析实战

image-20230502095426647

image-20230502095440533

5.域名解析相关企业项目实战技术架构

6.多用户二级域名

(1)端口号区分

在linux /根目录下创建www文件夹,在www下分别创建两个文件夹。结构如下。

image-20230502100910950

配置不同的虚拟主机:一个80端口,一个88端口

image-20230502101000769

区分端口号访问如下

image-20230502101254599

image-20230502101243668

(2)域名区分

接下来用不同的二级域名进行区分。

hosts修改

image-20230502101706551

nginx配置修改。同一端口号,不同域名

image-20230502102020150

端口号 + 主机名必须是唯一的,否则会报错。

6.1 ServerName匹配规则

上面同一个端口,不同域名配置了两份。太麻烦了!!

  • 我们可以在同一servername中匹配多个域名
    • 完整匹配
    • 通配符匹配
    • 通配符结束匹配
    • 正则匹配
完整匹配

image-20230502102718564

效果如下:

image-20230502102829977

通配符匹配

image-20230502103002555

image-20230502103047953

通配符结束匹配

image-20230502103832331

image-20230502103839670

正则匹配

在fastDFS上传文件的时候是不是用过?

image-20230502104058204

image-20230502104200209

六.应用场景

  • 网关、代理与反向代理

  • 反向代理在系统架构中的应用场景

  • Nginx的反向代理配置

  • 基于反向代理的负载均衡器

  • 负载均衡策略

1.反向代理

1.1 理论介绍

正向代理与反向代理,正向与反向是如何区分的?
正向与反向的目标是客户端,是对于客户端来说是正向还是反向

正向代理:有明确目标的请求是从客户端发出的,客户端发起的请求知道明确的目的地,只不过自己访问不到,比如外网,此时就需要借助一个代理服务器帮助从而去访问要访问的目标。正向代理中,被代理的是客户端,服务器端不知道请求是谁发的。
image-20230517133216999

反向代理:有明确目标的请求是从服务器发出的。客户端并不知道自己的请求真实的会发送到那已台服务器,而服务器是知道谁发给他的请求的。反向代理中,被代理的是服务器,客户端不知道请求真实发给谁。

image-20230517133304687

反向代理在系统架构中的应用场景:

传统公司系统架构:

image-20230517133736255

image-20230517133845120

首先用户通过路由,域名解析,到互联网,从而发送到机房网关,然后经过防火墙到nginx服务器,之后由nginx服务器代理转发到真正的服务器。
中小型互联网企业中:
nginx在其中不只是充当一个反向代理的功能,还会去做一些业务逻辑上功能上的作用,再就是回去做文件网关服务器等。

1.2 实战

准备4个nginx服务器。

nginx1:192.168.202.129

nginx2:43.143.141.57

nginx3:192.168.202.104

nginx4:192.168.202.103

用nginx1反向代理nginx2,3,4三个服务器。

nginx1配置文件 先代理到nginx2实验一下


#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;


        location / {
		
			proxy_pass http://43.143.141.57; #代理到nginx2了
            # root   html;
            # index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        
    }

}

效果

image-20230517143725811

2.基于反向代理的负载均衡

2.1 理论

什么是负载均衡:

为了提升网站的各方面能力,我们一般会把多台机器组成一个集群对外提供服务。然而,我们的网站对外提供的访问入口都是一个的,比如www.taobao.com。那么当用户在浏览器输入www.taobao.com的时候如何将用户的请求分发到集群中不同的机器上呢,这就是负载均衡在做的事情。

image-20230517165624539

负载均衡(Load Balance),意思是将负载(工作任务,访问请求)进行平衡、分摊到多个操作单元(服务器,组件)上进行执行。是解决高性能,单点故障(高可用),扩展性(水平伸缩)的终极解决方案。

负载均衡核心:负载均衡算法实现

负载均衡服务器在决定将请求转发到具体哪台真实服务器的时候,是通过负载均衡算法来实现的。负载均衡算法,是一个负载均衡服务器的核心。

就像电影院门口的引导员一样,他根据什么把排队人员分配到具体的入口呢?是哪个入口人少吗?还是哪个入口速度最快?还是哪个入口最近呢?如果来了一个VIP怎么办呢?

负载均衡算法可以分为两类:静态负载均衡算法动态负载均衡算法

静态负载均衡算法包括:轮询,比率,优先权。

动态负载均衡算法包括: 最少连接数,最快响应速度,观察方法,预测法,动态性能分配,动态服务器补充,服务质量,服务类型,规则模式

2.2 实战
(1) 配置轮询负载均衡

nginx1.conf配置


#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
	
	#定义一组服务器
	upstream httpds{
		server 43.143.141.57;    #代理到nginx2
		server 192.168.202.104;	 #代理到nginx3
		server 192.168.202.103;  #代理到nginx4
		# server 192.168.8.102 weight=10 down; #down表示不参与负载均衡
		# server 192.168.8.102 weight=10 backup; #backup表示是备用服务器,没有服务器可用的时候使用
	}
	
	#虚拟主机的配置
    server {
        listen       80;
        server_name  localhost;
	
		#配置根目录以及默认页面
        location / {
		
			proxy_pass http://httpds;
            # root   html;
            # index  index.html index.htm;
        }

		#出错页面配置
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        
    }

}

(2) 配置权重负载均衡

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

nginx1.conf配置

#定义一组服务器
upstream httpds{
    server 43.143.141.57    	weight=3;    #代理到nginx2
    server 192.168.202.104   	weight=2;	 #代理到nginx3
    server 192.168.202.103		weight=1;    #代理到nginx4
    # server 192.168.8.102 weight=10 down; #down表示不参与负载均衡
    # server 192.168.8.102 weight=10 backup; #backup表示是备用服务器,没有服务器可用的时候使用
}
  • down:表示当前的server暂时不参与负载

  • weight:默认为1.weight越大,负载的权重就越大。

  • backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。

(3) 其他负载均衡策略(不常用)

ip_hash
根据客户端的ip地址转发同一台服务器,可以保持会话,但是很少用这种方式去保持会话,例如我们当前正在使用wifi访问,当切换成手机信号访问时,会话就不保持了。

least_conn
最少连接访问,优先访问连接最少的那一台服务器,这种方式也很少使用,因为连接少,可能是由于该服务器配置较低,刚开始赋予的权重较低。

url_hash(需要第三方插件)
根据用户访问的url定向转发请求,不同的url转发到不同的服务器进行处理(定向流量转发)。

fair(需要第三方插件)
根据后端服务器响应时间转发请求,这种方式也很少使用,因为容易造成流量倾斜,给某一台服务器压垮。

3.动静分离

3.1 理论

为了提高网站的响应速度,减轻程序服务器(Tomcat,Jboss等)的负载,对于静态资源,如图片、js、css等文件,可以在反向代理服务器中进行缓存,这样浏览器在请求一个静态资源时,代理服务器就可以直接处理,而不用将请求转发给后端服务器。对于用户请求的动态文件,如servlet、jsp,则转发给Tomcat,Jboss服务器处理,这就是动静分离。即动态文件与静态文件的分离。

原理图

img

动静分离可通过location对请求url进行匹配,将网站静态资源(HTML,JavaScript,CSS,img等文件)与后台应用分开部署,提高用户访问静态代码的速度,降低对后台应用访问。通常将静态资源放到nginx中,动态资源转发到tomcat服务器中。

3.2 实战

首先选择一个linux(nginx3)192.168.202.104服务器安装tomcat:

[root@VM-8-13-centos ~]# cd /usr/local/
# 启动Tomcat
[root@hadoop104 bin]# ./startup.sh 

访问一下:

image-20230518135955493

随便搞一点代码放进tomcat的webapps的ROOT中。先把里面的删除掉。

image-20230518144833142

访问一下看看

image-20230518144923649

接下来我们通过nginx1192.168.202.129反向代理到nginx2的8080窗口上。

nginx1配置

#虚拟主机的配置 proxy_pass
server {
    listen       80;
    server_name  localhost;

    #配置根目录以及默认页面
    location / {

        proxy_pass http://192.168.202.104:8080;
        # root   html;
        # index  index.html index.htm;
    }

    #出错页面配置
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }

}

image-20230518145044993

现在我们把nginx3中的静态资源img,js,css文件夹都删除掉。

再次访问图片样式都不见了。

image-20230518150230263

接下来将静态资源配置到代理服务器nginx1上:

image-20230518150255711

nginx1配置静态路径访问本机nginx的路径。

通过location来配置。

#虚拟主机的配置
server {
    listen       80;
    server_name  localhost;

    #配置根目录以及默认页面
    location / {

        proxy_pass http://192.168.202.104:8080;
        # root   html;
        # index  index.html index.htm;
    }
	# 配置img
    location /img {

        root   /usr/local/nginx/html;
        index  index.html index.htm;
    }
	# 配置css
    location /css {
        root   /usr/local/nginx/html;
        index  index.html index.htm;
    }	
	# 配置js
    location /js {

        root   /usr/local/nginx/html;
        index  index.html index.htm;
    }

    #出错页面配置
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }


}

图片和样式都回来了。

image-20230518151007309

使用正则配置动静分离

常见的Nginx正则表达式

^ :匹配输入字符串的起始位置
$ :匹配输入字符串的结束位置
* :匹配前面的字符零次或多次。如“ol*”能匹配“o”及“ol”、“oll”
+ :匹配前面的字符一次或多次。如“ol+”能匹配“ol”及“oll”、“olll”,但不能匹配“o”
? :匹配前面的字符零次或一次,例如“do(es)?”能匹配“do”或者“does”,”?”等效于”{0,1}. :匹配除“\n”之外的任何单个字符,若要匹配包括“\n”在内的任意字符,请使用诸如“[.\n]”之类的模式
\ :将后面接着的字符标记为一个特殊字符或一个原义字符或一个向后引用。如“\n”匹配一个换行符,而“\$”则匹配“$”
\d :匹配纯数字
{n} :重复 n 次
{n,} :重复 n 次或更多次
{n,m} :重复 n 到 m 次
[] :定义匹配的字符范围
[c] :匹配单个字符 c
[a-z] :匹配 a-z 小写字母的任意一个
[a-zA-Z0-9] :匹配所有大小写字母或数字
() :表达式的开始和结束位置
| :或运算符  //例(js|img|css)

location正则:

//location大致可以分为三类
精准匹配:location = /{}
一般匹配:location /{}
正则匹配:location ~/{}
//location常用的匹配规则:
= :进行普通字符精确匹配,也就是完全匹配。
^~ :表示前缀字符串匹配(不是正则匹配,需要使用字符串),如果匹配成功,则不再匹配其它 location。
~ :区分大小写的匹配(需要使用正则表达式)。
~* :不区分大小写的匹配(需要使用正则表达式)。
!~ :区分大小写的匹配取非(需要使用正则表达式)。
!~* :不区分大小写的匹配取非(需要使用正则表达式)。
//优先级
首先精确匹配 =
其次前缀匹配 ^~
其次是按文件中顺序的正则匹配 ~~*
然后匹配不带任何修饰的前缀匹配
最后是交给 / 通用匹配

注意:

  • 精确匹配: = , 后面的表达式中写的是纯字符串
  • 字符串匹配: ^~无符号匹配 , 后面的表达式中写的是纯字符串
  • 正则匹配: ~~*!~!~* , 后面的表达式中写的是正则表达式

location的说明

 (1)location = / {}
=为精确匹配 / ,主机名后面不能带任何字符串,比如访问 //data,则 / 匹配,/data 不匹配
再比如 location = /abc,则只匹配/abc ,/abc//abcd不匹配。若 location  /abc,则即匹配/abc 、/abcd/ 同时也匹配 /abc/。

(2)location / {}
因为所有的地址都以 / 开头,所以这条规则将匹配到所有请求 比如访问 //data,/ 匹配, /data 也匹配,
但若后面是正则表达式会和最长字符串优先匹配(最长匹配)

(3)location /documents/ {}
匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索其它 location
只有其它 location后面的正则表达式没有匹配到时,才会采用这一条

(4)location /documents/abc {}
匹配任何以 /documents/abc 开头的地址,匹配符合以后,还要继续往下搜索其它 location
只有其它 location后面的正则表达式没有匹配到时,才会采用这一条

(5)location ^~ /images/ {}
匹配任何以 /images/ 开头的地址,匹配符合以后,停止往下搜索正则,采用这一条

(6)location ~* \.(gif|jpg|jpeg)$ {}
匹配所有以 gif、jpg或jpeg 结尾的请求
然而,所有请求 /images/ 下的图片会被 location ^~ /images/ 处理,因为 ^~ 的优先级更高,所以到达不了这一条正则

(7)location /images/abc {}
最长字符匹配到 /images/abc,优先级最低,继续往下搜索其它 location,会发现 ^~~ 存在

(8)location ~ /images/abc {}
匹配以/images/abc 开头的,优先级次之,只有去掉 location ^~ /images/ 才会采用这一条

(9)location /images/abc/1.html {}
匹配/images/abc/1.html 文件,如果和正则 ~ /images/abc/1.html 相比,正则优先级更高

优先级总结:
(location =) > (location 完整路径) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 部分起始路径) > (location /)

实际网站使用中,至少有三个匹配规则定义:

  • 第一个必选规则

直接匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理,比如说官网。这里是直接转发给后端应用服务器了,也可以是一个静态首页。

location = / {
    proxy_pass http://127.0.0.1:8080/; 
}
  • 第二个必选规则

处理静态文件请求,这是nginx作为http服务器的强项,有两种配置模式,目录匹配或后缀匹配,任选其一或搭配使用

location ^~ /static/ {
    root /webroot/static/;
}

location ~* \.(html|gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}

  • 第三个规则

通用规则,用来转发动态请求到后端应用服务器。可以转到后端服务的网关

location /api/ {
    proxy_pass http://127.0.0.1:3000/api/
}

URLRewrite

rewrite是实现URL重写的关键指令,根据regex(正则表达式)部分内容,重定向到repacement,结尾是flag标记。

格式:

rewrite是实现URL重写的关键指令,根据regex (正则表达式)部分内容,
重定向到replacement,结尾是flag标记。


rewrite 	<regex> 	<replacement> 	[flag];
关键字 正则 替代内容 flag标记
关键字:其中关键字error_log不能改变
正则:perl兼容正则表达式语句进行规则匹配
替代内容:将正则匹配的内容替换成replacement
flag标记:rewrite支持的flag标记


rewrite参数的标签段位置:
server,location,if

flag标记说明:
last #本条规则匹配完成后,继续向下匹配新的location URI规则
break #本条规则匹配完成即终止,不再匹配后面的任何规则
redirect #返回302临时重定向,浏览器地址会显示跳转后的URL地址
permanent #返回301永久重定向,浏览器地址栏会显示跳转后的URL地址

URLRewrite的优缺点

优点:掩藏真实的url以及url中可能暴露的参数,以及隐藏web使用的编程语言,提高安全性便于搜索引擎收录

缺点:降低效率,影响性能。如果项目是内网使用,比如公司内部软件,则没有必要配置。

实例

配置nginx1中的nginx.conf

将/test.html装换成/index.html?testParam=3

#配置根目录以及默认页面
location / {

    rewrite ^/test.html$ /index.html?testParam=3 break;
    proxy_pass http://192.168.202.104:8080;
    # root   html;
    # index  index.html index.htm;
}

也可以用正则表达式的形式: rewrite ^/[0-9]+.html$ /index.html?testParam=$1 break; //$1表示第一个匹配的字符串

image-20230518161633982

4.反向代理 + 负载均衡 + URLRewrite = 网关服务器

开启nginx3的防火墙

systemctl start firewalld

开启后通过nginx1代理不过来了:

image-20230518163306431

重载规则

firewall-cmd --reload

查看已配置规则

firewall-cmd --list-all

image-20230518162151155

添加指定端口和ip访问(添加之后记得重新启动防火墙)

开放nginx1192.168.202.129的ip和8080端口访问

firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="192.168.202.129" port protocol="tcp" port="8080" accept"

指定ip,表示通过nginx1代理转发过来的请求才能进来。nginx1相当于网关。开放之后可以挺高nginx1代理过来了。

image-20230518163456603

移除规则

firewall-cmd --permanent --remove-rich-rule="rule family="ipv4" source address="192.168.202.129" port protocol="tcp" port="8080" accept"

重启防火墙

firewall-cmd --reload

使用负载均衡的方式访问:

nginx1配置

#定义一组服务器
upstream httpds{
    server 43.143.141.57:8080    	weight=3;    #代理到nginx2
    server 192.168.202.104:8080   	weight=2;	 #代理到nginx3
    server 192.168.202.103:8080		weight=1;    #代理到nginx4

}

#虚拟主机的配置
server {
    listen       80;
    server_name  localhost;

    #配置根目录以及默认页面
    location / {

        #rewrite ^/[0-9]+.html$ /index.html?testParam=$1 break;
        rewrite ^/test.html$ /index.html?testParam=3 break;
        proxy_pass http://httpds;
        # root   html;
        # index  index.html index.htm;
    }

    location /img {

        root   /usr/local/nginx/html;
        index  index.html index.htm;
    }

    location /css {

        root   /usr/local/nginx/html;
        index  index.html index.htm;
    }

    location /js {

        root   /usr/local/nginx/html;
        index  index.html index.htm;
    }



    #出错页面配置
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }


}

5.防盗链配置

5.1 理论

盗链是指服务提供商自己不提供服务的内容,通过技术手段绕过其它有利益的最终用户界面(如广告),直接在自己的网站上向最终用户提供其它服务提供商的服务内容,骗取最终用户的浏览和点击率。受益者不提供资源或提供很少的资源,而真正的服务提供商却得不到任何的收益。

valid_referers解释
可以同时携带多个参数,表示多个 referer 头部都生效。

参数值

  • none:允许没有 referer 信息的请求访问,即直接通过url访问。
  • blocked:请求头Referer字段不为空(即存在Referer),但是值可以为空(值被代理或者防火墙删除了),并且允许refer不以“http://”或“https://”开头,通俗点说就是允许“http://”或"https//"以外的请求。
  • server_names:若 referer 中站点域名与 server_name 中本机域名某个匹配,则允许该请求访问。
  • 其他字符串类型:检测referer与字符串是否匹配,如果匹配则允许访问,可以采用通配符*。
  • 正则表达式:若 referer 的值匹配上了正则,就允许访问。

invalid_referer 变量

  • 允许访问时变量值为空
  • 不允许访问时变量值为 1
5.2 实战

nginx防盗链配置

为了模拟盗链,在这里让nginx1192.168.202.129为服务站点(正儿八经的客户端),nginx2192.168.202.102为网关服务器(里面有静态资源),nginx3192.168.202.104是后端tomcat服务器。nginx4是想要盗链的服务器(假冒的客户端)。

nginx1的conf文件


#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
	
	
	#虚拟主机的配置
    server {
        listen       80;
        server_name  localhost;
	
		#配置根目录以及默认页面
        location / {
			
			proxy_pass http://192.168.202.102;
            # root   html;
            # index  index.html index.htm;
        }
		
	

		#出错页面配置
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        
    }

}

nginx2.conf

server {
    listen       80;
    server_name  localhost;


    location / {

        proxy_pass	http://192.168.202.104:8080;  #转到nginx3
        #root   html;
        #index  index.html index.htm;
    }

    location ~*/(css|img|js) {
        root /usr/local/nginx/static;
        index index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }


}

nginx3启动一个tomcat,端口号是8080。对外提供服务。

通过nginx1访问:显示一切正常。

image-20230518174038092

现在nginx4192.168.202.103准备开始盗链了。这个配置其实和nginx1一样

nginx4.conf

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
	
	
	#虚拟主机的配置
    server {
        listen       80;
        server_name  localhost;
	
		#配置根目录以及默认页面
        location / {
			
			proxy_pass http://43.143.141.57;
            # root   html;
            # index  index.html index.htm;
        }
		
	

		#出错页面配置
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        
    }

}

通过nginx4也可以访问到并且得到图片。

image-20230520104216660

防盗链需要在网关服务器nginx2上配置。只有从nginx1的来源才能访问图片和样式。

server {
    listen       80;
    server_name  localhost;


    location / {

        proxy_pass	http://192.168.202.104:8080;  #转到nginx3
        #root   html;
        #index  index.html index.htm;
    }

    location ~*/(css|img|js) {
        valid_referers 192.168.202.129;  #valid_referers 指令,配置是否允许 referer 头部以及允许哪些 referer 访问。											真实中192.168.202.129不是ip而是域名(去掉http://前缀)
        if ($invalid_referer) {  # 注意这里if后要加空格
            return 403; ## 返回错误码
        }
        
        root /usr/local/nginx/static;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }


}

现在通过nginx4访问就不行了。

image-20230520111338665

使用curl测试

不带头

curl -I http://192.168.202.102/img/1.jpg

image-20230520113502119

带引用

curl -e "http://baidu.com" -I http://192.168.202.102/img/1.jpg

image-20230520113515979

六. nginx高可用配置

nginx如果只有一台机器,那么nginx如果宕机了,那么整个系统对外都提供不了服务了。这个时候可以采用主备机的方式搭建一猪一从机器。主备不是同时对外提供服务的。和数据库的主备是不一样的,数据库的主备是主机写读,从机读。

高可用场景及解决方案

Keepalived软件起初是专为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能。因此,Keepalived除了能够管理LVS软件外,还可以作为其他服务(例如:Nginx、Haproxy、MySQL等)的高可用解决方案软件。VRRP出现的目的就是为了解决静态路由单点故障问题的,它能够保证当个别节点宕机时,整个网络可以不间断地运行。所以,Keepalived 一方面具有配置管理LVS的功能,同时还具有对LVS下面节点进行健康检查的功能,另一方面也可实现系统网络服务的高可用功能。

keepalived官网http://www.keepalived.org

keepalived服务的三个重要功能:

  • 管理LVS负载均衡软件
  • 实现LVS集群节点的健康检查中
  • 作为系统网络服务的高可用性(failover)

原理,keepalived会有一个虚拟ip。先把虚拟放在主机上,外界访问的时候访问虚拟IP,备机会实时和主机进行心跳检测。一旦主机挂了,从机就会使用这个虚拟IP对外提供服务。

安装keepalived

centos安装命令:在nginx1192.168.202.102和nginx2192.168.202.129上都安装

 yum install -y keepalived

配置

使用yum安装后配置文件在

/etc/keepalived/keepalived.conf

实战

在该实战中,nginx1为主nginx,nginx2为备用机,首先需要修改nginx1和nginx2的keepalived.conf配置。

nginx1主机的keepalived.conf配置

! Configuration File for keepalived

global_defs {
   router_id LB_102
}

vrrp_instance VI_102 {
    state MASTER
    interface ens33
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.202.100    # 配置一个虚拟iP
    }
}


使用systemctl start keepalived启动keepalived,查看ip发现多了虚拟ip192.168.202.100:

image-20230520135731337

nginx2备机配置

! Configuration File for keepalived

global_defs {
   router_id LB_101
}

vrrp_instance VI_102 {
    state BACKUP
    interface ens33
    virtual_router_id 51
    priority 50
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.202.100
    }
}


image-20230520140452435

通过虚拟ip访问,通过主机nginx1访问服务器。

image-20230520141357003

nginx1关机。

image-20230520141443938

虚拟ip跑到了nginx2上。

image-20230520141535436

通过虚拟IP访问还是正常可以的。

image-20230520141652020

七.Https证书配置

1. 不安全的Http协议

协议不安全的根本原因:

数据没有加密:
HTTP本身传递的是明文,不会加密这些信息,只要攻击者能够获取这些明文,用户的隐私就完全暴露了。HTTP是基于TCP/IP的,TCP/IP的特点也决定了HTTP数据很容易被截获,网络传输过程中,路由策略决定HTTP数据会通过很多节点设备,节点很轻松就能截获明文数据,由于数据没有加密,很容易理解其含义。

2.无法验证身份

在HTTP应用中,客户端和服务器并不能确认对方的身份,在HTTP标准中,没有校验对端身份的标准。对于服务器来说,它接收的HTTP请求格式只要正确,就发送响应信息。对于客户端来说同样如此,它连接的是www.example.com主机,但由于有中间节点的存在,最终连接的可能是www.example.cn主机,但对于客户端来说,它无法校验服务器的身份。

3.数据易篡改

HTTP数据在传输过程中,会经过很多节点,这些节点都可以修改原始数据,而对于客户端和服务器来说,没有任何技术来确保接收的数据就是发送者发送的原始数据。

由于没有机制确保数据的完整性,客户端和服务器只能无条件信任接收到的数据,这也产生了很多安全问题,篡改数据也叫作中间人攻击。比如ISP插入广告的例子,如果有一种机制能够让浏览器知晓数据已经被篡改,那么浏览器就可以告知用户危险,并中断本次请求。

对称加密的不安全性。

image-20230520142556871

使用对称加密算法:加密和解密使用的秘钥是相同的。

对称加密算法是内置在浏览器,服务器中的,是一种开源的算法。客户端和服务端在正式传递数据前需要协商秘钥,然后使用秘钥加密解密数据,进而传输数据。

一旦秘钥泄露,拦截者可以伪造请求,也可以伪造响应,是十分危险的。

为什么秘钥会泄露呢?就是在传递秘钥的时候,会被拦截。如上所示666没有被加密传输。

2. Https原理

非对称加密

image-20230520150236420

再传递数据前客户端会先去服务器下载公钥(去443端口下载)。客户端的所有数据都会用公钥先加密,然后服务端用私钥解密。再用私钥加密想要传递的数据。

公钥加密(公钥解不开),这是保证即使公钥被拦截也不会被导致数据安全问题的方式。

道高一尺,魔高一丈。即使用非对称加密,我们还是有办法搞你。

image-20230520151024464

在客户端请求公钥的时候:

  • 拦截者将请求拦截下来,然后伪造请求信息去服务器替你申请公钥。然后给客户端一个假公钥。
  • 客户端传递假公钥的数据拦截者可以用假私钥解密,然后用拦截的公钥加密去请求服务器数据,请求回来的数据拦截者也能解密。

3. CA机构,证书,客户端(浏览器),服务器端

image-20230520152707888

1.公司需要向第三方认证机构提供资料,域名等等。机构会给公司一个秘钥,放在指定的文件夹下。每当服务器请求ca机构加密他的公钥的时候,ca机构会来指定目录下查看是否有之前发的秘钥,有的话颁发证书。之后有客户端请求服务器公钥,下发的是公钥经过ca加密后的证书。

2.客户端请求公钥的时候,服务器其实是会下发ca机构认证的证书给客户端。而ca机构的公钥是内嵌在操作系统中的,不是传输而来的。即使拦截者拦截了请求得到了证书,但是拦截者用自己的私钥加密之后的数据操作系统的内置ca公钥解不开,就不会认这个证书。

3.所以使用正版操作系统,正版浏览器就很重要了。

4. 证书自签名

公司内部自己做认证用的,自己做的ca机构,一般很少用。

OpenSSL

系统内置

图形化工具 XCA

下载地址

https://www.hohnstaedt.de/xca/index.php/download

5.在线证书申请

参考这篇博客

https://hashnode.blog.csdn.net/article/details/124555405
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值