一、Nginx实现服务器端集群搭建
1.1Nginx与Tomcat部署
我们都知道了
Nginx
在 高并发场景和处理静态资源是非常高性能的,但是在实际项目中除了静
态资源还有就是后台业务代码模块,一般后台业务都会被部署在
Tomcat
,
weblogic
或者是
websphere
等
web
服务器上。那么如何使用
Nginx
接收用户的请求并把请求转发到后台
web
服务器?
![](https://img-blog.csdnimg.cn/d544500381124233ab029b9ebc578277.png)
步骤分析
:
1.
准备
Tomcat
环境,并在
Tomcat
上部署一个
web
项目
2.
准备
Nginx
环境,使用
Nginx
接收请求,并把请求分发到
Tomat
上
环境准备
(Tomcat)
浏览器访问
:
http://192.168.200.146:8080/demo/index.html
![](https://img-blog.csdnimg.cn/5a01caa37f7b4172b4ad7a7ac0332858.png)
获取动态资源的链接地址
:
http://192.168.200.146:8080/demo/getAddress
本次将采用
Tomcat
作为后台
web
服务器
(1)在
Centos
上准备一个
Tomcat
1.Tomcat
官网地址
:https://tomcat.apache.org/
2.
下载
tomcat,
本次课程使用的是
apache-tomcat-8.5.59.tar.gz
3.
将
tomcat
进行解压缩
mkdir web_tomcat
tar -zxf apache-tomcat-8.5.59.tar.gz -C /web_tomcat
(2)准备一个
web
项目,将其打包为
war
1.
将资料中的
demo.war
上传到
tomcat8
目录下的
webapps
包下
2.
将
tomcat
进行启动,进入
tomcat8
的
bin
目录下
./startup.sh
(3)启动
tomcat
进行访问测试。
静态资源
: http://192.168.200.146:8080/demo/index.html
动态资源
: http://192.168.200.146:8080/demo/getAddress
环境准备
(Nginx)
(1)使用
Nginx
的反向代理,将请求转给
Tomcat
进行处理。
upstream webservice {
server 192.168.200.146:8080;
}
server{
listen 80;
server_name localhost;
location /demo {
proxy_pass http://webservice;
}
}
(2)启动访问测试
![](https://img-blog.csdnimg.cn/90ccbb25305843aa8a4434a43716db41.png)
学习到这,可能大家会有一个困惑,明明直接通过
tomcat
就能访问,为
什么还需要多加一个
nginx
,这样不是反而是系统的复杂度变高了么
?
那
接下来我们从两个方便给大家分析下这个问题,
第一个使用
Nginx
实现动静分离
第二个使用
Nginx
搭建
Tomcat
的集群
1.2Nginx实现动静分离
什么是动静分离
?
动
:
后台应用程序的业务处理
静
:
网站的静态资源
(html,javaScript,css,images
等文件
)
分离
:
将两者进行分开部署访问,提供用户进行访问。举例说明就是以后
所有和静态资源相关的内容都交给
Nginx
来部署访问,非静态内容则交
个类似于
Tomcat
的服务器来部署访问。
为什么要动静分离
?
前面我们介绍过
Nginx
在处理静态资源的时候,效率是非常高的,而且
Nginx
的并发访问量也是名列前茅,而
Tomcat
则相对比较弱一些,所以
把静态资源交个
Nginx
后,可以减轻
Tomcat
服务器的访问压力并提高静
态资源的访问速度。
动静分离以后,降低了动态资源和静态资源的耦合度。如动态资源宕机
了也不影响静态资源的展示。
如何实现动静分离
?
实现动静分离的方式很多,比如静态资源可以部署到
CDN
、
Nginx
等服
务器上,动态资源可以部署到
Tomcat,weblogic
或者
websphere
上。本
次课程只要使用
Nginx+Tomcat
来实现动静分离。
需求分析
![](https://img-blog.csdnimg.cn/227aeadae43c4877983191337dbbd5e9.png)
动静分离实现步骤
1.
将
demo.war
项目中的静态资源都删除掉,重新打包生成一个
war
包,
在资料中有提供。
2.
将
war
包部署到
tomcat
中,把之前部署的内容删除掉
进入到
tomcat
的
webapps
目录下,将之前的内容删除掉
将新的
war
包复制到
webapps
下
将
tomcat
启动
3.
在
Nginx
所在服务器创建如下目录,并将对应的静态资源放入指定的位
置
![](https://img-blog.csdnimg.cn/d6eb1e4129d64e898e20c8802f62f921.png)
其中
index.html
页面的内容如下
:
4.
配置
Nginx
的静态资源与动态资源的访问
<!DOCTYPE html>
<html
lang
=
"en"
>
<head>
<meta
charset
=
"UTF-8"
>
<title>
Title
</title>
<script
src
=
"js/jquery.min.js"
></script>
<script>
$
(
function
(){
$
.
get
(
'http://192.168.200.133/demo/getAddress'
,
functi
on
(
data
){
$
(
"#msg"
).
html
(
data
);
});
});
</script>
</head>
<body>
<img
src
=
"images/logo.png"
/>
<h1>
Nginx
如何将请求转发到后端服务器
</h1>
<h3
id
=
"msg"
></h3>
<img
src
=
"images/mv.png"
/>
</body>
</html>
4.
配置
Nginx
的静态资源与动态资源的访问
upstream webservice{
server 192.168.200.146:8080;
}
server {
listen 80;
server_name localhost;
#
动态资源
location /demo {
proxy_pass http://webservice;
}
#
静态资源
location ~/.*\.(png|jpg|gif|js){
root html/web;
gzip on;
}
location / {
root html/web;
index index.html index.htm;
}
}
5.
启动测试,访问
http://192.168.200.133/index.html
![](https://img-blog.csdnimg.cn/83d007e8a1ec49a99b13806884afb91b.png)
假如某个时间点,由于某个原因导致
Tomcat
后的服务器宕机了,我们再
次访问
Nginx,
会得到如下效果,用户还是能看到页面,只是缺失了访问
次数的统计,这就是前后端耦合度降低的效果,并且整个请求只和后的
服务器交互了一次,
js
和
images
都直接从
Nginx
返回,提供了效率,降
低了后的服务器的压力。
![](https://img-blog.csdnimg.cn/96b3b5601d63420aa3d1731d88e672c5.png)
Nginx
实现
Tomcat
集群搭建
在使用
Nginx
和
Tomcat
部署项目的时候,我们使用的是一台
Nginx
服务
器和一台
Tomcat
服务器,效果图如下
:
![](https://img-blog.csdnimg.cn/c3e227ede2944d80be42ec0ed86650de.png)
那么问题来了,如果
Tomcat
的真的宕机了,整个系统就会不完整,所以
如何解决上述问题,一台服务器容易宕机,那就多搭建几台
Tomcat
服务
器,这样的话就提升了后的服务器的可用性。这也就是我们常说的集
群,搭建
Tomcat
的集群需要用到了
Nginx
的反向代理和赋值均衡的知
识,具体如何来实现
?
我们先来分析下原理
![](https://img-blog.csdnimg.cn/b3c5f3a9220846da8bdf1e42eb4f2979.png)
环境准备:
(1)
准备
3
台
tomcat,
使用端口进行区分
[
实际环境应该是三台服务器
]
,修
改
server.ml
,将端口修改分别修改为
8080,8180,8280
(2)
启动
tomcat
并访问测试,
![](https://img-blog.csdnimg.cn/b560b0a896ee41949bdc909c0a18c3da.png)
(3)
在
Nginx
对应的配置文件中添加如下内容
:
upstream webservice{
server 192.168.200.146:8080;
server 192.168.200.146:8180;
server 192.168.200.146:8280;
}
好了,完成了上述环境的部署,我们已经解决了
Tomcat
的高可用性,一
台服务器宕机,还有其他两条对外提供服务,同时也可以实现后台服务
器的不间断更新。但是新问题出现了,上述环境中,如果是
Nginx
宕机
了呢,那么整套系统都将服务对外提供服务了,这个如何解决?
1.3Nginx高可用解决方案
针对于上面提到的问题,我们来分析下要想解决上述问题,需要面临哪
些问题
?
![](https://img-blog.csdnimg.cn/b45da5daf3894f9b956d0f47160403e1.png)
需要两台以上的
Nginx
服务器对外提供服务,这样的话就可以解决其中一
台宕机了,另外一台还能对外提供服务,但是如果是两台
Nginx
服务器的
话,会有两个
IP
地址,用户该访问哪台服务器,用户怎么知道哪台是好
的,哪台是宕机了的
?
Keepalived
使用
Keepalived
来解决,
Keepalived
软件由
C
编写的,最初是专为
LVS
负载均衡软件设计的,
Keepalived
软件主要是通过
VRRP
协议实现高可
用功能。
VRRP
介绍
![](https://img-blog.csdnimg.cn/d67a677d37f24a6fb1c53856ebbb79c6.png)
VRRP
(
Virtual Route Redundancy Protocol
)协议,翻译过来为虚拟路
由冗余协议。
VRRP
协议将两台或多台路由器设备虚拟成一个设备,对外
提供虚拟路由器
IP,
而在路由器组内部,如果实际拥有这个对外
IP
的路由
器如果工作正常的话就是
MASTER,MASTER
实现针对虚拟路由器
IP
的各
种网络功能。其他设备不拥有该虚拟
IP
,状态为
BACKUP,
处了接收
MASTER
的
VRRP
状态通告信息以外,不执行对外的网络功能。当主机失
效时,
BACKUP
将接管原先
MASTER
的网络功能。
从上面的介绍信息获取到的内容就是
VRRP
是一种协议,那这个协议是用
来干什么的?
1.
选择协议
VRRP
可以把一个虚拟路由器的责任动态分配到局域网上的
VRRP
路由器
中的一台。其中的虚拟路由即
Virtual
路由是由
VRRP
路由群组创建的一个
不真实存在的路由,这个虚拟路由也是有对应的
IP
地址。而且
VRRP
路由
1
和
VRRP
路由
2
之间会有竞争选择,通过选择会产生一个
Master
路由和一个
Backup
路由。
2.
路由容错协议
Master
路由和
Backup
路由之间会有一个心跳检测,
Master
会定时告知
Backup
自己的状态,如果在指定的时间内,
Backup
没有接收到这个通知
内容,
Backup
就会替代
Master
成为新的
Master
。
Master
路由有一个特
权就是虚拟路由和后端服务器都是通过
Master
进行数据传递交互的,而备
份节点则会直接丢弃这些请求和数据,不做处理,只是去监听
Master
的状
态
用了
Keepalived
后,解决方案如下
:
![](https://img-blog.csdnimg.cn/aa94a2a3ac094cbf886bcdc2d7318505.png)
环境搭建
环境准备
![](https://img-blog.csdnimg.cn/f66e1ba9a1e340fb84b03817ab11b58a.png)
keepalived
的安装
步骤
1:
从官方网站下载
keepalived,
官网地址
https://keepalived.org/
步骤
2:
将下载的资源上传到服务器
keepalived-2.0.20.tar.gz
步骤
3:
创建
keepalived
目录,方便管理资源
mkdir keepalived
步骤
4:
将压缩文件进行解压缩,解压缩到指定的目录
tar -zxf keepalived-2.0.20.tar.gz -C keepalived/
步骤
5:
对
keepalived
进行配置,编译和安装
cd keepalived/keepalived-2.0.20
./configure --sysconf=/etc --prefix=/usr/local
make && make install
安装完成后,有两个文件需要我们认识下,一个是
/etc/keepalived/keepalived.conf
(keepalived
的系统配置文件,我
们主要操作的就是该文件
)
,一个是
/usr/local/sbin
目录下的
keepalived
,
是系统配置脚本,用来启动和关闭
keepalived
Keepalived
配置文件介绍
打开
keepalived.conf
配置文件
这里面会分三部,第一部分是
global
全局配置、第二部分是
vrrp
相关配
置、第三部分是
LVS
相关配置。 本次课程主要是使用
keepalived
实现高
可用部署,没有用到
LVS
,所以我们重点关注的是前两部分
global
全局部分:
global_defs {
#
通知邮件,当
keepalived
发送切换时需要发
email
给具体的邮箱
地址
notification_email {
tom@itcast.cn
jerry@itcast.cn
}
#
设置发件人的邮箱信息
notification_email_from zhaomin@itcast.cn
#
指定
smpt
服务地址
smtp_server 192.168.200.1
#
指定
smpt
服务连接超时时间
smtp_connect_timeout 30
#
运行
keepalived
服务器的一个标识,可以用作发送邮件的主题信
息
router_id LVS_DEVEL
#
默认是不跳过检查。检查收到的
VRRP
通告中的所有地址可能会比较
耗时,设置此命令的意思是,如果通告与接收的上一个通告来自相同的
master
路由器,则不执行检查
(
跳过检查
)
vrrp_skip_check_adv_addr
#
严格遵守
VRRP
协议。
vrrp_strict
#
在一个接口发送的两个免费
ARP
之间的延迟。可以精确到毫秒级。
默认是
0
vrrp_garp_interval 0
#
在一个网卡上每组
na
消息之间的延迟时间,默认为
0
vrrp_gna_interval 0
}
VRRP
部分,该部分可以包含以下四个子模块
1. vrrp_script
2. vrrp_sync_group
3. garp_group
4. vrrp_instance
我们会用到第一个和第四个,
#
设置
keepalived
实例的相关信息,
VI_1
为
VRRP
实例名称
vrrp_instance VI_1 {
state MASTER #
有两个值可选
MASTER
主
BACKUP
备
interface ens33 #vrrp
实例绑定的接口,用于发送
VRRP
包
[
当前服务器使用的网卡名称
]
virtual_router_id 51#
指定
VRRP
实例
ID
,范围是
0-255
priority 100 #
指定优先级,优先级高的将成为
MASTER
advert_int 1 #
指定发送
VRRP
通告的间隔,单位是秒
authentication { #vrrp
之间通信的认证信息
auth_type PASS #
指定认证方式。
PASS
简单密码认证
(
推
荐
)
auth_pass 1111 #
指定认证使用的密码,最多
8
位
}
virtual_ipaddress { #
虚拟
IP
地址设置虚拟
IP
地址,供用户
访问使用,可设置多个,一行一个
192.168.200.222
}
}
配置内容如下
:
服务器
1
global_defs {
notification_email {
tom@itcast.cn
jerry@itcast.cn
}
notification_email_from zhaomin@itcast.cn
smtp_server 192.168.200.1
smtp_connect_timeout 30
router_id keepalived1
vrrp_skip_check_adv_addr
vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
}
vrrp_instance VI_1 {
state MASTER
interface ens33
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.200.222
}
}
服务器
2
! Configuration File for keepalived
global_defs {
notification_email {
tom@itcast.cn
jerry@itcast.cn
}
notification_email_from zhaomin@itcast.cn
smtp_server 192.168.200.1
smtp_connect_timeout 30
router_id keepalived2
vrrp_skip_check_adv_addr
vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
}
vrrp_instance VI_1 {
state BACKUP
interface ens33
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.200.222
}
}
访问测试
1.
启动
keepalived
之前,咱们先使用命令
ip a
,
查看
192.168.200.133
和
192.168.200.122
这两台服务器的
IP
情况。
![](https://img-blog.csdnimg.cn/23888c39337743729271e79ecbc959a3.png)
2.
分别启动两台服务器的
keepalived
cd /usr/local/sbin
./keepalived
再次通过
ip a
查看
ip
![](https://img-blog.csdnimg.cn/c98f98e6d85f4677983f46b694a99953.png)
3.
当把
192.168.200.133
服务器上的
keepalived
关闭后,再次查看
ip
![](https://img-blog.csdnimg.cn/ad8e5a87555f4ad9aa15c94a2a600703.png)
通过上述的测试,我们会发现,虚拟
IP(VIP)
会在
MASTER
节点上,当
MASTER
节点上的
keepalived
出问题以后,因为
BACKUP
无法收到
MASTER
发出的
VRRP
状态通过信息,就会直接升为
MASTER
。
VIP
也
会
"
漂移
"
到新的
MASTER
。
上面测试和
Nginx
有什么关系
?
我们把
192.168.200.133
服务器的
keepalived
再次启动下,由于它的优先
级高于服务器
192.168.200.122
的,所有它会再次成为
MASTER
,
VIP
也
会
"
漂移
"
过去,然后我们再次通过浏览器访问
:
http://192.168.200.222/
![](https://img-blog.csdnimg.cn/490e75d87f454ff080ecda60e2669938.png)
如果把
192.168.200.133
服务器的
keepalived
关闭掉,再次访问相同的地
址
![](https://img-blog.csdnimg.cn/e75d9cce2b784e6e9c538ba797cc3633.png)
效果实现了以后, 我们会发现要想让
vip
进行切换,就必须要把服务器上
的
keepalived
进行关闭,而什么时候关闭
keepalived
呢
?
应该是在
keepalived
所在服务器的
nginx
出现问题后,把
keepalived
关闭掉,就可
以让
VIP
执行另外一台服务器,但是现在这所有的操作都是通过手动来完
成的,我们如何能让系统自动判断当前服务器的
nginx
是否正确启动,如
果没有,要能让
VIP
自动进行
"
漂移
"
,这个问题该如何解决
?
keepalived
之
vrrp_script
keepalived
只能做到对网络故障和
keepalived
本身的监控,即当出现网
络故障或者
keepalived
本身出现问题时,进行切换。但是这些还不够,
我们还需要监控
keepalived
所在服务器上的其他业务,比如
Nginx,
如果
Nginx
出现异常了,仅仅
keepalived
保持正常,是无法完成系统的正常
工作的,因此需要根据业务进程的运行状态决定是否需要进行主备切
换,这个时候,我们可以通过编写脚本对业务进程进行检测监控。
实现步骤
:
1.
在
keepalived
配置文件中添加对应的配置像
vrrp_script
脚本名称
{
script "
脚本位置
"
interval 3 #
执行时间间隔
weight -20 #
动态调整
vrrp_instance
的优先级
}
2.
编写脚本
ck_nginx.sh
#!/bin/bash
num=`ps -C nginx --no-header | wc -l`
if [ $num -eq 0 ];then
/usr/local/nginx/sbin/nginx
sleep 2
if [ `ps -C nginx --no-header | wc -l` -eq 0 ]; then
killall keepalived
fi
fi
Linux ps
命令用于显示当前进程
(process)
的状态。
-C(command) :
指定命令的所有进程
--no-header
排除标题
3.
为脚本文件设置权限
chmod 755 ck_nginx.sh
4.
将脚本添加到
vrrp_script ck_nginx {
script "/etc/keepalived/ck_nginx.sh" #
执行脚本的位置
interval 2 #
执行脚本的周期,秒为单位
weight -20 #
权重的计算方式
}
vrrp_instance VI_1 {
state MASTER
interface ens33
virtual_router_id 10
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.200.111
}
track_script {
ck_nginx
}
}
5.
如果效果没有出来,可以使用
tail
-
f /var/log/messages
查看日
志信息,找对应的错误信息。
6.
测试
问题思考
:
通常如果
master
服务死掉后
backup
会变成
master
,但是当
master
服务
又好了的时候
master
此时会抢占
VIP
,这样就会发生两次切换对业务繁
忙的网站来说是不好的。所以我们要在配置文件加入
nopreempt
非抢
占,但是这个参数只能用于
state
为
backup
,故我们在用
HA
的时候最好
master
和
backup
的
state
都设置成
backup
让其通过
priority
来竞争。
二、Nginx制作下载站点
首先我们先要清楚什么是下载站点
?
我们先来看一个网站
http://nginx.org/download/
这个我们刚开始学
习
Nginx
的时候给大家看过这样的网站,该网站主要就是用来提供用户
来下载相关资源的网站,就叫做下载网站。
1583825943945
如何制作一个下载站点
:
nginx
使用的是模块
ngx_http_autoindex_module
来实现的,该模块处
理以斜杠
("/")
结尾的请求,并生成目录列表。
nginx
编译的时候会自动加载该模块,但是该模块默认是关闭的,我们需
要使用下来指令来完成对应的配置
(1)
autoindex:
启用或禁用目录列表输出
![](https://img-blog.csdnimg.cn/b4541c8804e84e1fb8b4f3acd200157c.png)
(2)
autoindex_exact_size:
对应
HTLM
格式,指定是否在目录列表展示
文件的详细大小
默认为
on
,显示出文件的确切大小,单位是
bytes
。 改为
offff
后,显示出
文件的大概大小,单位是
kB
或者
MB
或者
GB
![](https://img-blog.csdnimg.cn/bca43347d333480eb15f4b8e51b9c687.png)
(3)
autoindex_format
:设置目录列表的格式
![](https://img-blog.csdnimg.cn/7ffc2ac769624c86a1e9c5464a89b5cd.png)
注意
:
该指令在
1.7.9
及以后版本中出现
(4)
autoindex_localtime:
对应
HTML
格式,是否在目录列表上显示时
间。
默认为
off
,显示的文件时间为
GMT
时间。 改为
on
后,显示的文件时间
为文件的服务器时间
![](https://img-blog.csdnimg.cn/f15f133e90d248e0ad95e2e9f29a5650.png)
配置方式如下
:
location /download{
root /usr/local;
autoindex on;
autoindex_exact_size on;
autoindex_format html;
autoindex_localtime on;
}
XML/JSON
格式
[
一般不用这两种方式
]
三、Nginx的用户认证模块
对应系统资源的访问,我们往往需要限制谁能访问,谁不能访问。这块
就是我们通常所说的认证部分,认证需要做的就是根据用户输入的用户
名和密码来判定用户是否为合法用户,如果是则放行访问,如果不是则
拒绝访问。
Nginx
对应用户认证这块是通过
ngx_http_auth_basic_module
模块来实
现的,它允许通过使用
"HTTP
基本身份验证
"
协议验证用户名和密码来限
制对资源的访问。默认情况下
nginx
是已经安装了该模块,如果不需要则
使用
--without-http_auth_basic_module
。
该模块的指令比较简单,
(1)
auth_basic:
使用
“ HTTP
基本认证
”
协议启用用户名和密码的验证
![](https://img-blog.csdnimg.cn/62b4bfd736414521bb4d06e76b365700.png)
开启后,服务端会返回
401
,指定的字符串会返回到客户端,给用户以
提示信息,但是不同的浏览器对内容的展示不一致。
(2)
auth_basic_user_fifile:
指定用户名和密码所在文件
![](https://img-blog.csdnimg.cn/8d6a39aa892e4de4ad963425e6db6e54.png)
指定文件路径,该文件中的用户名和密码的设置,密码需要进行加密。
可以采用工具自动生成
实现步骤
:
1.nginx.conf
添加如下内容
location /download{
root /usr/local;
autoindex on;
autoindex_exact_size on;
autoindex_format html;
autoindex_localtime on;
auth_basic 'please input your auth';
auth_basic_user_file htpasswd;
}
2.
我们需要使用
htpasswd
工具生成
yum install -y httpd-tools
htpasswd -c /usr/local/nginx/conf/htpasswd username //
创建一个新文件记录用户名和密码
htpasswd -b /usr/local/nginx/conf/htpasswd username
password //
在指定文件新增一个用户名和密码
htpasswd -D /usr/local/nginx/conf/htpasswd username //
从指定文件删除一个用户信息
htpasswd -v /usr/local/nginx/conf/htpasswd username //
验证用户名和密码是否正确
上述方式虽然能实现用户名和密码的验证,但是大家也看到了,所有的
用户名和密码信息都记录在文件里面,如果用户量过大的话,这种方式
就显得有点麻烦了,这时候我们就得通过后台业务代码来进行用户权限
的校验了。
四、Nginx的扩展模块
Nginx
是可扩展的,可用于处理各种使用场景。本节中,我们将探讨使
用
Lua
扩展
Nginx
的功能。
4.1Lua
概念
Lua
是一种轻量、小巧的脚本语言,用标准
C
语言编写并以源代码形式开
发。设计的目的是为了嵌入到其他应用程序中,从而为应用程序提供灵
活的扩展和定制功能。
特性
跟其他语言进行比较,
Lua
有其自身的特点:
(1)轻量级
Lua
用标准
C
语言编写并以源代码形式开发,编译后仅仅一百余千字节,可
以很方便的嵌入到其他程序中。
(2)可扩展
Lua
提供非常丰富易于使用的扩展接口和机制,由宿主语言
(
通常是
C
或
C++)
提供功能,
Lua
可以使用它们,就像内置的功能一样。
(3)支持面向过程编程和函数式编程
应用场景
Lua
在不同的系统中得到大量应用,场景的应用场景如下
:
游戏开发、独立应用脚本、
web
应用脚本、扩展和数据库插件、系统安
全上。
Lua
的安装
在
linux
上安装
Lua
非常简单,只需要下载源码包并在终端解压、编译即
可使用。
Lua
的官网地址为
:
https://www.lua.org
![](https://img-blog.csdnimg.cn/069a74cbe60c498da01e6e0496755247.png)
1.
点击
download
可以找到对应版本的下载地址,我们本次课程采用的
是
lua-5.3.5,
其对应的资源链接地址为
https://www.lua.org/ftp/lua-5.
4.1.tar.gz,
也可以使用
wget
命令直接下载
:
wget https://www.lua.org/ftp/lua-5.4.1.tar.gz
2.
编译安装
cd lua-5.4.1
make linux test
make install
如果在执行
make linux test
失败,报如下错误
:
![](https://img-blog.csdnimg.cn/6c1e0e9c81944c8cabeea4fa7604ea12.png)
说明当前系统缺少
libreadline-dev
依赖包,需要通过命令来进行安装
yum install -y readline-devel
验证是否安装成功
lua -v
Lua
的语法
Lua
和
C/C++
语法非常相似,整体上比较清晰,简洁。条件语句、循环语
句、函数调用都与
C/C++
基本一致。如果对
C/C++
不太熟悉的同学来说,
也没关系,因为天下语言是一家,基本上理解起来都不会太困难。我们
一点点来讲。
第一个
Lua
程序
大家需要知道的是,
Lua
有两种交互方式,分别是
:
交互式和脚本式,这
两者的区别,下面我们分别来讲解下:
交互式之
HELLOWORLD
交互式是指可以在命令行输入程序,然后回车就可以看到运行的效果。
Lua
交互式编程模式可以通过命令
lua -i
或
lua
来启用
:
![](https://img-blog.csdnimg.cn/7398860d1f3348c392af0745fdf7e8a2.png)
在命令行中
key
输入如下命令,并按回车
,
会有输出在控制台:
![](https://img-blog.csdnimg.cn/6b2bf47a861b4da5abe36ae6788f6c41.png)
脚本式之
HELLOWORLD
脚本式是将代码保存到一个以
lua
为扩展名的文件中并执行的方式。
方式一
:
我们需要一个文件名为
hello.lua,
在文件中添加要执行的代码,然后通过
命令
lua hello.lua
来执行,会在控制台输出对应的结果。
hello.lua
print("Hello World!!")
![](https://img-blog.csdnimg.cn/ac28386e2a254015a1900a4a34ecbe2a.png)
方式二
:
将
hello.lua
做如下修改
#!/usr/local/bin/lua
print("Hello World!!!")
第一行用来指定
Lua
解释器所在位置为
/usr/local/bin/lua
,加上
#
号标记
解释器会忽略它。一般情况下
#!
就是用来指定用哪个程序来运行本文
件。但是
hello.lua
并不是一个可执行文件,需要通过
chmod
来设置可执
行权限,最简单的方式为
:
chmod 755 hello.lua
然后执行该文件
./hello.lua
![](https://img-blog.csdnimg.cn/f87ae2c17a4048a7a98b363481f87cb7.png)
补充一点,如果想在交互式中运行脚本式的
hello.lua
中的内容,我们可
以使用一个
dofile
函数,如:
dofile("lua_demo/hello.lua")
注意
:
在
Lua
语言中,连续语句之间的分隔符并不是必须的,也就是说后
面不需要加分号,当然加上也不会报错,
在
Lua
语言中,表达式之间的换行也起不到任何作用。如以下四个写
法,其实都是等效的
写法一
a=1
b=a+2
写法二
a=1;
b=a+2;
写法三
a=1; b=a+2;
写法四
a=1 b=a+2
不建议使用第四种方式,可读性太差。
Lua
的注释
关于
Lua
的注释要分两种,第一种是单行注释,第二种是多行注释。
单行注释的语法为:
--
注释内容
多行注释的语法为
:
--[[
注释内容
注释内容
--]]
如果想取消多行注释,只需要在第一个
--
之前在加一个
-
即可,如:
---[[
注释内容
注释内容
--]]
标识符
换句话说标识符就是我们的变量名,
Lua
定义变量名以一个字母
A
到
Z
或
a
到
z
或下划线
_
开头后加上
0
个或多个字母,下划线,数字(0到 9)。这块建议大家最好不要使用下划线加大写字母的标识符,因为
Lua 的保留字也是这样定义的,容易发生冲突。注意Lua
是区分大小写字母 的。
A0
关键字
下列是
Lua
的关键字,大家在定义常量、变量或其他用户自定义标识符
都要避免使用以下这些关键字:
![](https://img-blog.csdnimg.cn/20d315358ee644d5b7df7feaf35bf70d.png)
一般约定,以下划线开头连接一串大写字母的名字(比如
_VERSION
)
被保留用于
Lua
内部全局变量。这个也是上面我们不建议这么定义标识
符的原因。
运算符
Lua
中支持的运算符有算术运算符、关系运算符、逻辑运算符、其他运
算符。
算术运算符
:
+
加法
-
减法
*
乘法
/
除法
%
取余
^
乘幂
-
负号
例如
:
10+20 -->30
20-10 -->10
10*20 -->200
20/10 -->2
3%2 -->1
10^2 -->100
-10 -->-10
关系运算符
==
等于
~=
不等于
>
大于
<
小于
>=
大于等于
<=
小于等于
例如
:
10==10 -->true
10~=10 -->false
20>10 -->true
20<10 -->false
20>=10 -->true
20<=10 -->false
逻辑运算符
and
逻辑与
A and B &&
or
逻辑或
A or B ||
not
逻辑非 取反,如果为
true,
则返回
false !
逻辑运算符可以作为
if
的判断条件,返回的结果如下
:
A = true
B = true
A and B -->true
A or B -->true
not A -->false
A = true
B = false
A and B -->false
A or B -->true
not A -->false
A = false
B = true
A and B -->false
A or B -->true
not A -->true
其他运算符
..
连接两个字符串
#
一元预算法,返回字符串或表的长度
例如
:
> "HELLO ".."WORLD" -->HELLO WORLD
> #"HELLO" -->5
全局变量
&
局部变量
在
Lua
语言中,全局变量无须声明即可使用。在默认情况下,变量总是
认为是全局的,如果未提前赋值,默认为
nil:
![](https://img-blog.csdnimg.cn/43079c3c2785407a9b1d53590e5bc33f.png)
要想声明一个局部变量,需要使用
local
来声明
![](https://img-blog.csdnimg.cn/0d30da28ed4a4bb4ab8cd2bbf3a1d063.png)
Lua
数据类型
Lua
有
8
个数据类型
nil(
空,无效值
)
boolean(
布尔,
true/false)
number(
数值
)
string(
字符串
)
function(
函数
)
table
(表)
thread(
线程
)
userdata
(用户数据)
可以使用
type
函数测试给定变量或者的类型:
print(type(nil)) -->nil
print(type(true)) --> boolean
print(type(1.1*1.1)) --> number
print(type("Hello world")) --> string
print(type(io.stdin)) -->userdata
print(type(print)) --> function
print(type(type)) -->function
print(type{}) -->table
print(type(type(X))) --> string
nil
nil
是一种只有一个
nil
值的类型,它的作用可以用来与其他所有值进行区
分,也可以当想要移除一个变量时,只需要将该变量名赋值为
nil,
垃圾回
收就会会释放该变量所占用的内存。
boolean
boolean
类型具有两个值,
true
和
false
。
boolean
类型一般被用来做条
件判断的真与假。在
Lua
语言中,只会将
false
和
nil
视为假,其他的都视
为真,特别是在条件检测中
0
和空字符串都会认为是真,这个和我们熟悉
的大多数语言不太一样。
number
在
Lua5.3
版本开始,
Lua
语言为数值格式提供了两种选择
:integer(
整型
)
和
float(
双精度浮点型
)[
和其他语言不太一样,
float
不代表单精度类型
]
。
数值常量的表示方式
:
>4 -->4
>0.4 -->0.4
>4.75e-3 -->0.00475
>4.75e3 -->4750
不管是整型还是双精度浮点型,使用
type()
函数来取其类型,都会返回的
是
number
>type(3) -->number
>type(3.3) -->number
所以它们之间是可以相互转换的,同时,具有相同算术值的整型值和浮
点型值在
Lua
语言中是相等的
string
Lua
语言中的字符串即可以表示单个字符,也可以表示一整本书籍。在
Lua
语言中,操作
100K
或者
1M
个字母组成的字符串的程序很常见。
可以使用单引号或双引号来声明字符串
>a = "hello"
>b = 'world'
>print(a) -->hello
>print(b) -->world
如果声明的字符串比较长或者有多行,则可以使用如下方式进行声明
html = [[
<html>
<head>
<title>Lua-string</title>
</head>
<body>
<a href="http://www.lua.org">Lua</a>
</body>
</html>
]]
table
table
是
Lua
语言中最主要和强大的数据结构。使用表,
Lua
语言可以以
一种简单、统一且高效的方式表示数组、集合、记录和其他很多数据结
构。
Lua
语言中的表本质上是一种辅助数组。这种数组比
Java
中的数组
更加灵活,可以使用数值做索引,也可以使用字符串或其他任意类型的
值作索引
(
除
nil
外
)
。
创建表的最简单方式
:
> a = {}
创建数组
:
我们都知道数组就是相同数据类型的元素按照一定顺序排列的集合,那
么使用
table
如何创建一个数组呢
?
>arr = {"TOM","JERRY","ROSE"}
要想获取数组中的值,我们可以通过如下内容来获取
:
print(arr[0]) nil
print(arr[1]) TOM
print(arr[2]) JERRY
print(arr[3]) ROSE
从上面的结果可以看出来,数组的下标默认是从
1
开始的。所以上述创建
数组,也可以通过如下方式来创建
>arr = {}
>arr[1] = "TOM"
>arr[2] = "JERRY"
>arr[3] = "ROSE"
上面我们说过了,表的索引即可以是数字,也可以是字符串等其他的内
容,所以我们也可以将索引更改为字符串来创建
>arr = {}
>arr["X"] = 10
>arr["Y"] = 20
>arr["Z"] = 30
当然,如果想要获取这些数组中的值,可以使用下面的方式
方式一
>print(arr["X"])
>print(arr["Y"])
>print(arr["Z"])
方式二
>print(arr.X)
>print(arr.Y)
>print(arr.Z)
当前
table
的灵活不进于此,还有更灵活的声明方式
>arr = {"TOM",X=10,"JERRY",Y=20,"ROSE",Z=30}
如何获取上面的值
?
TOM : arr[1]
10 : arr["X"] | arr.X
JERRY: arr[2]
20 : arr["Y"] | arr.Y
ROESE?
function
在
Lua
语言中,函数(
Function
)是对语句和表达式进行抽象的主要方
式。
定义函数的语法为
:
function functionName(params)
end
函数被调用的时候,传入的参数个数与定义函数时使用的参数个数不一
致的时候,
Lua
语言会通过 抛弃多余参数和将不足的参数设为
nil
的方
式来调整参数的个数。
function f(a,b)
print(a,b)
end
f() --> nil nil
f(2) --> 2 nil
f(2,6) --> 2 6
f(2.6.8) --> 2 6 (8
被丢弃
)
可变长参数函数
function add(...)
a,b,c=...
print(a)
print(b)
print(c)
end
add(1,2,3) --> 1 2 3
函数返回值可以有多个,这点和
Java
不太一样
function f(a,b)
return a,b
end
x,y=f(11,22) --> x=11,y=22
thread
thread
翻译过来是线程的意思,在
Lua
中,
thread
用来表示执行的独立
线路,用来执行协同程序。
userdata
userdata
是一种用户自定义数据,用于表示一种由应用程序或
C/C++
语
言库所创建的类型。
Lua
控制结构
Lua
语言提供了一组精简且常用的控制结构,包括用于条件执行的证 以 及用于循环的 while、
repeat
和
for
。 所有的控制结构语法上都有一个 显式的终结符: end
用于终结
if
、
for
及
while
结构,
until
用于终结 repeat 结构。
if then elseif else
if
语句先测试其条件,并根据条件是否满足执行相应的
then
部分或
else
部分。
else
部分 是可选的。
function testif(a)
if a>0 then
print("a
是正数
")
end
end
function testif(a)
if a>0 then
print("a
是正数
")
else
print("a
是负数
")
end
end
如果要编写嵌套的
if
语句,可以使用
elseif
。 它类似于在
else
后面紧跟
一个
if
。根据传入的年龄返回不同的结果,如
age<=18
青少年,
age>18 , age <=45
青年
age>45 , age<=60
中年人
age>60
老年人
function show(age)
if age<=18 then
return "
青少年
"
elseif age>18 and age<=45 then
return "
青年
"
elseif age>45 and age<=60 then
return "
中年人
"
elseif age>60 then
return "
老年人
"
end
end
while
循环
顾名思义,当条件为真时
while
循环会重复执行其循环体。
Lua
语言先
测试
while
语句 的条件,若条件为假则循环结束;否则,
Lua
会执行循
环体并不断地重复这个过程。
语法:
while
条件
do
循环体
end
例子
:
实现数组的循环
function testWhile()
local i = 1
while i<=10 do
print(i)
i=i+1
end
end
repeat
循环
顾名思义,
repeat-until
语句会重复执行其循环体直到条件为真时结
束。 由于条件测试在循环体之后执行,所以循环体至少会执行一次。
语法
repeat
循环体
until
条件
function testRepeat()
local i = 10
repeat
print(i)
i=i-1
until i < 1
end
for
循环
数值型
for
循环
语法
for param=exp1,exp2,exp3 do
循环体
end
param
的值从
exp1
变化到
exp2
之前的每次循环会执行 循环体,并在每
次循环结束后将步长
(step)exp3
增加到
param
上。
exp3
可选,如果不设
置默认为
1
for i = 1,100,10 do
print(i)
end
泛型
for
循环
泛型
for
循环通过一个迭代器函数来遍历所有值,类似于
java
中的
foreach
语句。
语法
for i,v in ipairs(x) do
循环体
end
i
是数组索引值,
v
是对应索引的数组元素值,
ipairs
是
Lua
提供的一个迭
代器函数,用来迭代数组,
x
是要遍历的数组。
例如
:
arr = {"TOME","JERRY","ROWS","LUCY"}
for i,v in ipairs(arr) do
print(i,v)
end
上述实例输出的结果为
1 TOM
2 JERRY
3 ROWS
4 LUCY
但是如果将
arr
的值进行修改为
arr = {"TOME","JERRY","ROWS",x="JACK","LUCY"}
同样的代码在执行的时候,就只能看到和之前一样的结果,而其中的
x
为
JACK
就无法遍历出来,缺失了数据,如果解决呢
?
我们可以将迭代器函数变成
pairs,
如
for i,v in pairs(arr) do
print(i,v)
end
上述实例就输出的结果为
1 TOM
2 JERRY
3 ROWS
4 LUCY
x JACK
4.2ngx_lua模块概念
淘宝开发的
ngx_lua
模块通过将
lua
解释器集成进
Nginx
,可以采用
lua
脚
本实现业务逻辑,由于
lua
的紧凑、快速以及内建协程,所以在保证高并
发服务能力的同时极大地降低了业务逻辑实现成本。
4.3ngx_lua模块环境准备
方式一:lua-nginx-module
1. LuaJIT是采用C语言编写的Lua代表的解释器。
官网地址为
:
http://luajit.org/
在官网上找到对应的下载地址
:
http://luajit.org/download/LuaJIT-2.0.5.t
ar.gz
方式二
:OpenRestry
概述
前面我们提到过,
OpenResty
是由淘宝工程师开发的,所以其官方网站
(
http://openresty.org/
)
我们读起来是非常的方便。
OpenResty
是一个基
于
Nginx
与
Lua
的高性能
Web
平台,其内部集成了大量精良的
Lua
库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并
发、扩展性极高的动态
Web
应用、
Web
服务和动态网关。所以本身
OpenResty
内部就已经集成了
Nginx
和
Lua
,所以我们使用起来会更加方
便。
4.4ngx_lua的使用
使用Lua编写Nginx脚本的基本构建块是指令。指令用于指定何时运行用
户
Lua
代码以及如何使用结果。下图显示了执行指令的顺序。
4.5ngx_lua操作Redis
Redis
在系统中经常作为数据缓存、内存数据库使用,在大型系统中扮演
着非常重要的作用。在
Nginx
核心系统中,
Redis
是常备组件。
Nginx
支
持
3
种方法访问
Redis,
分别是
HttpRedis
模块、
HttpRedis2Module
、
lua
resty-redis
库。这三种方式中
HttpRedis
模块提供的指令少,功能单一,
适合做简单缓存,
HttpRedis2Module
模块比
HttpRedis
模块操作更灵
活,功能更强大。而
Lua-resty-redis
库是
OpenResty
提供的一个操作
Redis
的接口库,可根据自己的业务情况做一些逻辑处理,适合做复杂的
业务逻辑。所以本次课程将主要以
Lua-resty-redis
来进行讲解。
lua-resty-redis
环境准备
步骤一
:
准备一个
Redis
环境
4.6ngx_lua操作Mysql
MySQL
是一个使用广泛的关系型数据库。在
ngx_lua
中,
MySQL
有两种
访问模式
,
分别是使
(1)用
ngx_lua
模块和
lua-resty-mysql
模块:这两个模块是安装
OpenResty
时默认安装的。
(2)使用
drizzle_nginx_module(HttpDrizzleModule)
模块:需要单独
安装,这个库现不在
OpenResty
中。
lua-resty-mysql
lua-resty-mysql
是
OpenResty
开发的模块,使用灵活、功能强大,适合
复杂的业务场景,同时支持存储过程的访问。
使用
lua-resty-mysql
实现数据库的查询
使用
lua-cjson
处理查询结果
通过上述的案例学习,
read_result()
得到的结果
res
都是
table
类型,要想
在页面上展示,就必须知道
table
的具体数据结构才能进行遍历获取。处
理起来比较麻烦,接下来我们介绍一种简单方式
cjson
,使用它就可以将
table
类型的数据转换成
json
字符串,把
json
字符串展示在页面上即可。
具体如何使用
?
步骤一:引入
cjson
lua-resty-mysql
实现数据库的增删改
优化
send_query
和
read_result
本方法是
send_query
和
read_result
组合的快捷方法。
语法
:
有了该
API
,上面的代码我们就可以进行对应的优化,如下
:
综合小案例
使用
ngx_lua
模块完成
Redis
缓存预热。
分析
:
(1)先得有一张表
(users)
(2)浏览器输入如下地址
http://191.168.200.133?username=TOM
(3)从表中查询出符合条件的记录,此时获取的结果为
table
类型
(4)使用
cjson
将
table
数据转换成
json
字符串
(5)将查询的结果数据存入
Redis
中
init_by_lua_block{
redis = require "resty.redis"
mysql = require "resty.mysql"
cjson = require "cjson"
}
location /{
default_type "text/html";
content_by_lua_block{
--
获取请求的参数
username
local param = ngx.req.get_uri_args()
["username"]
--
建立
mysql
数据库的连接
local db = mysql:new()
local ok,err = db:connect{
host="192.168.200.111",
port=3306,
user="root",
password="123456",
database="nginx_db"
}
if not ok then
ngx.say("failed connect to
mysql:",err)
return
end
--
设置连接超时时间
db:set_timeout(1000)
--
查询数据
local sql = "";
if not param then
31
sql="select * from users"
else
sql="select * from users where
username=".."'"..param.."'"
end
local
res,err,errcode,sqlstate=db:query(sql)
if not res then
ngx.say("failed to query from
mysql:",err)
return
end
--
连接
redis
local rd = redis:new()
ok,err =
rd:connect("192.168.200.111",6379)
if not ok then
ngx.say("failed to connect to
redis:",err)
return
end
rd:set_timeout(1000)
--
循环遍历数据
for i,v in ipairs(res) do
rd:set("user_"..v.username,cjson.encode(v))
end
ngx.say("success")
rd:close()
db:close()
}
}