百万架构师第四十四课:Nginx:Nginx 的扩展-OpenRestry|JavaGuide

百万架构师系列文章阅读体验感更佳

原文链接:https://javaguide.net

公众号:不止极客

Nginx 的扩展-OpenRestry

课程目标

  1. Nginx 进程模型简介

  2. Nginx 的高可用方案

  3. OpenResty 安装及使用

  4. 什么是 API 网关?

  5. OpenResty 实现灰度发布功能

Nginx 进程模型简介

多进程

  • Tomcat

    • BIO
    • NIO
    • AIO
  • Nginx

    • 多进程+多路复用
    • master 进程 、 worker 进程

​ 当它启动以后,会产生一个主进程和多个工作进程。多个工作进程是通过 Master 进程去管理的。它是基于 Master 进程 Fork 出来的。当我们的 Nginx 收到一个请求的时候,它会向我们的 work 发送一个信号。然后通过 worker 进程去管理。类似于中心进程的意思。

​ 当有一个请求过来的时候,只会有一个 worker 进程去处理。

root /nginx 7473 1 0 20:09 ? 00:00:00 nginx: master process .
nobody 7474 7473 0 20:09 ? 00:00:00 nginx: worker process
c 复制代码
[root@Darian1 nginx]# ps -ef|grep nginx
root      47432      1  0 00:14 ?        00:00:00 nginx: master process ./sbin/nginx
nobody    47433  47432  0 00:14 ?        00:00:00 nginx: worker process
root      47438  47405  0 00:14 pts/1    00:00:00 grep --color=auto nginx
[root@Darian1 nginx]# 

​ Master 进程会去管理每一个 worker 进程。当客户端发起一个请求的时候,它会由 worker 进程去处理,而不是由 Master 进程去处理。如果有多个 worker 进程的时候,一个请求过来,多个 worker 会有竞争。每一个进程会去争抢获得这样的一个许可。

​ 相当于多个 worker 进程 组成一个集群去实现,每一个 worker 进程后边是多路复用。

Nginx 配置几个进程
  • 工作进程建议设置成我们的 CPU 总核心数。

  • IO模型

    • epoll . select ....
  • Linux 理论上最大的连接数是 6535 。

最后支持的就是 CPU 数 * 进程数

