目录
- 1.Nginx实现服务器端集群搭建
- 1.1.Nginx 与 Tomcat 部署
- 1.1.1.环境准备 (Tomcat)
- 1.1.2.环境准备 (Nginx)
- 1.2.Nginx实现动静分离
- 1.2.1.概述
- 1.2.2.需求分析
- 1.2.3.动静分离实现步骤
- 1.3.Nginx 实现 Tomcat 集群搭建
- 1.4.Nginx 高可用解决方案
- 1.4.1.概述
- 1.4.2.Keepalived 介绍
- 1.4.3.环境搭建
- 1.4.4.Keepalived 配置文件介绍
- 1.4.5.访问测试
- 1.4.6.keepalived 之 vrrp_script
- 2.Nginx 制作下载站点
- 3.Nginx 的用户认证模块
- 4.Nginx 的扩展模块
- 4.1.Lua
- 4.1.1.概述
- 4.1.2.Lua 的安装
- 4.1.3.Lua 的语法
- 4.1.3.1.第一个 Lua 程序
- 4.1.3.2.Lua 的注释
- 4.1.3.3.标识符
- 4.1.3.4.关键字
- 4.1.3.5.运算符
- 4.1.3.6.全局变量 & 局部变量
- 4.1.4.Lua 数据类型
- 4.1.4.1nil
- 4.1.4.2.boolean
- 4.1.4.3.number
- 4.1.4.4.string
- 4.1.4.5.table
- 4.1.4.6.function
- 4.1.4.7.thread
- 4.1.4.8.userdata
- 4.1.5.Lua 控制结构
- 4.1.5.1.if then elseif else
- 4.1.5.2.while 循环
- 4.1.5.3.repeat 循环
- 4.1.5.4.for 循环
- 4.2.ngx_lua 模块概念
- 4.3.ngx_lua 模块环境准备
- 4.3.1.方式一:lua-nginx-module
- 4.3.2.方式二:OpenRestry
- 4.3.2.1.概述
- 4.3.2.2.安装
- 4.4.ngx_lua 的使用
- 4.5.ngx_lua 操作 Redis
- 4.6.ngx_lua 操作 MySQL
- 4.6.1.概述
- 4.6.2.lua-resty-mysql
- 4.6.2.1.使用 lua-cjson 处理查询结果
- 4.6.2.2.lua-resty-mysql 实现数据库的增删改
- 4.7.综合小案例
本文笔记整理自黑马程序员 Nginx 教程,相关资料可在该视频评论区中领取。
1.Nginx实现服务器端集群搭建
1.1.Nginx 与 Tomcat 部署
(1)前面已经将 Nginx 的大部分内容进行了讲解,我们都知道了 Nginx 在高并发场景和处理静态资源是非常高性能的,但是在实际项目中除了静态资源还有就是后台业务代码模块,一般后台业务都会被部署在 Tomcat,Weblogic 或者是 Websphere 等 Web 服务器上。那么如何使用 Nginx 接收用户的请求并把请求转发到后台 Web 服务器?
(2)步骤分析:
- 准备 Tomcat 环境,并在 Tomcat 上部署一个 Web 项目;
- 准备 Nginx 环境,使用 Nginx 接收请求,并把请求分发到 Tomcat 上;
1.1.1.环境准备 (Tomcat)
浏览器访问:
http://192.168.200.146:8080/demo/index.html
获取动态资源的链接地址:
http://192.168.200.146:8080/demo/getAddress
本次课程将采用 Tomcat 作为后台 Web 服务器:
- 在 CentOS 上准备一个 Tomcat
# Tomcat官网地址:https://tomcat.apache.org/
# 下载 Tomcat,这里使用的是 apache-tomcat-8.5.59.tar.gz
# 将 Tomcat 进行解压缩
mkdir web_tomcat
tar -zxf apache-tomcat-8.5.59.tar.gz -C /web_tomcat
- 准备一个 Web 项目,将其打包为 war
# 将资料中的 demo.war 上传到 tomcat8 目录下的 webapps 包下
# 将tomcat进行启动,进入 tomcat8 的 bin 目录下
./startup.sh
- 启动 Tomcat 进行访问测试:
- 静态资源:
http://192.168.200.146:8080/demo/index.html
- 动态资源:
http://192.168.200.146:8080/demo/getAddress
- 静态资源:
1.1.2.环境准备 (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)启动访问测试:
可能大家会有一个困惑,明明直接通过 Tomcat 就能访问,为什么还需要多加一个 Nginx,这样不是反而是系统的复杂度变高了?那接下来我们从两个方便给大家分析下这个问题:
- 第一个使用 Nginx 实现动静分离;
- 第二个使用 Nginx 搭建 Tomcat 的集群;
1.2.Nginx实现动静分离
1.2.1.概述
(1)什么是动静分离?
- 动:后台应用程序的业务处理;
- 静:网站的静态资源 (html、javaScript、css、images 等文件);
- 分离:将两者进行分开部署访问,提供用户进行访问。举例说明就是以后所有和静态资源相关的内容都交给 Nginx 来部署访问,非静态内容则交个类似于 Tomcat 的服务器来部署访问。
(2)为什么要动静分离?
- 前面我们介绍过 Nginx 在处理静态资源的时候,效率是非常高的,而且 Nginx 的并发访问量也是名列前茅,而 Tomcat 则相对比较弱一些,所以把静态资源交个 Nginx 后,可以减轻 Tomcat 服务器的访问压力并提高静态资源的访问速度。
- 动静分离以后,降低了动态资源和静态资源的耦合度。如动态资源宕机了也不影响静态资源的展示。
(3)如何实现动静分离?
实现动静分离的方式很多,比如静态资源可以部署到 CDN、Nginx 等服务器上,动态资源可以部署到 Tomcat、Weblogic 或者 Websphere 上。这里使用 Nginx + Tomcat 来实现动静分离。
1.2.2.需求分析
1.2.3.动静分离实现步骤
(1)将 demo.war 项目中的静态资源都删除掉,重新打包生成一个 war 包,在资料中有提供。
(2)将 war 包部署到 Tomcat 中,把之前部署的内容删除掉。
- 进入到 Tomcat 的 webapps 目录下,将之前的内容删除掉;
- 将新的 war 包复制到 webapps 下;
- 将 Tomcat 启动;
(3)在 Nginx 所在服务器创建如下目录,并将对应的静态资源放入指定的位置。
其中 index.html 页面的内容如下:
<!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',function(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
假如某个时间点,由于某个原因导致 Tomcat 后的服务器宕机了,我们再次访问 Nginx,会得到如下效果,用户还是能看到页面,只是缺失了访问次数的统计,这就是前后端耦合度降低的效果,并且整个请求只和后的服务器交互了一次,js 和 images 都直接从 Nginx 返回,提供了效率,降低了后端服务器的压力。
1.3.Nginx 实现 Tomcat 集群搭建
(1)在使用 Nginx 和 Tomcat 部署项目的时候,我们使用的是一台 Nginx 服务器和一台 Tomcat 服务器,效果图如下:
那么问题来了,如果 Tomcat 的真的宕机了,整个系统就会不完整,所以如何解决上述问题,一台服务器容易宕机,那就多搭建几台 Tomcat 服务器,这样的话就提升了后的服务器的可用性。这也就是我们常说的集群,搭建 Tomcat 的集群需要用到了 Nginx 的反向代理和赋值均衡的知识,具体如何来实现?我们先来分析下原理:
(2)环境准备:
- 准备 3 台 Tomcat,使用端口进行区分(实际环境应该是三台服务器),修改 server.xml,将端口修改分别修改为8080、8180、8280
- 启动 Tomcat 并访问测试;
http://192.168.200.146:8080/demo/getAddress
http://192.168.200.146:8180/demo/getAddress
http://192.168.200.146:8280/demo/getAddress
(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.4.Nginx 高可用解决方案
1.4.1.概述
针对于上面提到的问题,我们来分析下要想解决上述问题,需要面临哪些问题?
需要两台以上的 Nginx 服务器对外提供服务,这样的话就可以解决其中一台宕机了,另外一台还能对外提供服务,但是如果是两台 Nginx 服务器的话,会有两个 IP 地址,用户该访问哪台服务器,用户怎么知道哪台是好的,哪台是宕机了的?
1.4.2.Keepalived 介绍
(1)使用 Keepalived
来解决,Keepalived 软件由 C 编写的,最初是专为 LVS 负载均衡软件设计的,Keepalived 软件主要是通过 VRRP 协议实现高可用功能。
(2)VRRP (Virtual Route Redundancy Protocol) 协议,翻译过来为虚拟路由冗余协议。VRRP 协议将两台或多台路由器设备虚拟成一个设备,对外提供虚拟路由器IP,而在路由器组内部,如果实际拥有这个对外IP的路由器如果工作正常的话就是 MASTER,MASTER 实现针对虚拟路由器 IP 的各种网络功能。其他设备不拥有该虚拟 IP,状态为 BACKUP,除了接收 MASTER 的 VRRP 状态通告信息以外,不执行对外的网络功能。当主机失效时,BACKUP 将接管原先 MASTER 的网络功能。
从上面的介绍信息获取到的内容就是 VRRP 是一种协议,那这个协议是用来干什么的?
- 选择协议:VRRP 可以把一个虚拟路由器的责任动态分配到局域网上的 VRRP 路由器中的一台。其中的虚拟路由即 Virtual 路由是由 VRRP 路由群组创建的一个不真实存在的路由,这个虚拟路由也是有对应的 IP 地址。而且 VRRP 路由 1 和 VRRP 路由 2 之间会有竞争选择,通过选择会产生一个 Master 路由和一个 Backup 路由。
- 路由容错协议:Master 路由和 Backup 路由之间会有一个心跳检测,Master 会定时告知 Backup 自己的状态,如果在指定的时间内,Backup 没有接收到这个通知内容,Backup就会替代 Master 成为新的 Master。Master 路由有一个特权就是虚拟路由和后端服务器都是通过Master 进行数据传递交互的,而备份节点则会直接丢弃这些请求和数据,不做处理,只是去监听 Master 的状态
(3)用了 Keepalived 后,解决方案如下:
1.4.3.环境搭建
(1)环境准备
VIP | IP | 主机名 | 主/从 |
---|---|---|---|
192.168.200.133 | keepalived1 | Master | |
192.168.200.222 | |||
192.168.200.122 | keepalived2 | Backup |
(2)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;
1.4.4.Keepalived 配置文件介绍
打开 keepalived.conf 配置文件,这里面会分三部,第一部分是 global 全局配置、第二部分是 VRRP 相关配置、第三部分是 LVS 相关配置。这里主要是使用 keepalived 实现高可用部署,没有用到 LVS,所以我们重点关注的是前两部分。
# global 全局部分
global_defs {# 通知邮件,当 keepalived 发送切换时需要发邮件给具体的邮箱地址notification_email {tom@itcast.cnjerry@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 之间的延迟,可以精确到毫秒级,默认是 0vrrp_garp_interval 0# 在一个网卡上每组 na 消息之间的延迟时间,默认为 0vrrp_gna_interval 0
}
VRRP 部分,该部分可以包含以下四个子模块
vrrp_script
vrrp_sync_group
garp_group
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-255priority 100 # 指定优先级,优先级高的将成为 MASTERadvert_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.cnjerry@itcast.cn}notification_email_from zhaomin@itcast.cnsmtp_server 192.168.200.1smtp_connect_timeout 30router_id keepalived1vrrp_skip_check_adv_addrvrrp_strictvrrp_garp_interval 0vrrp_gna_interval 0
}vrrp_instance VI_1 {state MASTERinterface ens33virtual_router_id 51priority 100advert_int 1authentication {auth_type PASSauth_pass 1111}virtual_ipaddress {192.168.200.222}
}
服务器 2
# Configuration File for keepalivedglobal_defs {notification_email {tom@itcast.cnjerry@itcast.cn}notification_email_from zhaomin@itcast.cnsmtp_server 192.168.200.1smtp_connect_timeout 30router_id keepalived2vrrp_skip_check_adv_addrvrrp_strictvrrp_garp_interval 0vrrp_gna_interval 0
}vrrp_instance VI_1 {state BACKUPinterface ens33virtual_router_id 51priority 90advert_int 1authentication {auth_type PASSauth_pass 1111}virtual_ipaddress {192.168.200.222}
}
1.4.5.访问测试
- 启动 keepalived 之前,先使用命令
ip a
查看 192.168.200.133 和 192.168.200.122 这两台服务器的 IP 情况。
- 分别启动两台服务器的 keepalived
cd /usr/local/sbin
./keepalived
- 再次通过
ip a
查看 IP
- 当把 192.168.200.133 服务器上的 keepalived 关闭后,再次查看 IP
通过上述的测试,我们会发现,虚拟 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/
如果把 192.168.200.133 服务器的 keepalived 关闭掉,再次访问相同的地址
效果实现了以后, 我们会发现要想让 VIP 进行切换,就必须要把服务器上的 keepalived 进行关闭,而什么时候关闭 keepalived 呢?应该是在 keepalived 所在服务器的 Nginx 出现问题后,把 keepalived 关闭掉,就可以让 VIP 执行另外一台服务器,但是现在这所有的操作都是通过手动来完成的,我们如何能让系统自动判断当前服务器的 Nginx 是否正确启动,如果没有,要能让 VIP 自动进行"漂移",这个问题该如何解决?
1.4.6.keepalived 之 vrrp_script
keepalived 只能做到对网络故障和 keepalived 本身的监控,即当出现网络故障或者 keepalived 本身出现问题时,进行切换。但是这些还不够,我们还需要监控 keepalived 所在服务器上的其他业务,比如 Nginx,如果 Nginx 出现异常了,仅仅 keepalived 保持正常,是无法完成系统的正常工作的,因此需要根据业务进程的运行状态决定是否需要进行主备切换,这个时候,我们可以通过编写脚本对业务进程进行检测监控。实现步骤如下:
- 在 keepalived 配置文件中添加对应的配置像
vrrp_script 脚本名称
{script "脚本位置"interval 3 # 执行时间间隔weight -20 # 动态调整 vrrp_instance 的优先级
}
- 编写脚本 ck_nginx.sh
#!/bin/bash
num=`ps -C nginx --no-header | wc -l`
if [ $num -eq 0 ];then/usr/local/nginx/sbin/nginxsleep 2if [ `ps -C nginx --no-header | wc -l` -eq 0 ]; thenkillall keepalivedfi
fi
Linux 中的 ps
命令用于显示当前进程 (process) 的状态,其中 -C(command) 指定命令的所有进程,–no-header 排除标题
- 为脚本文件设置权限
chmod 755 ck_nginx.sh
- 将脚本添加到
vrrp_script ck_nginx {script "/etc/keepalived/ck_nginx.sh" # 执行脚本的位置interval 2 # 执行脚本的周期,秒为单位weight -20 # 权重的计算方式
}
vrrp_instance VI_1 {state MASTERinterface ens33virtual_router_id 10priority 100advert_int 1authentication {auth_type PASSauth_pass 1111}virtual_ipaddress {192.168.200.111}track_script {ck_nginx}
}
- 如果效果没有出来,可以使用
tail -f /var/log/messages
查看日志信息,找对应的错误信息。 - 测试
问题思考:通常如果 master 服务死掉后 backup 会变成 master,但是当 master 服务又好了的时候 master 此时会抢占 VIP,这样就会发生两次切换对业务繁忙的网站来说是不好的。所以我们要在配置文件加入 nopreempt 非抢占,但是这个参数只能用于 state 为 backup,故我们在用 HA 的时候最好 master 和 backup 的 state 都设置成 ·backup·,让其通过 priority
来竞争。
2.Nginx 制作下载站点
(1)首先我们先要清楚什么是下载站点?我们先来看一个网站 http://nginx.org/download/
这个我们刚开始学习 Nginx 的时候给大家看过这样的网站,该网站主要就是用来提供用户来下载相关资源的网站,就叫做下载网站。
(2)如何制作一个下载站点:
- Nginx 使用的是模块
ngx_http_autoindex_module
来实现的,该模块处理以斜杠 (“/”) 结尾的请求,并生成目录列表; - Nginx 编译的时候会自动加载该模块,但是该模块默认是关闭的,我们需要使用下来指令来完成对应的配置;
(3)相关指令如下
autoindex
:启用或禁用目录列表输出;
语法 | autoindex on|off; |
---|---|
默认值 | autoindex off; |
位置 | http、server、location |
autoindex_exact_size
:对应 HTLM 格式,指定是否在目录列表展示文件的详细大小;- 默认为 on,显示出文件的确切大小,单位是 bytes;
- 改为 off 后,显示出文件的大概大小,单位是 KB 或者 MB 或者 GB;
语法 | autoindex_exact_size on|off; |
---|---|
默认值 | autoindex_exact_size on; |
位置 | http、server、location |
autoindex_format
:设置目录列表的格式;
语法 | autoindex_format html|xml|json|jsonp; |
---|---|
默认值 | autoindex_format html; |
位置 | http、server、location |
注意:该指令在 1.7.9 及以后版本中出现;
autoindex_localtime
:对应 HTML 格式,是否在目录列表上显示时间。- 默认为 off,显示的文件时间为 GMT 时间;
- 改为 on 后,显示的文件时间为文件的服务器时间;
语法 | autoindex_localtime on | off; |
---|---|
默认值 | autoindex_localtime off; |
位置 | http、server、location |
(4)配置方式如下:
# 下载站点的相关配置
location /download {root /usr/local; # 存放下载资源的路径,即 /usr/local/download autoindex on; # 启用目录列表输出autoindex_exact_size on; # 显示出文件的确切大小,单位是 bytesautoindex_format html; # 设置页面展示的格式,默认为 HTMLautoindex_localtime on; # 开启显示文件时间所在服务器的时间
}
3.Nginx 的用户认证模块
(1)对应系统资源的访问,我们往往需要限制谁能访问,谁不能访问。这块就是我们通常所说的认证部分,认证需要做的就是根据用户输入的用户名和密码来判定用户是否为合法用户,如果是则放行访问,如果不是则拒绝访问。
(2)Nginx 对应用户认证这块是通过 ngx_http_auth_basic_module
模块来实现的,它允许通过使用 "HTTP 基本身份验证"协议验证用户名和密码来限制对资源的访问。默认情况下 Nginx 是已经安装了该模块,如果不需要则使用 --without-http_auth_basic_module
。
(3)该模块的指令比较简单:
auth_basic
:使用“ HTTP基本认证”协议启用用户名和密码的验证,开启后,服务端会返回 401,指定的字符串会返回到客户端,给用户以提示信息,但是不同的浏览器对内容的展示不一致。
语法 | auth_basic string|off; |
---|---|
默认值 | auth_basic off; |
位置 | http,server,location,limit_except |
auth_basic_user_file
:指定用户名和密码所在文件
语法 | auth_basic_user_file file; |
---|---|
默认值 | — |
位置 | http,server,location,limit_except |
指定文件路径,该文件中的用户名和密码的设置,密码需要进行加密。可以采用工具自动生成
实现步骤:
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 # 验证用户名和密码是否正确
上述方式虽然能实现用户名和密码的验证,但是所有的用户名和密码信息都记录在文件里面,如果用户量过大的话,这种方式就显得有点麻烦,这时候我们就得通过后台业务代码来进行用户权限的校验了。
4.Nginx 的扩展模块
Nginx 是可扩展的,可用于处理各种使用场景。本节中,我们将探讨使用 Lua 来扩展 Nginx 的功能。
4.1.Lua
4.1.1.概述
(1)Lua 是一种轻量、小巧的脚本语言,用标准 C 语言编写并以源代码形式开发。设计的目的是为了嵌入到其他应用程序中,从而为应用程序提供灵活的扩展和定制功能。
(2)跟其他语言进行比较,Lua 有其自身的特点:
- 轻量级:Lua 用标准 C 语言编写并以源代码形式开发,编译后仅仅一百余千字节,可以很方便的嵌入到其他程序中;
- 可扩展:Lua 提供非常丰富易于使用的扩展接口和机制,由宿主语言(通常是 C 或 C++)提供功能,Lua 可以使用它们,就像内置的功能一样;
- 支持面向过程编程和函数式编程;
(3)Lua 在不同的系统中得到大量应用,场景的应用场景如下:游戏开发、独立应用脚本、Web 应用脚本、扩展和数据库插件、系统安全上。
4.1.2.Lua 的安装
在 Linux 上安装 Lua 非常简单,只需要下载源码包并在终端解压、编译即可使用。Lua 的官网地址为:https://www.lua.org。
- 点击 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
- 编译安装
cd lua-5.4.1
make linux test
make install
- 如果在执行
make linux test
失败,报如下错误:
则说明当前系统缺少 libreadline-dev
依赖包,需要通过命令来进行安装:
yum install -y readline-devel
- 验证 Lua 是否安装成功
lua -v
4.1.3.Lua 的语法
4.1.3.1.第一个 Lua 程序
(1)Lua 和 C/C++ 语法非常相似,整体上比较清晰,简洁。条件语句、循环语句、函数调用都与 C/C++ 基本一致。Lua 有两种交互方式,分别是交互式和脚本式,这两者的区别如下:
- 交互式之 HELLOWORLD:交互式是指可以在命令行输入程序,然后回车就可以看到运行的效果。 Lua 交互式编程模式可以通过命令
lua -
i 或lua
来启用:
在命令行中输入命令 print("Hello World!!")
,并按回车,会有输出在控制台:
- 脚本式之 HELLOWORLD:脚本式是将代码保存到一个以 lua 为扩展名的文件中并执行的方式。
方式一:我们需要一个文件名为 hello.lua,在文件中添加要执行的代码,然后通过命令 lua hello.lua
来执行,会在控制台输出对应的结果。
--hello.lua
print("Hello World!!")
方式二:将 hello.lua
做如下修改
#!/usr/local/bin/lua
print("Hello World!!!")
第一行用来指定 Lua 解释器所在位置为 /usr/local/bin/lua,加上 # 号标记解释器会忽略它。一般情况下 #! 就是用来指定用哪个程序来运行本文件。但是 hello.lua 并不是一个可执行文件,需要通过 chmod 来设置可执行权限,最简单的方式为:
chmod 755 hello.lua
然后执行该文件
./hello.lua
(2)补充一点,如果想在交互式中运行脚本式的 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
不建议使用第四种方式,可读性太差。
4.1.3.2.Lua 的注释
关于 Lua 的注释要分两种,第一种是单行注释,第二种是多行注释。
- 单行注释的语法为:
--注释内容
- 多行注释的语法为:
--[[注释内容注释内容
--]]
- 如果想取消多行注释,只需要在第一个 – 之前在加一个 - 即可,如:
---[[注释内容注释内容
--]]
4.1.3.3.标识符
换句话说标识符就是我们的变量名,Lua 定义变量名以一个字母 A 到 Z 、或 a 到 z 、或以下划线 _ 开头后,加上 0 个或多个字母、下划线、数字 0到 9。这块建议大家最好不要使用下划线加大写字母的标识符,因为 Lua 的保留字也是这样定义的,容易发生冲突。注意 Lua 是区分大小写字母的。
4.1.3.4.关键字
下列是 Lua 的关键字,大家在定义常量、变量或其他用户自定义标识符都要避免使用以下这些关键字:
and | break | do | else |
---|---|---|---|
elseif | end | false | for |
function | if | in | local |
nil | not | or | repeat |
return | then | true | until |
while | goto |
一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量。这个也是上面我们不建议这么定义标识符的原因。
4.1.3.5.运算符
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 = trueA and B -->true
A or B -->true
not A -->falseA = true
B = falseA and B -->false
A or B -->true
not A -->falseA = false
B = trueA and B -->false
A or B -->true
not A -->true
- 其他运算符:
.. 连接两个字符串
# 一元预算法,返回字符串或表的长度
例如:
> "HELLO ".."WORLD" --> HELLO WORLD
> #"HELLO" --> 5
4.1.3.6.全局变量 & 局部变量
(1)在 Lua 语言中,全局变量无须声明即可使用。在默认情况下,变量总是认为是全局的,如果未提前赋值,默认为 nil
:
(2)要想声明一个局部变量,需要使用 local
来声明:
4.1.4.Lua 数据类型
(1)Lua 有 8 个数据类型
nil
:空,无效值boolean
:布尔值,true/falsenumber
:数值string
:字符串function
:函数table
:表thread
:线程userdata
:用户数据
(2)可以使用 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
4.1.4.1nil
nil 是一种只有一个 nil 值的类型,它的作用可以用来与其他所有值进行区分,也可以当想要移除一个变量时,只需要将该变量名赋值为nil,垃圾回收就会会释放该变量所占用的内存。
4.1.4.2.boolean
boolean 类型具有两个值,true 和 false。boolean 类型一般被用来做条件判断的真与假。在 Lua 语言中,只会将 false 和 nil 视为假,其他的都视为真,特别是在条件检测中 0 和空字符串都会认为是真,这个和我们熟悉的大多数语言不太一样。
4.1.4.3.number
在 Lua 5.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 语言中是相等的。
4.1.4.4.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>
]]
4.1.4.5.table
table 是 Lua 语言中最主要和强大的数据结构。通过使用 table , 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?
4.1.4.6.function
(1)在 Lua 语言中,函数 (Function) 是对语句和表达式进行抽象的主要方式。定义函数的语法为:
function functionName(params)end
(2)函数被调用的时候,传入的参数个数与定义函数时使用的参数个数不一致的时候,Lua 语言会通过 抛弃多余参数和将不足的参数设为 nil 的方式来调整参数的个数。
function f(a,b)
print(a,b)
endf() --> 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)
endadd(1,2,3) --> 1 2 3
函数返回值可以有多个,这点和 Java 不太一样:
function f(a,b)
return a,b
endx,y=f(11,22) --> x=11,y=22
4.1.4.7.thread
thread 翻译过来是线程的意思,在Lua中,thread 用来表示执行的独立线路,用来执行协同程序。
4.1.4.8.userdata
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型。
4.1.5.Lua 控制结构
Lua 语言提供了一组精简且常用的控制结构,包括用于条件执行的 if 以及用于循环的 while、 repeat 和 for。 所有的控制结构语法上都有一个显式的终结符 end
,它用于终结 if、 for 及 while 结构, until 用于终结 repeat 结构。
4.1.5.1.if then elseif else
if 语句先测试其条件,并根据条件是否满足执行相应的 then 部分或 else 部分。 else 部分是可选的。
function testif(a)if a>0 thenprint("a是正数")end
endfunction testif(a)if a>0 thenprint("a是正数")elseprint("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 thenreturn "青少年"
elseif age>18 and age<=45 thenreturn "青年"
elseif age>45 and age<=60 thenreturn "中年人"
elseif age>60 thenreturn "老年人"
end
end
4.1.5.2.while 循环
顾名思义,当条件为真时 while 循环会重复执行其循环体。 Lua 语言先测试 while 语句 的条件,若条件为假则循环结束;否则, Lua 会执行循环体并不断地重复这个过程。语法:
while 条件 do循环体
end
例子:实现数组的循环
function testWhile()local i = 1while i<=10 doprint(i)i=i+1end
end
4.1.5.3.repeat 循环
顾名思义, repeat-until语句会重复执行其循环体直到条件为真时结束。 由于条件测试在循环体之后执行,所以循环体至少会执行一次。语法如下:
repeat循环体until 条件
例子如下:
function testRepeat()local i = 10repeatprint(i)i=i-1until i < 1
end
4.1.5.4.for 循环
(1)数值型 for 循环。语法如下
for param=exp1,exp2,exp3 do循环体
end
param 的值从 exp1 变化到 exp2,之前的每次循环会执行循环体,并在每次循环结束后将步长 exp3 增加到 param 上。exp3 可选,如果不设置默认为 1。例子如下:
for i = 1,100,10 doprint(i)
end
(2)泛型 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) doprint(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) doprint(i,v)
end
上述实例就输出的结果为
1 TOM
2 JERRY
3 ROWS
4 LUCY
x JACK
4.2.ngx_lua 模块概念
淘宝开发的 ngx_lua
模块通过将 Lua 解释器集成进 Nginx,可以采用 Lua 脚本实现业务逻辑,由于 Lua 的紧凑、快速以及内建协程,所以在保证高并发服务能力的同时极大地降低了业务逻辑实现成本。
4.3.ngx_lua 模块环境准备
4.3.1.方式一:lua-nginx-module
(1)LuaJIT 是采用 C 语言编写的 Lua 代表的解释器,官网地址为:http://luajit.org/
- 在官网上找到对应的下载地址为 http://luajit.org/download/LuaJIT-2.0.5.tar.gz。
- 在 CentOS 上使用
wget
来下载:wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
- 将下载的资源进行解压:
tar -zxf LuaJIT-2.0.5.tar.gz
- 进入解压的目录:
cd LuaJIT-2.0.5
- 执行编译和安装:
make && make install
(2)下载 lua-nginx-module
- 下载地址:https://github.com/openresty/lua-nginx-module/archive/v0.10.16rc4.tar.gz
- 在 CentOS 上使用
wget
来下载:wget https://github.com/openresty/lua-nginx-module/archive/v0.10.16rc4.tar.gz
- 将下载的资源进行解压:
tar -zxf lua-nginx-module-0.10.16rc4.tar.gz
- 更改目录名:
mv lua-nginx-module-0.10.16rc4 lua-nginx-module
- 导入环境变量,告诉 Nginx 去哪里找 luajit:
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0
- 进入 Nginx 的目录执行如下命令:
./configure --prefix=/usr/local/nginx --add-module=../lua-nginx-module
make && make install
(3)注意事项:如果启动 Nginx 出现如下错误
(4)解决方案:设置软链接,使用如下命令
ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2
(5)如果启动 Nginx 出现以下错误信息:
分析原因:因为 lua-nginx-module
是来自 OpenRestry,错误中提示的 resty.core 是 OpenRestry 的核心模块,对其下的很多函数进行了优化等工作。以前的版本默认不会把该模块编译进去,所以需要使用的话,我们得手动安装,或者禁用就可以。但是最新的 lua-nginx-module
模块已经强制性安装了该模块,所以此处因为缺少 resty 模块导致的报错信息。
解决方案有两个:一种是下载对应的模块,另一种则是禁用掉 restry 模块,禁用的方式为:
http{lua_load_resty_core off;
}
测试:在 nginx.conf
下配置如下内容
location /lua{default_type 'text/html';content_by_lua 'ngx.say("<h1>HELLO,LUA</h1>")';
}
配置成功后,启动 Nginx,通过浏览器进行访问,如果获取到如下结果,则证明安装成功。
4.3.2.方式二:OpenRestry
4.3.2.1.概述
前面我们提到过,OpenResty 是由淘宝工程师开发的,所以其官方网站 (http://openresty.org/) 我们读起来是非常的方便。OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。所以本身 OpenResty 内部就已经集成了 Nginx 和Lua,我们使用起来会更加方便。
4.3.2.2.安装
- 下载 OpenResty:https://openresty.org/download/openresty-1.15.8.2.tar.gz
- 使用 wget 下载:
wget https://openresty.org/download/openresty-1.15.8.2.tar.gz
- 解压缩:
tar -zxf openresty-1.15.8.2.tar.gz
- 进入 OpenResty 目录:
cd openresty-1.15.8.2
- 执行命令:
./configure
- 执行命令:
make && make install
- 进入 OpenResty 的目录,找到 nginx:
cd /usr/local/openresty/nginx/
- 在 conf 目录下的 nginx.conf 添加如下内容:
location /lua{default_type 'text/html';content_by_lua 'ngx.say("<h1>HELLO,OpenRestry</h1>")';
}
- 在 sbin 目录下启动 nginx
- 通过浏览器访问测试:
4.4.ngx_lua 的使用
(1)使用 Lua 编写 Nginx 脚本的基本构建块是指令。指令用于指定何时运行用户 Lua 代码以及如何使用结果。下图显示了执行指令的顺序:
先来解释下 * 的作用
- *:无 , 即 xxx_by_lua,指令后面跟的是 lua 指令
- *:_file,即 xxx_by_lua_file 指令后面跟的是 lua 文件
- *:_block,即 xxx_by_lua_block 在 0.9.17 版后替换 init_by_lua_file
(2)下面来介绍上图中常用指令的作用:
init_by_lua*
:该指令在每次 Nginx 重新加载配置时执行,可以用来完成一些耗时模块的加载,或者初始化一些全局配置init_worker_by_lua*
:该指令用于启动一些定时任务,如心跳检查、定时拉取服务器配置等set_by_lua*
:该指令只要用来做变量赋值,这个指令一次只能返回一个值,并将结果赋值给 Nginx 中指定的变量rewrite_by_lua*
:该指令用于执行内部 URL 重写或者外部重定向,典型的如伪静态化 URL 重写,本阶段在 rewrite 处理阶段的最后默认执行access_by_lua*
:该指令用于访问控制。例如,如果只允许内网IP访问content_by_lua*
:该指令是应用最多的指令,大部分任务是在这个阶段完成的,其他的过程往往为这个阶段准备数据,正式处理基本都在本阶段header_filter_by_lua*
:该指令用于设置应答消息的头部信息body_filter_by_lua*
:该指令是对响应数据进行过滤,如截断、替换log_by_lua*
:该指令用于在 log 请求处理阶段,用 Lua 代码处理日志,但并不替换原有 log 处理balancer_by_lua*
:该指令主要的作用是用来实现上游服务器的负载均衡器算法ssl_certificate_by_*
:该指令作用在 Nginx 和下游服务开始一个 SSL 握手操作时将允许本配置项的 Lua 代码
(3)下面将是要哪个上述指令实现如下的需求:
- 发送请求
http://192.168.200.133?name=张三&gender=1
- Nginx 接收到请求后,根据 gender 传入的值来展示不同的内容:
- 如果 gender 传入的是 1,则在页面上展示张三先生
- 如果 gender 传入的是 0,则在页面上展示张三女士
- 如果 gender 未传或者传入的不是 1 或 2,则在页面上展示张三
(4)实现代码:
location /getByGender {default_type 'text/html';set_by_lua $name "local uri_args = ngx.req.get_uri_args()gender = uri_args['gender']name = uri_args['name']if gender=='1' thenreturn name..'先生'elseif gender=='0' thenreturn name..'女士'elsereturn nameend";header_filter_by_lua "ngx.header.aaa='bbb'";return 200 $name;
}
4.5.ngx_lua 操作 Redis
(1)Redis 在系统中经常作为数据缓存、内存数据库使用,在大型系统中扮演着非常重要的作用。在 Nginx 核心系统中,Redis 是常备组件。Nginx 支持 3 种方法访问 Redis,分别是 HttpRedis 模块、HttpRedis2Module、lua-resty-redis 库。这三种方式中 HttpRedis 模块提供的指令少,功能单一,适合做简单缓存,HttpRedis2Module 模块比 HttpRedis 模块操作更灵活,功能更强大。而 lua-resty-redis 库是 OpenResty 提供的一个操作 Redis 的接口库,可根据自己的业务情况做一些逻辑处理,适合做复杂的业务逻辑。所以这里将主要以 lua-resty-redis 来进行介绍。
(2)lua-resty-redis 环境准备
1)步骤一:准备一个 Redis 环境
# 连接地址
host = 192.168.200.111
port = 6379
2)步骤二:准备对应的 API
lua-resty-redis 提供了访问 Redis 的详细 API,包括创建对接、连接、操作、数据处理等。这些 API 基本上与 Redis 的操作一一对应。
(1)redis = require "resty.redis"
(2)new语法: redis,err = redis:new(),创建一个Redis对象。
(3)connect语法:ok,err=redis:connect(host,port[,options_table]),设置连接 Redis 的连接信息。ok:连接成功返回 1,连接失败返回nilerr:返回对应的错误信息
(4)set_timeout语法: redis:set_timeout(time) ,设置请求操作 Redis 的超时时间。
(5)close语法: ok,err = redis:close(),关闭当前连接,成功返回 1,失败返回 nil 和错误信息
(6)redis 命令对应的方法在lua-resty-redis中,所有的 Redis 命令都有自己的方法,方法名字和命令名字相同,只是全部为小写。
步骤三:效果实现
location / {default_type "text/html";content_by_lua_block{local redis = require "resty.redis" -- 引入Redislocal redisObj = redis:new() --创建 Redis 对象redisObj:set_timeout(1000) --设置超时数据为1slocal ok,err = redisObj:connect("192.168.200.1",6379) --设置 Redis 连接信息if not ok then --判断是否连接成功ngx.say("failed to connection redis",err)returnendok,err = redisObj:set("username","TOM") --存入数据if not ok then --判断是否存入成功ngx.say("failed to set username",err)returnendlocal res,err = redisObj:get("username") --从 Redis 中获取数据ngx.say(res) --将数据写回消息体中redisObj:close()}
}
步骤四:运行测试效果
4.6.ngx_lua 操作 MySQL
4.6.1.概述
MySQL 是一个使用广泛的关系型数据库。在 ngx_lua 中,MySQL 有两种访问模式,分别是:
- 用 ngx_lua 模块和 lua-resty-mysql 模块:这两个模块是安装 OpenResty 时默认安装的。
- 使用 drizzle_nginx_module(HttpDrizzleModule) 模块:需要单独安装,这个库现不在 OpenResty 中。
4.6.2.lua-resty-mysql
(1)lua-resty-mysql 是 OpenResty 开发的模块,使用灵活、功能强大,适合复杂的业务场景,同时支持存储过程的访问。
(2)使用 lua-resty-mysql 实现数据库的查询
1)步骤一:
- 准备 MySQL
host: 192.168.200.111
port: 3306
username: root
password: 123456
- 创建一个数据库表及表中的数据。
create database nginx_db;use nginx_db;create table users(id int primary key auto_increment,username varchar(30),birthday date,salary double
);insert into users(id,username,birthday,salary) values(null,"TOM","1988-11-11",10000.0);
insert into users(id,username,birthday,salary) values(null,"JERRY","1989-11-11",20000.0);
insert into users(id,username,birthday,salary) values(null,"ROWS","1990-11-11",30000.0);
insert into users(id,username,birthday,salary) values(null,"LUCY","1991-11-11",40000.0);
insert into users(id,username,birthday,salary) values(null,"JACK","1992-11-11",50000.0);
- 数据库连接四要素:
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.200.111:3306/nginx_db
username=root
password=123456
2)步骤二:API 学习
(1)引入"resty.mysql"模块local mysql = require "resty.mysql"
(2)new创建一个MySQL连接对象,遇到错误时,db为nil,err为错误描述信息语法: db,err = mysql:new()
(3)connect尝试连接到一个MySQL服务器语法:ok,err=db:connect(options),options是一个参数的Lua表结构,里面包含数据库连接的相关信息host:服务器主机名或IP地址port:服务器监听端口,默认为3306user:登录的用户名password:登录密码database:使用的数据库名
(4)set_timeout设置子请求的超时时间(ms),包括connect方法语法:db:set_timeout(time)
(5)close关闭当前 MySQL 连接并返回状态。如果成功,则返回1;如果出现任何错误,则将返回nil和错误描述。语法:db:close()
(6)send_query异步向远程MySQL发送一个查询。如果成功则返回成功发送的字节数;如果错误,则返回nil和错误描述语法:bytes,err=db:send_query(sql)
(7)read_result从MySQL服务器返回结果中读取一行数据。res返回一个描述OK包或结果集包的Lua表,语法:res, err, errcode, sqlstate = db:read_result() res, err, errcode, sqlstate = db:read_result(rows) :rows指定返回结果集的最大值,默认为4如果是查询,则返回一个容纳多行的数组。每行是一个数据列的key-value对,如{{id=1,username="TOM",birthday="1988-11-11",salary=10000.0},{id=2,username="JERRY",birthday="1989-11-11",salary=20000.0}}如果是增删改,则返回类上如下数据{insert_id = 0,server_status=2,warning_count=1,affected_rows=2,message=nil}返回值:res: 操作的结果集err: 错误信息errcode: MySQL的错误码,比如 1064sqlstate: 返回由 5 个字符组成的标准 SQL 错误码,比如 42000
3)步骤三:效果实现
location /{content_by_lua_block{local mysql = require "resty.mysql"local db = mysql:new()local ok,err = db:connect{host="192.168.200.111",port=3306,user="root",password="123456",database="nginx_db"}db:set_timeout(1000)db:send_query("select * from users where id =1")local res,err,errcode,sqlstate = db:read_result()ngx.say(res[1].id..","..res[1].username..","..res[1].birthday..","..res[1].salary)db:close()}}
问题:
- 如何获取返回数据的内容
- 如何实现查询多条数据
- 如何实现数据库的增删改操作
4.6.2.1.使用 lua-cjson 处理查询结果
(1)通过上述的案例学习,read_result() 得到的结果都是 table 类型,要想在页面上展示,就必须知道table的具体数据结构才能进行遍历获取。处理起来比较麻烦,接下来我们介绍一种简单方式 cjson,使用它就可以将 table 类型的数据转换成 json 字符串,把 json 字符串展示在页面上即可。
(2)具体使用如下:
- 步骤一:引入 cjson
local cjson = require "cjson"
- 步骤二:调用 cjson 的 encode 方法进行类型转换
cjson.encode(res)
- 步骤三:
location /{content_by_lua_block{local mysql = require "resty.mysql"local cjson = require "cjson"local db = mysql:new()local ok,err = db:connect{host="192.168.200.111",port=3306,user="root",password="123456",database="nginx_db"}db:set_timeout(1000)--db:send_query("select * from users where id = 2")db:send_query("select * from users")local res,err,errcode,sqlstate = db:read_result()ngx.say(cjson.encode(res))for i,v in ipairs(res) dongx.say(v.id..","..v.username..","..v.birthday..","..v.salary)enddb:close()}
}
4.6.2.2.lua-resty-mysql 实现数据库的增删改
(1)优化 send_query
和 read_result
,本方法是 send_query 和 read_result 组合的快捷方法。语法如下:
res, err, errcode, sqlstate = db:query(sql[,rows])
有了该 API,上面的代码我们就可以进行对应的优化,如下:
location /{content_by_lua_block{local mysql = require "resty.mysql"local db = mysql:new()local ok,err = db:connect{host="192.168.200.1",port=3306,user="root",password="123456",database="nginx_db",max_packet_size=1024,compact_arrays=false}db:set_timeout(1000)local res,err,errcode,sqlstate = db:query("select * from users")--local res,err,errcode,sqlstate = db:query("insert into users(id,username,birthday,salary) values(null,'zhangsan','2020-11-11',32222.0)")--local res,err,errcode,sqlstate = db:query("update users set username='lisi' where id = 6")--local res,err,errcode,sqlstate = db:query("delete from users where id = 6")db:close()}}
4.7.综合小案例
(1)使用 ngx_lua 模块完成 Redis 缓存预热。
(2)分析:
- 先得有一张表 users
- 浏览器输入如下地址:
http://191.168.200.133?username=TOM
- 从表中查询出符合条件的记录,此时获取的结果为table类型
- 使用 cjson 将 table 数据转换成 json 字符串
- 将查询的结果数据存入 Redis 中
(3)具体实现如下:
init_by_lua_block{redis = require "resty.redis"mysql = require "resty.mysql"cjson = require "cjson"
}
location /{default_type "text/html";content_by_lua_block{--获取请求的参数usernamelocal 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 thenngx.say("failed connect to mysql:",err)returnend--设置连接超时时间db:set_timeout(1000)--查询数据local sql = "";if not param thensql="select * from users"elsesql="select * from users where username=".."'"..param.."'"endlocal res,err,errcode,sqlstate=db:query(sql)if not res thenngx.say("failed to query from mysql:",err)returnend--连接redislocal rd = redis:new()ok,err = rd:connect("192.168.200.111",6379)if not ok thenngx.say("failed to connect to redis:",err)returnendrd:set_timeout(1000)--循环遍历数据for i,v in ipairs(res) dord:set("user_"..v.username,cjson.encode(v))endngx.say("success")rd:close()db:close()}}