c 复制代码
# 用户组,这个用户组是当前 Linux 的用户组。和用户账号
#user  nobody;         
# Nginx 工作进程数, 建议设置成我们的 CPU 总核心数。 
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 {
    # io 模型
    use epoll ;  
    # 理论上  processes* connections
    worker_connections  1024;  
}
c 复制代码
#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;
    # 日志格式,命名,把日志放到某一个文件里边,然后用 Kafka 收集起来,最后做分析
    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;
    # 是否开启 0 拷贝模式,我们传输文件 0 拷贝效率高
    sendfile        on;
    # tcp 超时时间
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    # 扫描这个目录下的配置文件
    include    extra/*.conf;

    # 是否打开 gzip
    gzip on;
    # 超过多长长度再进行压缩
    gzip_min_length 5K;
    # 压缩的等级越高,压缩后的文件越小,占用的 CPU 越高
    gzip_comp_level 3;
    # 对哪些文件去做压缩
    gzip_types application/javascript image/jpeg;
    # 设置缓冲区,按照我们我们指定大小的倍数去申请内存,
    # 按照我们原始文件的大小,以 32K 为单位的四倍去申请内存。
    gzip_buffers 4 32k;
    # 是否传输 "vary: Accept-Encoding" 的文件头标志
    # 根据客户端的头去判断我们是不是要去做压缩。
    gzip_vary on;
}

Nginx 的高可用方案

​ 这模型的整个吞吐量是有限的。一旦大了以后,就会崩溃

​ Nginx 作为反向代理服务器,所有的流量都会经过 Nginx,所以 Nginx 本身的可靠性是我们首先要考虑的问题。

​ 问题: HTTP Server 之间的同步,出现问题的切换,DNS 是没有办法解决的。

​ 通过 Nginx 去代理后端应用的服务器。Nginx 对外是一个 IP 地址,七层负载,做一个转发,通过应用层的 URI 做一些转发。 我可以做 IP_Hash 、轮询、权重 ... ... 各种方式去做转发。

​ Nginx 性能比 Tomcat 高得多得多。高性能的反向代理服务器。(DNS 是对域名做解析的。)通过 Nginx 做一个集群。

​ 我们为了避免单点故障,就需要解决 Nginx 的单点问题!!!

​ 我一个请求过来,会根据它的一个 IP + 端口号 做一个转发,转发以后,后续请求,不是通过 F5 去做一个返回。而是直接返回,只是通过 F5 做一个解析而已。 F5 的性能 Nginx 更高。

流量问题,不能纯靠技术问题去解决。

www.baidu.com 它是可以分发到不同的站点的,叫做 地域服务器

在北京访问北京的机房。其他的地方,访问其他地方的机房。

流量是无限的。我们需要通过 多机房,地域来解析。

把你的请求转发到离你最近的服务器上。

keepalived

keeppalived 可以生成 虚拟IP ( vip )。

Keepalived 是 Linux 下一个轻量级别的高可用解决方案,Keepalived 软件起初是专为 LVS 负载均衡软件 设计的,用来管理并监控 LVS 集群系统中各个服务节点的状态,后来又加入了可以实现高可用的 VRRP 功能。因此,Keepalived 除了能够管理 LVS 软件外,还可以作为其他服务(例如:Nginx、Haproxy、MySQL 等)的高可用解决方案软件。

​ Keepalived 软件主要是通过 VRRP 协议实现高可用功能的。VRRP 是 Virtual Router RedundancyProtocol (虚拟路由器冗余协议)的缩写,VRRP 出现的目的就是为了解决静态路由单点故障问题的,它能够保证当个别节点宕机时,整个网络可以不间断地运行;(简单来说,vrrp 就是把两台或多台路由器设备虚拟成一个设备, 实现主备高可用)。(它是虚拟 IP )

​ 所以,Keepalived 一方面具有配置管理 LVS 的功能,同时还具有对 LVS 下面节点进行健康检查的功能,另一方面也可实现系统网络服务的高可用功能。

​ LVS 是 Linux Virtual Server 的缩写,也就是 Linux 虚拟服务器,在 linux2.4 内核以后,已经完全内置了 LVS 的各个功能模块。

​ 它是工作在四层的负载均衡,类似于 Haproxy, 主要用于实现对服务器集群的负载均衡。

​ 关于四层负载,我们知道 osi 网络层次模型的 7 层模型(应用层、表示层、会话层、传输层、网络层、数据链路层、物理层);四层负载就是基于传输层,也就是 ip+端口 的负载;而七层负载就是需要基于 URL 等应用层的信息来做负载,同时还有二层负载(基于 MAC)、三层负载(IP);

常见的四层负载有:LVS、F5 ; 七层负载有 : Nginx、HAproxy ; 在软件层面,

Nginx / LVS / HAProxy 是使用得比较广泛的三种负载均衡软件

对于中小型的 Web 应用,可以使用 Nginx、大型网站或者重要的服务并且服务比较多的时候,可以考虑使用 LVS

轻量级的高可用解决方案

​ LVS 四层负载均衡软件(Linux virtual server) 监控 lvs 集群系统中的各个服务节点的状态VRRP 协议(虚拟路由冗余协议) linux2.4 以后,是内置在 linux 内核中的

lvs(四层) -> HAproxy 七层

lvs(四层) -> Nginx(七层)

实践

  1. 下载 keepalived 的安装包

  2. tar -zxvf keepalived-2.0.7.tar.gz

  3. /data/program/ 目录下创建一个 keepalived 的文件

  4. cd 到 keepalived-2.0.7 目录下,执行 ./configure -- prefix=/data/program/keepalived --sysconf=/etc

  5. 如果缺少依赖库,则 yum install gcc; yum install openssl-devel ; yum install libnl libnl-devel

  6. 编译安装 make && make install

  7. 进入安装后的路径 cd /data/program/keepalived , 创建软连接: ln -s sbin/keepalived /sbin

  8. cp /data/program/keepalived-2.0.7/keepalived/etc/init.d/keepalived /etc/init.d

    把运行的的服务添加进去

  9. chkconfig --add keepalived

  10. chkconfig keepalived on

  11. service keepalived start

安装步骤

版本,2.0.7 其他版本会有问题。

c 复制代码
[root@Darian1 software]# tar -zxvf keepalived-2.0.11.tar.gz 
[root@Darian1 software]# mkdir keepalived
[root@Darian1 software]# cd keepalived-2.0.11/
[root@Darian1 keepalived-2.0.11]# ./configure --prefix=/software/keepalived --sysconf=/etc

configure: error: 
!!! OpenSSL is not properly installed on your system. !!!
!!! Can not include OpenSSL headers files.            !!!

[root@Darian1 keepalived-2.0.11]# yum install openssl-devel
Is this ok [y/d/N]: y

*** WARNING - this build will not support IPVS with IPv6. Please install libnl/libnl-3 dev libraries to support IPv6 with IPVS.

[root@Darian1 keepalived-2.0.11]# yum install libnl libnl-devel
[root@Darian1 keepalived-2.0.11]# yum install gcc

[root@Darian1 keepalived-2.0.11]# ./configure --prefix=/software/keepalived --sysconf=/etc

configure: error: libnfnetlink headers missing

[root@Darian3 keepalived-2.0.7]# yum install -y libnfnetlink-devel
[root@Darian1 keepalived-2.0.7]# make && make install
[root@Darian3 software]# mkdir keepalived
[root@Darian1 keepalived]# cd ../keepalived
[root@Darian1 keepalived]# ln -s sbin/keepalived /sbin
[root@Darian1 keepalived]# cp /software/keepalived-2.0.7/keepalived/etc/init.d/keepalived /etc/init.d

[root@Darian1 keepalived]# chkconfig --add keepalived
[root@Darian1 keepalived]# chkconfig keepalived on
注意:正在将请求转发到"systemctl enable keepalived.service"。
Created symlink from /etc/systemd/system/multi-user.target.wants/keepalived.service to /usr/lib/systemd/system/keepalived.service.
 
[root@Darian1 keepalived]# service keepalived start
[root@Darian1 keepalived]# service keepalived status

请立马配置 keepalived 的日志

keepalived 的配置

c 复制代码
[root@Darian1 keepalived]# vim /etc/keepalived/keepalived.conf 

! Configuration File for keepalived

# 配置一些全局的东西
global_defs {
   # 邮箱的配置, 当 keepalived 发生错误的时候,发送到你的邮箱里边
   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   router_id LVS_DEVEL
   vrrp_skip_check_adv_addr
   vrrp_strict
   vrrp_garp_interval 0
   vrrp_gna_interval 0
}

# 虚拟的路由的冗余协议
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    # 配置多个虚拟 IP 地址
    virtual_ipaddress {
        192.168.200.16
        192.168.200.17
        192.168.200.18
    }
}

# 虚拟的服务,就是我们的 LVS  了,就是我们对外的一个虚拟的 IP 地址的配置的映射,默认 443 是对 HTTPS 的一个映射,
virtual_server 192.168.200.100 443 {
    delay_loop 6
    lb_algo rr
    lb_kind NAT
    persistence_timeout 50
    protocol TCP

    # 这里是真实服务的一个配置
    real_server 192.168.201.100 443 {
        weight 1
        SSL_GET {
            url {
              path /
              digest ff20ad2481f97b1754ef3e12ecd3a9cc
            }
            url {
              path /mrtg/
              digest 9b3a0c85a887a256d6939da88aabd8cd
            }
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
}

virtual_server 10.10.10.2 1358 {
    delay_loop 6
    lb_algo rr
    lb_kind NAT
    persistence_timeout 50
    protocol TCP

    sorry_server 192.168.200.200 1358

    real_server 192.168.200.2 1358 {
        weight 1
        HTTP_GET {
            url {
              path /testurl/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            url {
              path /testurl2/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            url {
              path /testurl3/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }

    real_server 192.168.200.3 1358 {
        weight 1
        HTTP_GET {
            url {
              path /testurl/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334c
            }
            url {
              path /testurl2/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334c
            }
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
}

virtual_server 10.10.10.3 1358 {
    delay_loop 3
    lb_algo rr
    lb_kind NAT
    persistence_timeout 50
    protocol TCP

    real_server 192.168.200.4 1358 {
        weight 1
        HTTP_GET {
            url {
              path /testurl/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            url {
              path /testurl2/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            url {
              path /testurl3/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }

    real_server 192.168.200.5 1358 {
        weight 1
        HTTP_GET {
            url {
              path /testurl/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            url {
              path /testurl2/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            url {
              path /testurl3/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }

​ 我们需要在 Keepalived 里边配置 Nginx 的一些东西。通过 Keepalived 实现了对 Nginx 的高可用,keepalived 是对 LVS 的一个检测。这个 虚拟 IP 会根据你的配置去进行映射。

master

c 复制代码
[root@Darian1 keepalived]# vim /etc/keepalived/keepalived.conf 

! Configuration File for keepalived 

global_defs {
    # 运行 keepalived 服务器的标识,在一个网络内应该是唯一的
    router_id LVS_DEVEL   
}

# vrrp 实例定义部分
vrrp_instance VI_1 {
    # 设置 lvs 的状态,MASTER 和 BACKUP 两种,必须大写
    state MASTER  
    # 设置对外服务的接口,网卡的地址
    interface ens33   
    # 设置虚拟路由标示,这个标示是一个数字,同一个 vr rp 实例使用唯一标示
    virtual_router_id 51   
    # 定义优先级,数字越大优先级越高,在一个 vrrp------instance 下,master 的优先级必须大于 backup
    priority 100 
    # 设定 master 与 backup 负载均衡器之间同步检查的时间间隔,单位是秒
    advert_int 1 
 
    authentication {  
        # 设置验证类型和密码
        auth_type PASS
        # 验证密码,同一个 vrrp_instance 下 MASTER 和 BACKUP 密码必须相同
        auth_pass 1111   
    }

    virtual_ipaddress { 
        #设置虚拟 ip 地址,可以设置多个,每行一个
        192.168.40.100
    }
}

# 设置虚拟服务器,需要指定虚拟 ip 和服务端口
virtual_server 192.168.40.100 80 {
    # 健康检查时间间隔
    delay_loop 6
    # 负载均衡调度算法
    lb_algo rr
    # 负载均衡转发规则
    lb_kind NAT   
    # 设置会话保持时间protocol TCP #指定转发协议类型,有 TCP 和 UDP 两种
    persistence_timeout 50
    # 配置服务器节点 1,需要指定 real server 的真实 IP 地址和端口
    real_server 192.168.40.128 80 { 
        # 设置权重,数字越大权重越高
        weight 1  
        # realserver 的状态监测设置部分单位秒,用来去检测它的状态
        TCP_CHECK {  
            # 超时时间
            connect_timeout 3  
            # 重试间隔
            delay_before_retry 3
            # 监测端口
            connect_port 80  
        }
    }
}

backup

vim /etc/keepalived/keepalived.conf

c 复制代码
! Configuration File for keepalived

global_defs { 
   router_id LVS_DEVEL
}

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

virtual_server 192.168.40.100 80 {
   delay_loop 6 
   lb_algo rr
   lb_kind NAT
   persistence_timeout 50 
   protocol TCP

   real_server 192.168.40.129 80 {
       weight 1 
       TCP_CHECK {
           connect_timeout 3
           delay_before_retry 3
           connect_port 80
       }
   }
}
c 复制代码
[root@Darian1 software]# service keepalived restart
Restarting keepalived (via systemctl):                     [  确定  ]
[root@Darian1 software]# service keepalived status
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; enabled; vendor preset: disabled)
   Active: active (running) since 日 2019-01-20 20:10:22 CST; 20s ago
  Process: 83210 ExecStart=/software/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 83211 (keepalived)
   CGroup: /system.slice/keepalived.service
           ├─83211 /software/keepalived/sbin/keepalived -D
           ├─83212 /software/keepalived/sbin/keepalived -D
           └─83213 /software/keepalived/sbin/keepalived -D

1月 20 20:10:25 Darian1 Keepalived_vrrp[83213]: Sending gratuitous ARP on ens33 for 192.168.11.100
1月 20 20:10:25 Darian1 Keepalived_vrrp[83213]: Sending gratuitous ARP on ens33 for 192.168.11.100
1月 20 20:10:25 Darian1 Keepalived_vrrp[83213]: Sending gratuitous ARP on ens33 for 192.168.11.100
1月 20 20:10:28 Darian1 Keepalived_healthcheckers[83212]: TCP connection to [192.168.40.128]:udp:80 success.
1月 20 20:10:30 Darian1 Keepalived_vrrp[83213]: Sending gratuitous ARP on ens33 for 192.168.11.100
1月 20 20:10:30 Darian1 Keepalived_vrrp[83213]: (VI_1) Sending/queueing gratuitous ARPs on ens33 for 192.168.11.100
1月 20 20:10:30 Darian1 Keepalived_vrrp[83213]: Sending gratuitous ARP on ens33 for 192.168.11.100
1月 20 20:10:30 Darian1 Keepalived_vrrp[83213]: Sending gratuitous ARP on ens33 for 192.168.11.100
1月 20 20:10:30 Darian1 Keepalived_vrrp[83213]: Sending gratuitous ARP on ens33 for 192.168.11.100
1月 20 20:10:30 Darian1 Keepalived_vrrp[83213]: Sending gratuitous ARP on ens33 for 192.168.11.100
[root@Darian1 software]# 


[root@Darian3 software]# service keepalived restart
Restarting keepalived (via systemctl):                     [  确定  ]
[root@Darian3 software]#  service keepalived status
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; enabled; vendor preset: disabled)
   Active: active (running) since 日 2019-01-20 20:12:31 CST; 15s ago
  Process: 47254 ExecStart=/software/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 47255 (keepalived)
   CGroup: /system.slice/keepalived.service
           ├─47255 /software/keepalived/sbin/keepalived -D
           ├─47256 /software/keepalived/sbin/keepalived -D
           └─47257 /software/keepalived/sbin/keepalived -D

1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: (Line 3) Unexpected '{' - ignoring
1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: (Line 4) Unknown keyword 'router_id'
1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: (Line 5) Unknown keyword '}'
1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: Assigned address 192.168.40.129 for interface ens33
1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: Assigned address fe80::d3a1:60b3:dbb3:68c2 for interface ens33
1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: Registering gratuitous ARP shared channel
1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: (VI_1) removing VIPs.
1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: (VI_1) Entering BACKUP STATE (init)
1月 20 20:12:31 Darian3 Keepalived_vrrp[47257]: VRRP sockpool: [ifindex(2), proto(112), unicast(0), fd(8,9)]
1月 20 20:12:37 Darian3 Keepalived_healthcheckers[47256]: TCP connection to [192.168.40.129]:tcp:80 success.
[root@Darian3 software]# 

Nginx 是每秒都可以抗十万的。

踩坑:

配置同一个网段,不是同一个网段,访问会有问题!!!

线程在进程之内是共享的。共享资源相互影响。

线程产生的成本比进程低。

keepalived 日志文件配置

  1. 首先看一下/etc/sysconfig/keepalived 文件

    c 复制代码
    vim /etc/sysconfig/keepalived
    
    KEEPALIVED_OPTIONS="-D -d -S 0"

    -D 就是输出日志的选项

    这里的"-S 0"表示 local0.* 具体地还需要看一下/etc/syslog.conf 文件

  2. /etc/rsyslog.conf 里添加: local0.* /var/log/keepalived.log

    c 复制代码
    [root@Darian3 keepalived]# vim /etc/rsyslog.conf
  3. 重新启动 keepalived 和 rsyslog 服务:

    service rsyslog restart

    service keepalived restart

    c 复制代码
    [root@Darian1 keepalived]# vim /etc/sysconfig/keepalived 
    [root@Darian1 keepalived]# vim /etc/rsyslog.conf
    
    # keepalived 的log
    local0.* /var/log/keepalived.log
    
    [root@Darian1 keepalived]# service rsyslog restart 
    [root@Darian1 keepalived]# service keepalived restart
  4. 查看日志:

    c 复制代码
    tail -f /var/log/keepalived.log

通过脚本实现动态切换

Keepalived 需要配置主备来实现动态的切换。Nginx 挂掉的时候,keepalived 也要停止掉。keepalived 的存在的意义是用来监听 Nginx 的请求。这个监听的过程实际上是通过 LVS 来转发的。需要脚本来触发。

  1. 在 master 和 slave 节点的 /data/program/nginx/sbin/nginx-ha-check.sh 目录下增加一个脚本

    --no-headers 不打印头文件

    Wc --l

    统计行数

    c 复制代码
    [root@Darian1 keepalived]# cd ../nginx/sbin/
    [root@Darian1 sbin]# vim nginx_status_check.sh
    c 复制代码
    #!bin/sh   #! /bin/sh 是指此脚本使用/bin/sh 来执行
    
    A=`ps -C nginx --no-header |wc -l`
    
    if [ $A -eq 0 ]
        then
            echo 'nginx server is died'
            service keepalived stop
    fi
  2. 修改 keepalived.conf 文件,增加如下配置

    track_script: #执行监控的服务。chknginxservice #

    引用 VRRP 脚本,即在 vrrp_script 部分指定的名字。定期运行它们来改变优先级, 并最终引发主备切换。当我们 Nginx 挂掉以后,会触发这个脚本。

操作步骤:

  1. 编写 keepalived.conf 脚本
c 复制代码
[root@Darian1 sbin]# vim /etc/keepalived/keepalived.conf 

global_defs {
    # 运行 keepalived 服务器的标识,在一个网络内应该是唯一的
    router_id LVS_DEVEL
    enable_script_security
}

vrrp_script nginx_status_process {
    # 实现当 Nginx 挂掉以后,keepalived 也挂掉
    script "/software/nginx/sbin/nginx_status_check.sh"
    # 用户的隔离,用户的运行权限,防止其他用户对这个脚本的执行
    user root
    # 检查频次
    interval 3
}

# vrrp 实例定义部分
vrrp_instance VI_1 {
    # 设置 lvs 的状态,MASTER 和 BACKUP 两种,必须大写
    state MASTER
    # 设置对外服务的接口,网卡的地址
    interface ens33
    # 设置虚拟路由标示,这个标示是一个数字,同一个 vr rp 实例使用唯一标示
    virtual_router_id 51
    # 定义优先级,数字越大优先级越高,在一个 vrrp------instance 下,master 的优先级必须大于 backup
    priority 100
    # 设定 master 与 backup 负载均衡器之间同步检查的时间间隔,单位是秒
    advert_int 1

    authentication {
        # 设置验证类型和密码
        auth_type PASS
        # 验证密码,同一个 vrrp_instance 下 MASTER 和 BACKUP 密码必须相同
        auth_pass 1111
    }

    virtual_ipaddress {
        #设置虚拟 ip 地址,可以设置多个,每行一个
        192.168.40.100
    }

    track_script{
        # 对应的 上边的 nginx_status_process
        nginx_status_process
    }
}

# 设置虚拟服务器,需要指定虚拟 ip 和服务端口
virtual_server 192.168.40.100 80 {
    # 健康检查时间间隔
    delay_loop 6
    # 负载均衡调度算法
    lb_algo rr
    # 负载均衡转发规则
    lb_kind NAT
    # 设置会话保持时间protocol TCP #指定转发协议类型,有 TCP 和 UDP 两种
    persistence_timeout 50
    # 配置服务器节点 1,需要指定 real server 的真实 IP 地址和端口
    real_server 192.168.40.128 80 {
        # 设置权重,数字越大权重越高
        weight 1
        # realserver 的状态监测设置部分单位秒,用来检测它的状态
        TCP_CHECK {
            # 超时时间
            connect_timeout 3
            # 重试间隔
            delay_before_retry 3
            # 监测端口
            connect_port 80
        }
    }
}
  1. 编写 脚本。Nginx/sbin
c 复制代码
[root@Darian1 sbin]# vim nginx-ha-check.sh

#!bin/sh   #! /bin/sh 是指此脚本使用/bin/sh 来执行

A=`ps -C nginx --no-header |wc -l`

if [ $A -eq 0 ]
 then
     echo 'nginx server is died'
     service keepalived stop
fi
  1. 测试脚本是否可用
c 复制代码
[root@Darian1 sbin]# sh nginx_status_check.sh 
nginx server is died
Stopping keepalived (via systemctl):                       [  确定  ]
  1. 添加生效
c 复制代码
[root@Darian1 sbin]# chmod +x nginx_status_check.sh
[root@Darian1 sbin]# service keepalived restart

[root@Darian1 sbin]# ./nginx -s stop

美团的 Camel

OpenResty

Nginx + lua

OpenResty 是一个通过 Lua 扩展 Nginx 实现的可伸缩的 Web 平台,内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

安装

  1. 下载安装包

    https://openresty.org/cn/download.html
    *

    sh 复制代码
    weget https://openresty.org/download/openresty-1.13.6.2.tar.gz
  2. 安装软件包

    c 复制代码
    [root@Darian1 software]# tar -zxvf openresty-1.13.6.2.tar.gz cd openrestry-1.13.6.2
    [root@Darian1 software]# mkdir openresty
    [root@Darian1 software]# cd openresty-1.13.6.2/
    [root@Darian1 software]# mkdir openresty
    [root@Darian1 openresty-1.13.6.2]# ./configure  --prefix=/software/openresty
    [root@Darian1 openresty-1.13.6.2]# make && make install
    c 复制代码
    ./configure [默认会安装在/usr/local/openresty 目录] --prefix= 指定路径
    make && make install
  3. 可能存在的错误,第三方依赖库没有安装的情况下会报错

    c 复制代码
    yum install readline-devel / pcre-devel /openssl-devel

    安装过程和 Nginx 是一样的,因为他是基于 Nginx 做的扩展

HelloWorld

开始第一个程序,HelloWorld

openresty/nginx/conf/nginx.conf 就是 nginx 的配置的东西。

c 复制代码
[root@Darian3 software]# cd openresty/nginx/
[root@Darian3 nginx]# vim conf/nginx.conf


location / {
	default_type         text/html;
	content_by_lua_block {
	    ngx.say("helloworld");
    }
}

[root@Darian3 nginx]# ./sbin/nginx 

在 sbin 目录下执行.nginx 命令就可以运行,看到 helloworld

建立工作空间

创建目录

​ 或者为了不影响默认的安装目录,我们可以创建一个独立的空间来练习,先在安装目录下创建 demo 目录,安装目录为 /data/program/openresty/demo

mkdir demo

​ 然后在 demo 目录下创建两个子目录,一个是 logs 、一个是 conf

创建配置文件

执行:./nginx -p /data/program/openresty/demo 【-p 主要是指明 nginx 启动时的配置目录】

c 复制代码
[root@Darian1 openresty]# mkdir demo
[root@Darian1 openresty]# cd demo/
[root@Darian1 demo]# mkdir conf
[root@Darian1 demo]# mkdir logs
[root@Darian1 demo]# cd conf/
[root@Darian1 conf]# vim nginx.conf

worker_processes 1; 
error_log logs/error.log;
events {
   worker_connections 1024;
}
http {
   server {
       listen 8888;
       location / {
           default_type text/html;
           content_by_lua_block {
               ngx.say("Hello world")
           }
       }
   }
}

[root@Darian1 conf]# cd ../../nginx/sbin/
[root@Darian1 sbin]# ./nginx -p /software/openresty/demo/

总结

​ 我们刚刚通过一个 helloworld 的简单案例来演示了 nginx + lua 的功能,其中用到了 ngx.say 这个表达式,通过在 content_by_lua_block 这个片段中进行访问;这个表达式属于 ngx_lua 模块提供的 api, 用于向客户端输出一个内容。

​ 我们配置了多个 模块,就会有固定的顺序。

写一个完全做认证的模块

c 复制代码
[root@Darian1 openresty]# cd demo/conf/
[root@Darian1 conf]# mkdir lua
[root@Darian1 conf]# cd lua
[root@Darian1 lua]# vim add.lua

local args = ngx.req.get_uri_args();
ngx.say(args.a + args.b);
                     
[root@Darian1 lua]# vim params.lua

local _M = {}

function _M.is_number(...)
    local arg = {...}
    local num;
    for i,v in ipairs(arg) do
        num=tonumber(v);
        if nil == num then
            return false;
        end
    end
        return true;
end
    return _M;

[root@Darian1 lua]#  vim check.lua

local param=require("params");

local args=ngx.req.get_uri_args();

if not args.a or not args.b or not param.is_number(args.a, args.b) then
    ngx.exit(ngx.HTTP_BAD_REQUEST);
    return;
end

[root@Darian1 lua]# vim ../nginx.conf 

worker_processes 1;
error_log logs/error.log;
events {
    worker_connections 1024;
}
http {
    lua_package_path '$prefix/lua/?.lua';
    lua_code_cache off;
    server {
        listen 80;
        location ~ ^/api/([-_a-zA-Z0_9]+) {
            access_by_lua_file lua/check.lua;
            content_by_lua_file lua/$1.lua;
        }
    }
}

[root@Darian1 lua]# cd ../../../nginx/sbin/
[root@Darian1 sbin]# history   
[root@Darian1 sbin]# ./nginx -p /software/openresty/demo/
    
nginx: [alert] lua_code_cache is off; this will hurt performance in /software/openresty/demo/conf/nginx.conf:11
    

​ 在 lua 里边有模块的概念,有包的概念。我可以把公共的代码放到一个文件里边。以 API 的方式去提供。可以把他作为一个方法。

​ 正常地来说,我们会有一个 lua 文件夹,按照我们的需求去写。

​ 我可以利用 lua 写一些复杂的逻辑。可以写业务代码的。

注意:

lua 目录在 demo 目录下 openresty/demo/lua/sub.lua

  • JS 、 shell 都叫做脚本语言。
  • history 查看命令的历史。

库文件使用

​ 通过上面的案例,我们基本上对 OpenResty 有了一个更深的认识,其中我们用到了自定义的 lua 模块。实际上 openresty 提供了很丰富的模块。让我们在实现某些场景的时候更加方便。可以在 /openresty/lualib 目录下看到;比如在 resty 目录下可以看到 redis.luamysql.lua 这样的操作 redis 和操作数据库的模块。

使用 redis 模块连接 redis

c 复制代码
worker_processes 1;
error_log           
logs/error.log; events {
    worker_connections 1024;
}

http {
    lua_package_path '$prefix/lualib/?.lua;;'; # 添加";;"表示默认路径下的lualib
    lua_package_cpath '$prefix/lualib/?.so;;';

server {
    location /demo { 
        content_by_lua_block {
            local redisModule=require "resty.redis";
            local redis=redisModule:new();   # lua 的对象实例 
            redis:set_timeout(1000); 
            ngx.say("===begin connect redis server");
            local ok,err = redis:connect("127.0.0.1",6379);   #连接 redis
            if not ok then
                ngx.say("==connection redis failed,error message:",err);
            end
                ngx.say("==begin set key and value");
                ok,err=redis:set("hello","world");
            if not ok then
                ngx.say("set value failed");
                return;
            end
                ngx.say("===set value result:",ok);
                redis:close();
            }
        }
    }
}

演示效果

到 nginx 路径下执行 ./nginx -p /data/program/openresty/redisdemo 在浏览器中输入:http://192.168.11.160/demo即可看到输出内容并且连接到 redis 服务器上以后,可以看到 redis 上的结果

redis 的所有命令操作,在 lua 中都有提供相应的操作。比如 redis:get("key")、

redis:set()等

网关

​ 通过扩展以后,在实际过程中应该怎么去应用呢?一般的使用场景: 网关、​web 防火墙、缓存服务器(对响应内容进行缓存,减少到达后端的请求,来提升性能),接下来重点讲讲网关的概念以及如何通过 Openresty 实现网关开发。

网关的概念

​ 从一个房间到另一个房间,必须要经过一扇门,同样,从一个网络向另一个网络发送信息,必须经过一道"关口",这道关口就是网关。顾名思义,网关(Gateway) 就是一个网络连接到另一个网络的"关口"。

​ 那什么是 API 网关呢?

​ 在微服务流行起来之前,api 网关就一直存在,最主要的应用场景就是开放平台, 也就是 open api; 这种场景大家接触的一定比较多,比如阿里的开放平台;当微服务流行起来以后,api 网关就成了上层应用集成的标配组件。

​ 比如说,支付包地址会有一个网关,做一个统一的路由转发。

为什么需要网关?

  • Kong 、Orange 、

对微服务组件地址进行统一抽象

​ API 网关意味着你要把 API 网关放到你的微服务的最前端,并且要让 API 网关变成由应用所发起的每个请求的入口。这样就可以简化实现客户端和微服务应用程序之间的沟通方式。

Backends for frontends

​ 当服务越来越多以后,我们需要考虑一个问题,就是对某些服务进行安全校验以及用户身份校验。甚至包括对流量进行控制。 我们会对需要做流控、需要做身份认证的服务单独提供认证功能,但是服务越来越多以后,会发现很多组件的校验是重复的。这些东西很明显不是每个微服务组件需要去关心的事情。微服务组件只需要负责接收请求以及返回响应即可。可以把身份认证、流控都放在 API 网关层来进行控制。

​ 针对于 app 或者 web 等分别做不同的网关。不同的客户端是不同的验证、不同的方式。

  • 按照服务组件进行统一的抽象
  • 针对不同的客户端来实现不同的 API 网关

网关的作用

  • 鉴权
  • 限流
  • 灰度发布
  • 分流
  • 日志记录

OpenResty 实现 API 网关限流及登录授权

OpenResty 为什么能做网关?

​ 前面我们了解到了网关的作用,通过网关,可以对 api 访问的前置操作进行统一的管理,比如鉴权、限流、负载均衡、日志收集、请求分片等。所以 API 网关的核心是所有客户端对接后端服务之前,都需要统一接入网关,通过网关层将所有非业务功能进行处理。

​ OpenResty 为什么能实现网关呢? OpenResty 有一个非常重要的因素是,对于每一个请求,Openresty 会把请求分为不同阶段,从而可以让第三方模块通过挂载行为来实现不同阶段的自定义行为。而这样的机制能够让我们非常方便地设计 api 网关。

​ Nginx 本身在处理一个用户请求时,会按照不同的阶段进行处理,总共会分为 11 个阶段。而 openresty 的执行指令,就是在这 11 个步骤中挂载 lua 执行脚本实现扩展,我们分别看看每个指令的作用。

initbylua : 当 Nginx master 进程加载 nginx 配置文件时会运行这段 lua 脚本,一般用来注册全局变量或者预加载 lua 模块

initwokerby_lua: 每个 Nginx worker 进程启动时会执行的 lua 脚本,可以用来做健康检查

setbylua:设置一个变量

rewritebylua:在 rewrite 阶段执行,为每个请求执行指定的 lua 脚本

accessbylua:为每个请求在访问阶段调用 lua 脚本

contentbylua:前面演示过,通过 lua 脚本生成 content 输出给 http 响应

balancerbylua :实现动态负载均衡,如果不是走 contentbylua,则走 proxy_pass,再通过 upstream 进行转发

headerfilterby_lua: 通过 lua 来设置 headers 或者 cookie

bodyfilterby_lua:对响应数据进行过滤

logbylua : 在 log 阶段执行的脚本,一般用来做数据统计,将请求数据传输到后端进行分析

zuul 的 filter 也是类似的实现。

灰度发布

​ 在单一架构中,随着代码量和业务量不断扩大,版本迭代会逐步变成一个很困难的事情,哪怕是一点小的修改,都必须要对整个应用重新部署。 但是在微服务中, 各个模块是一个独立运行的组件,版本迭代会很方便,影响面很小。

​ 同时,微服务化的组件节点,对于我们去实现灰度发布(金丝雀发布:将一部分流量引导到新的版本)来说,也会变得很简单;

​ 还可以有白名单,比如说 QQ 的升级改造计划,针对你升级。

​ 所以通过 API 网关,可以对指定调用的微服务版本,通过版本来隔离。如下图所示。

灰度发布的实现

  1. 文件目录, /data/program/openresty/gray [conf、logs、lua]

  2. 编写 Nginx 的配置文件 nginx.conf

c 复制代码
   [root@Darian1 openresty]# mkdir gray
   [root@Darian1 openresty]# cd gray
   [root@Darian1 gray]# mkdir lua
   [root@Darian1 gray]# mkdir logs
   [root@Darian1 gray]# mkdir conf
   [root@Darian1 gray]# cd conf/
   
   worker_processes 1;
   error_log      logs/error.log;
   
   events{
       worker_connections 1024;
   }
   http{
       lua_package_path "$prefix/lualib/?.lua;;";
       lua_package_cpath "$prefix/lualib/?.so;;";
       upstream prod {
           server 192.168.40.128:8080;
       }
       upstream pre {
           server 192.168.40.129:8080;
       }
       server {
           listen  80;
           server_name localhost;
           location /api {
               content_by_lua_file lua/gray.lua;
           }
           location @prod {
               proxy_pass http://prod;
           }
           location @pre {
               proxy_pass http://pre;
           }
       }
   }
       
   
   [root@Darian1 conf]# cd ../lua/
   [root@Darian1 lua]# vim gray.lua
  1. 编写 gray.lua 文件
c 复制代码
   local redis=require "resty.redis";
   local red=redis:new();
   red:set_timeout(1000);
   
   local ok,err=red:connect("192.168.40.128",6379);
   
   if not ok then
       ngx.say("failed to connect redis",err);
       return;
   end
   
   local headers=ngx.req.get_headers()
   local local_ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr
   
   local ip_lists=red:get("gray");
   if ip_lists ~= nil and  string.find(ip_lists,local_ip) == nil then
       ngx.exec("@prod");
   else
       ngx.exec("@pre");
   end
       local ok,err=red:close();
  1. 执行命令启动 nginx: [./nginx -p /data/program/openresty/gray]

  2. 启动 redis,并设置 set gray 192.168.11.160

  3. 通过浏览器运行: http://192.168.11.160/api查看运行结果

    修改 redis gray 的值,将客户端的 ip 存储到 redis 中 。set gray 1 再次运行结果, 即可看到访问结果已经发生了变化。

c 复制代码
[root@Darian1 sbin]# ./nginx  -p ../../gray

[root@Darian1 bin]# ./redis-server ../../redis-3.2.8/redis.conf 
[root@Darian1 bin]# ./redis-cli 
127.0.0.1:6379> set gray 192.168.23.66
OK
127.0.0.1:6379> set gray 192.168.40.1
OK

高可用踩坑:

记得网卡配置正确。

OpenResty/Demo/conf/nginx.conf 的配置文件

c 复制代码
worker_processes 1;
error_log logs/error.log;
events {
    worker_connections 1024;
}
http {
    server {
        listen 8888;
        location / {
            default_type text/html;
            content_by_lua_block {
                local args = ngx.req.get_uri_args();
                ngx.say("Hello world");
            }
        }
        location /sub {
            content_by_lua_block{
                local args = ngx.req.get_uri_args();
                ngx.say(args.a-args.b);
            }
        }
    }
}
c 复制代码
worker_processes 1; 
error_log      logs/error.log;

events{  
    worker_connections 1024;
}
http{
    # 两个分号,表示默认路径下
    lua_package_path "$prefix/lualib/?.lua;;"; 
    lua_package_cpath "$prefix/lualib/?.so;;"; 
    upstream prod {
        server 192.168.11.156:8080;
    }
    upstream pre {
        server 192.168.11.156:8081;
    }
    server {
        listen  80;
        server_name localhost; 
        location /api {
            content_by_lua_file lua/gray.lua;
        }
        location @prod { 
            proxy_pass http://prod;
        }
        location @pre { 
            proxy_pass http://pre;
        }
    }
    server {
        listen 8080;
        location / {
            content_by_lua_block { 
                ngx.say("I'm prod env");
            }
        }
    }
    server {
        listen 8081; 
        location / {
            content_by_lua_block { 
                ngx.say("I'm pre env");
            }
        }
    }
}

百万架构师系列文章阅读体验感更佳

原文链接:https://javaguide.net

公众号:不止极客

来源于: https://javaguide.net

微信公众号:不止极客