nginx05-服务器集群
Nginx实现服务器端集群搭建
Nginx与Tomcat部署
前面文章已经将Nginx的大部分内容进行了讲解,我们都知道了Nginx在高并发场景和处理静态资源是非常高性能的,但是在实际项目中除了静态资源还有就是后台业务代码模块,一般后台业务都会被部署在Tomcat,weblogic或者是websphere等web服务器上。那么如何使用Nginx接收用户的请求并把请求转发到后台web服务器?
步骤分析:
1 | 1.准备Tomcat环境,并在Tomcat上部署一个web项目 |
环境准备(Tomcat)
浏览器访问:
1 | http://192.168.200.146:8080/demo/index.html |
获取动态资源的链接地址:
1 | http://192.168.200.146:8080/demo/getAddress |
本次博文将采用Tomcat作为后台web服务器
(1)在Centos上准备一个Tomcat
1 | 1.Tomcat官网地址:https://tomcat.apache.org/ |
(2)准备一个web项目,将其打包为war
1 | 1.将资料中的demo.war上传到tomcat8目录下的webapps包下 |
(3)启动tomcat进行访问测试。
1 | 静态资源: http://192.168.200.146:8080/demo/index.html |
环境准备(Nginx)
(1)使用Nginx的反向代理,将请求转给Tomcat进行处理。
1 | upstream webservice { |
(2)启动访问测试
学习到这,可能大家会有一个困惑,明明直接通过tomcat就能访问,为什么还需要多加一个nginx,这样不是反而是系统的复杂度变高了么?
那接下来我们从两个方便给大家分析下这个问题,
第一个使用Nginx实现动静分离
第二个使用Nginx搭建Tomcat的集群
Nginx实现动静分离
什么是动静分离?
动:后台应用程序的业务处理
静:网站的静态资源(html,javaScript,css,images等文件)
分离:将两者进行分开部署访问,提供用户进行访问。举例说明就是以后所有和静态资源相关的内容都交给Nginx来部署访问,非静态内容则交个类似于Tomcat的服务器来部署访问。
为什么要动静分离?
前面我们介绍过Nginx在处理静态资源的时候,效率是非常高的,而且Nginx的并发访问量也是名列前茅,而Tomcat则相对比较弱一些,所以把静态资源交个Nginx后,可以减轻Tomcat服务器的访问压力并提高静态资源的访问速度。
动静分离以后,降低了动态资源和静态资源的耦合度。如动态资源宕机了也不影响静态资源的展示。
如何实现动静分离?
实现动静分离的方式很多,比如静态资源可以部署到CDN、Nginx等服务器上,动态资源可以部署到Tomcat,weblogic或者websphere上。本次博文只要使用Nginx+Tomcat来实现动静分离。
需求分析
动静分离实现步骤
1.将demo.war项目中的静态资源都删除掉,重新打包生成一个war包,在资料中有提供。
2.将war包部署到tomcat中,把之前部署的内容删除掉
1 | 进入到tomcat的webapps目录下,将之前的内容删除掉 |
3.在Nginx所在服务器创建如下目录,并将对应的静态资源放入指定的位置
其中index.html页面的内容如下:
1 |
|
4.配置Nginx的静态资源与动态资源的访问
1 | upstream webservice{ |
5.启动测试,访问http://192.168.200.133/index.html
假如某个时间点,由于某个原因导致Tomcat后的服务器宕机了,我们再次访问Nginx,会得到如下效果,用户还是能看到页面,只是缺失了访问次数的统计,这就是前后端耦合度降低的效果,并且整个请求只和后的服务器交互了一次,js和images都直接从Nginx返回,提供了效率,降低了后的服务器的压力。
Nginx实现Tomcat集群搭建
在使用Nginx和Tomcat部署项目的时候,我们使用的是一台Nginx服务器和一台Tomcat服务器,效果图如下:
那么问题来了,如果Tomcat的真的宕机了,整个系统就会不完整,所以如何解决上述问题,一台服务器容易宕机,那就多搭建几台Tomcat服务器,这样的话就提升了后的服务器的可用性。这也就是我们常说的集群,搭建Tomcat的集群需要用到了Nginx的反向代理和赋值均衡的知识,具体如何来实现?我们先来分析下原理
环境准备:
(1)准备3台tomcat,使用端口进行区分[实际环境应该是三台服务器],修改server.ml,将端口修改分别修改为8080,8180,8280
(2)启动tomcat并访问测试,
1 | http://192.168.200.146:8080/demo/getAddress |
1 | http://192.168.200.146:8180/demo/getAddress |
1 | http://192.168.200.146:8280/demo/getAddress |
(3)在Nginx对应的配置文件中添加如下内容:
1 | upstream webservice{ |
好了,完成了上述环境的部署,我们已经解决了Tomcat的高可用性,一台服务器宕机,还有其他两条对外提供服务,同时也可以实现后台服务器的不间断更新。但是新问题出现了,上述环境中,如果是Nginx宕机了呢,那么整套系统都将服务对外提供服务了,这个如何解决?
Nginx高可用解决方案
针对于上面提到的问题,我们来分析下要想解决上述问题,需要面临哪些问题?
1 | 需要两台以上的Nginx服务器对外提供服务,这样的话就可以解决其中一台宕机了,另外一台还能对外提供服务,但是如果是两台Nginx服务器的话,会有两个IP地址,用户该访问哪台服务器,用户怎么知道哪台是好的,哪台是宕机了的? |
Keepalived
使用Keepalived来解决,Keepalived 软件由 C 编写的,最初是专为 LVS 负载均衡软件设计的,Keepalived 软件主要是通过 VRRP 协议实现高可用功能。
VRRP介绍
VRRP(Virtual Route Redundancy Protocol)协议,翻译过来为虚拟路由冗余协议。VRRP协议将两台或多台路由器设备虚拟成一个设备,对外提供虚拟路由器IP,而在路由器组内部,如果实际拥有这个对外IP的路由器如果工作正常的话就是MASTER,MASTER实现针对虚拟路由器IP的各种网络功能。其他设备不拥有该虚拟IP,状态为BACKUP,处了接收MASTER的VRRP状态通告信息以外,不执行对外的网络功能。当主机失效时,BACKUP将接管原先MASTER的网络功能。
从上面的介绍信息获取到的内容就是VRRP是一种协议,那这个协议是用来干什么的?
1.选择协议
1 | VRRP可以把一个虚拟路由器的责任动态分配到局域网上的 VRRP 路由器中的一台。其中的虚拟路由即Virtual路由是由VRRP路由群组创建的一个不真实存在的路由,这个虚拟路由也是有对应的IP地址。而且VRRP路由1和VRRP路由2之间会有竞争选择,通过选择会产生一个Master路由和一个Backup路由。 |
2.路由容错协议
1 | Master路由和Backup路由之间会有一个心跳检测,Master会定时告知Backup自己的状态,如果在指定的时间内,Backup没有接收到这个通知内容,Backup就会替代Master成为新的Master。Master路由有一个特权就是虚拟路由和后端服务器都是通过Master进行数据传递交互的,而备份节点则会直接丢弃这些请求和数据,不做处理,只是去监听Master的状态 |
用了Keepalived后,解决方案如下:
环境搭建
环境准备
VIP | IP | 主机名 | 主/从 |
---|---|---|---|
192.168.200.133 | keepalived1 | Master | |
192.168.200.222 | |||
192.168.200.122 | keepalived2 | Backup |
keepalived的安装
1 | 步骤1:从官方网站下载keepalived,官网地址https://keepalived.org/ |
安装完成后,有两个文件需要我们认识下,一个是 /etc/keepalived/keepalived.conf
(keepalived的系统配置文件,我们主要操作的就是该文件),一个是/usr/local/sbin目录下的keepalived
,是系统配置脚本,用来启动和关闭keepalived
Keepalived配置文件介绍
打开keepalived.conf配置文件
这里面会分三部,第一部分是global全局配置、第二部分是vrrp相关配置、第三部分是LVS相关配置。
本次文章主要是使用keepalived实现高可用部署,没有用到LVS,所以我们重点关注的是前两部分
1 | global全局部分: |
1 | VRRP部分,该部分可以包含以下四个子模块 |
配置内容如下:
服务器1
1 | global_defs { |
服务器2
1 | ! Configuration File for keepalived |
访问测试
- 启动keepalived之前,咱们先使用命令
ip a
,查看192.168.200.133和192.168.200.122这两台服务器的IP情况。
- 分别启动两台服务器的keepalived
1 | cd /usr/local/sbin |
再次通过 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也会”漂移”过去,然后我们再次通过浏览器访问:
1 | http://192.168.200.222/ |
如果把192.168.200.133服务器的keepalived关闭掉,再次访问相同的地址
效果实现了以后, 我们会发现要想让vip进行切换,就必须要把服务器上的keepalived进行关闭,而什么时候关闭keepalived呢?应该是在keepalived所在服务器的nginx出现问题后,把keepalived关闭掉,就可以让VIP执行另外一台服务器,但是现在这所有的操作都是通过手动来完成的,我们如何能让系统自动判断当前服务器的nginx是否正确启动,如果没有,要能让VIP自动进行”漂移”,这个问题该如何解决?
keepalived之vrrp_script
keepalived只能做到对网络故障和keepalived本身的监控,即当出现网络故障或者keepalived本身出现问题时,进行切换。但是这些还不够,我们还需要监控keepalived所在服务器上的其他业务,比如Nginx,如果Nginx出现异常了,仅仅keepalived保持正常,是无法完成系统的正常工作的,因此需要根据业务进程的运行状态决定是否需要进行主备切换,这个时候,我们可以通过编写脚本对业务进程进行检测监控。
实现步骤:
- 在keepalived配置文件中添加对应的配置像
1 | vrrp_script 脚本名称 |
- 编写脚本
ck_nginx.sh
1 | #!/bin/bash |
Linux ps命令用于显示当前进程 (process) 的状态。
-C(command) :指定命令的所有进程
–no-header 排除标题
- 为脚本文件设置权限
1 | chmod 755 ck_nginx.sh |
- 将脚本添加到
1 | vrrp_script ck_nginx { |
- 如果效果没有出来,可以使用
tail -f /var/log/messages
查看日志信息,找对应的错误信息。 - 测试
问题思考:
通常如果master服务死掉后backup会变成master,但是当master服务又好了的时候 master此时会抢占VIP,这样就会发生两次切换对业务繁忙的网站来说是不好的。所以我们要在配置文件加入 nopreempt 非抢占,但是这个参数只能用于state 为backup,故我们在用HA的时候最好master 和backup的state都设置成backup 让其通过priority来竞争。
Nginx制作下载站点
首先我们先要清楚什么是下载站点?
我们先来看一个网站http://nginx.org/download/
这个我们刚开始学习Nginx的时候给大家看过这样的网站,该网站主要就是用来提供用户来下载相关资源的网站,就叫做下载网站。
如何制作一个下载站点:
nginx使用的是模块ngx_http_autoindex_module来实现的,该模块处理以斜杠(“/“)结尾的请求,并生成目录列表。
nginx编译的时候会自动加载该模块,但是该模块默认是关闭的,我们需要使用下来指令来完成对应的配置
(1)autoindex:启用或禁用目录列表输出
语法 | autoindex on|off; |
---|---|
默认值 | autoindex off; |
位置 | http、server、location |
(2)autoindex_exact_size:对应HTLM格式,指定是否在目录列表展示文件的详细大小
默认为on,显示出文件的确切大小,单位是bytes。
改为off后,显示出文件的大概大小,单位是kB或者MB或者GB
语法 | autoindex_exact_size on|off; |
---|---|
默认值 | autoindex_exact_size on; |
位置 | http、server、location |
(3)autoindex_format:设置目录列表的格式
语法 | autoindex_format html|xml|json|jsonp; |
---|---|
默认值 | autoindex_format html; |
位置 | http、server、location |
注意:该指令在1.7.9及以后版本中出现
(4)autoindex_localtime:对应HTML格式,是否在目录列表上显示时间。
默认为off,显示的文件时间为GMT时间。
改为on后,显示的文件时间为文件的服务器时间
语法 | autoindex_localtime on | off; |
---|---|
默认值 | autoindex_localtime off; |
位置 | http、server、location |
配置方式如下:
1 | location /download{ |
XML/JSON格式[一般不用这两种方式]
Nginx的用户认证模块
对应系统资源的访问,我们往往需要限制谁能访问,谁不能访问。这块就是我们通常所说的认证部分,认证需要做的就是根据用户输入的用户名和密码来判定用户是否为合法用户,如果是则放行访问,如果不是则拒绝访问。
Nginx对应用户认证这块是通过ngx_http_auth_basic_module模块来实现的,它允许通过使用”HTTP基本身份验证”协议验证用户名和密码来限制对资源的访问。默认情况下nginx是已经安装了该模块,如果不需要则使用–without-http_auth_basic_module。
该模块的指令比较简单,
(1)auth_basic:使用“ HTTP基本认证”协议启用用户名和密码的验证
语法 | auth_basic string|off; |
---|---|
默认值 | auth_basic off; |
位置 | http,server,location,limit_except |
开启后,服务端会返回401,指定的字符串会返回到客户端,给用户以提示信息,但是不同的浏览器对内容的展示不一致。
(2)auth_basic_user_file:指定用户名和密码所在文件
语法 | auth_basic_user_file file; |
---|---|
默认值 | — |
位置 | http,server,location,limit_except |
指定文件路径,该文件中的用户名和密码的设置,密码需要进行加密。可以采用工具自动生成
实现步骤:
1.nginx.conf添加如下内容
1 | location /download{ |
2.我们需要使用htpasswd
工具生成
1 | yum install -y httpd-tools |
1 | htpasswd -c /usr/local/nginx/conf/htpasswd username //创建一个新文件记录用户名和密码 |
上述方式虽然能实现用户名和密码的验证,但是大家也看到了,所有的用户名和密码信息都记录在文件里面,如果用户量过大的话,这种方式就显得有点麻烦了,这时候我们就得通过后台业务代码来进行用户权限的校验了。
Nginx的扩展模块
Nginx是可扩展的,可用于处理各种使用场景。本节中,我们将探讨使用Lua扩展Nginx的功能。
Lua
概念
Lua是一种轻量、小巧的脚本语言,用标准C语言编写并以源代码形式开发。设计的目的是为了嵌入到其他应用程序中,从而为应用程序提供灵活的扩展和定制功能。
特性
跟其他语言进行比较,Lua有其自身的特点:
(1)轻量级
1 | Lua用标准C语言编写并以源代码形式开发,编译后仅仅一百余千字节,可以很方便的嵌入到其他程序中。 |
(2)可扩展
1 | Lua提供非常丰富易于使用的扩展接口和机制,由宿主语言(通常是C或C++)提供功能,Lua可以使用它们,就像内置的功能一样。 |
(3)支持面向过程编程和函数式编程
应用场景
Lua在不同的系统中得到大量应用,场景的应用场景如下:
游戏开发、独立应用脚本、web应用脚本、扩展和数据库插件、系统安全上。
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命令直接下载:
1 | wget https://www.lua.org/ftp/lua-5.4.1.tar.gz |
- 编译安装
1 | cd lua-5.4.1 |
如果在执行make linux test失败,报如下错误:
说明当前系统缺少libreadline-dev依赖包,需要通过命令来进行安装
1 | yum install -y readline-devel |
验证是否安装成功
1 | lua -v |
Lua的语法
Lua和C/C++语法非常相似,整体上比较清晰,简洁。条件语句、循环语句、函数调用都与C/C++基本一致。如果对C/C++不太熟悉的同学来说,也没关系,因为天下语言是一家,基本上理解起来都不会太困难。我们一点点来讲。
第一个Lua程序
大家需要知道的是,Lua有两种交互方式,分别是:交互式和脚本式,这两者的区别,下面我们分别来讲解下:
交互式之HELLOWORLD
1 | 交互式是指可以在命令行输入程序,然后回车就可以看到运行的效果。 |
Lua交互式编程模式可以通过命令lua -i 或lua来启用:
在命令行中key输入如下命令,并按回车,会有输出在控制台:
脚本式之HELLOWORLD
脚本式是将代码保存到一个以lua为扩展名的文件中并执行的方式。
方式一:
我们需要一个文件名为 hello.lua,在文件中添加要执行的代码,然后通过命令 lua hello.lua
来执行,会在控制台输出对应的结果。
hello.lua
1 | print("Hello World!!") |
方式二:
将hello.lua做如下修改
1 | #!/usr/local/bin/lua |
第一行用来指定Lua解释器所在位置为 /usr/local/bin/lua,加上#号标记解释器会忽略它。一般情况下#!就是用来指定用哪个程序来运行本文件。但是hello.lua并不是一个可执行文件,需要通过chmod来设置可执行权限,最简单的方式为:
1 | chmod 755 hello.lua |
然后执行该文件
1 | ./hello.lua |
补充一点,如果想在交互式中运行脚本式的hello.lua中的内容,我们可以使用一个dofile函数,如:
1 | dofile("lua_demo/hello.lua") |
注意:在Lua语言中,连续语句之间的分隔符并不是必须的,也就是说后面不需要加分号,当然加上也不会报错,
在Lua语言中,表达式之间的换行也起不到任何作用。如以下四个写法,其实都是等效的
1 | 写法一 |
不建议使用第四种方式,可读性太差。
Lua的注释
关于Lua的注释要分两种,第一种是单行注释,第二种是多行注释。
单行注释的语法为:
1 | --注释内容 |
多行注释的语法为:
1 | --[[ |
如果想取消多行注释,只需要在第一个–之前在加一个-即可,如:
1 | ---[[ |
标识符
换句话说标识符就是我们的变量名,Lua定义变量名以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上0个或多个字母,下划线,数字(0到9)。这块建议大家最好不要使用下划线加大写字母的标识符,因为Lua的保留字也是这样定义的,容易发生冲突。注意Lua是区分大小写字母的。
A0
关键字
下列是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 内部全局变量。这个也是上面我们不建议这么定义标识符的原因。
运算符
Lua中支持的运算符有算术运算符、关系运算符、逻辑运算符、其他运算符。
算术运算符:
1 | + 加法 |
例如:
1 | 10+20 -->30 |
关系运算符
1 | == 等于 |
例如:
1 | 10==10 -->true |
逻辑运算符
1 | and 逻辑与 A and B && |
逻辑运算符可以作为if的判断条件,返回的结果如下:
1 | A = true |
其他运算符
1 | .. 连接两个字符串 |
例如:
1 | > "HELLO ".."WORLD" -->HELLO WORLD |
全局变量&局部变量
在Lua语言中,全局变量无须声明即可使用。在默认情况下,变量总是认为是全局的,如果未提前赋值,默认为nil:
要想声明一个局部变量,需要使用local来声明
Lua数据类型
Lua有8个数据类型
1 | nil(空,无效值) |
可以使用type函数测试给定变量或者的类型:
1 | print(type(nil)) -->nil |
nil
nil是一种只有一个nil值的类型,它的作用可以用来与其他所有值进行区分,也可以当想要移除一个变量时,只需要将该变量名赋值为nil,垃圾回收就会会释放该变量所占用的内存。
boolean
boolean类型具有两个值,true和false。boolean类型一般被用来做条件判断的真与假。在Lua语言中,只会将false和nil视为假,其他的都视为真,特别是在条件检测中0和空字符串都会认为是真,这个和我们熟悉的大多数语言不太一样。
number
在Lua5.3版本开始,Lua语言为数值格式提供了两种选择:integer(整型)和float双精度浮点型。
数值常量的表示方式:
1 | >4 -->4 |
不管是整型还是双精度浮点型,使用type()函数来取其类型,都会返回的是number
1 | >type(3) -->number |
所以它们之间是可以相互转换的,同时,具有相同算术值的整型值和浮点型值在Lua语言中是相等的
string
Lua语言中的字符串即可以表示单个字符,也可以表示一整本书籍。在Lua语言中,操作100K或者1M个字母组成的字符串的程序很常见。
可以使用单引号或双引号来声明字符串
1 | >a = "hello" |
如果声明的字符串比较长或者有多行,则可以使用如下方式进行声明
1 | html = [[ |
table
table是Lua语言中最主要和强大的数据结构。使用表, Lua 语言可以以一种简单、统一且高效的方式表示数组、集合、记录和其他很多数据结构。 Lua语言中的表本质上是一种辅助数组。这种数组比Java中的数组更加灵活,可以使用数值做索引,也可以使用字符串或其他任意类型的值作索引(除nil外)。
创建表的最简单方式:
1 | > a = {} |
创建数组:
我们都知道数组就是相同数据类型的元素按照一定顺序排列的集合,那么使用table如何创建一个数组呢?
1 | >arr = {"TOM","JERRY","ROSE"} |
要想获取数组中的值,我们可以通过如下内容来获取:
1 | print(arr[0]) nil |
从上面的结果可以看出来,数组的下标默认是从1开始的。所以上述创建数组,也可以通过如下方式来创建
1 | >arr = {} |
上面我们说过了,表的索引即可以是数字,也可以是字符串等其他的内容,所以我们也可以将索引更改为字符串来创建
1 | >arr = {} |
当然,如果想要获取这些数组中的值,可以使用下面的方式
1 | 方式一 |
当前table的灵活不进于此,还有更灵活的声明方式
1 | >arr = {"TOM",X=10,"JERRY",Y=20,"ROSE",Z=30} |
如何获取上面的值?
1 | TOM : arr[1] |
function
在 Lua语言中,函数( Function )是对语句和表达式进行抽象的主要方式。
定义函数的语法为:
1 | function functionName(params) |
函数被调用的时候,传入的参数个数与定义函数时使用的参数个数不一致的时候,Lua 语言会通过 抛弃多余参数和将不足的参数设为 nil 的方式来调整参数的个数。
1 | function f(a,b) |
可变长参数函数
1 | function add(...) |
函数返回值可以有多个,这点和Java不太一样
1 | function f(a,b) |
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 部分 是可选的。
1 | function testif(a) |
如果要编写嵌套的 if 语句,可以使用 elseif。 它类似于在 else 后面紧跟一个if。根据传入的年龄返回不同的结果,如
1 | age<=18 青少年, |
while循环
顾名思义,当条件为真时 while 循环会重复执行其循环体。 Lua 语言先测试 while 语句 的条件,若条件为假则循环结束;否则, Lua 会执行循环体并不断地重复这个过程。
语法:
1 | while 条件 do |
例子:实现数组的循环
1 | function testWhile() |
repeat循环
顾名思义, repeat-until语句会重复执行其循环体直到条件为真时结束。 由于条件测试在循环体之后执行,所以循环体至少会执行一次。
语法
1 | repeat |
1 | function testRepeat() |
for循环
数值型for循环
语法
1 | for param=exp1,exp2,exp3 do |
param的值从exp1变化到exp2之前的每次循环会执行 循环体,并在每次循环结束后将步长(step)exp3增加到param上。exp3可选,如果不设置默认为1
1 | for i = 1,100,10 do |
泛型for循环
泛型for循环通过一个迭代器函数来遍历所有值,类似于java中的foreach语句。
语法
1 | for i,v in ipairs(x) do |
i是数组索引值,v是对应索引的数组元素值,ipairs是Lua提供的一个迭代器函数,用来迭代数组,x是要遍历的数组。
例如:
1 | arr = {"TOME","JERRY","ROWS","LUCY"} |
上述实例输出的结果为
1 | 1 TOM |
但是如果将arr的值进行修改为
1 | arr = {"TOME","JERRY","ROWS",x="JACK","LUCY"} |
同样的代码在执行的时候,就只能看到和之前一样的结果,而其中的x为JACK就无法遍历出来,缺失了数据,如果解决呢?
我们可以将迭代器函数变成pairs,如
1 | for i,v in pairs(arr) do |
上述实例就输出的结果为
1 | 1 TOM |
ngx_lua模块概念
淘宝开发的ngx_lua模块通过将lua解释器集成进Nginx,可以采用lua脚本实现业务逻辑,由于lua的紧凑、快速以及内建协程,所以在保证高并发服务能力的同时极大地降低了业务逻辑实现成本。
ngx_lua模块环境准备
方式一:lua-nginx-module
- 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
- 下载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
1 | export LUAJIT_LIB=/usr/local/lib |
进入Nginx的目录执行如下命令:
1 | ./configure --prefix=/usr/local/nginx --add-module=../lua-nginx-module |
注意事项:
(1)如果启动Nginx出现如下错误:
解决方案:
设置软链接,使用如下命令
1 | ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2 |
(2)如果启动Nginx出现以下错误信息
分析原因:因为lua-nginx-module是来自openrestry,错误中提示的resty.core是openrestry的核心模块,对其下的很多函数进行了优化等工作。以前的版本默认不会把该模块编译进去,所以需要使用的话,我们得手动安装,或者禁用就可以。但是最新的lua-nginx-module模块已经强制性安装了该模块,所以此处因为缺少resty模块导致的报错信息。
解决方案有两个:一种是下载对应的模块,另一种则是禁用掉restry模块,禁用的方式为:
1 | http{ |
- 测试
在nginx.conf下配置如下内容:
1 | location /lua{ |
配置成功后,启动nginx,通过浏览器进行访问,如果获取到如下结果,则证明安装成功。
方式二:OpenRestry
概述
前面我们提到过,OpenResty是由淘宝工程师开发的,所以其官方网站(http://openresty.org/)我们读起来是非常的方便。OpenResty是一个基于Nginx与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。所以本身OpenResty内部就已经集成了Nginx和Lua,所以我们使用起来会更加方便。
安装
1 | (1) 下载OpenResty:https://openresty.org/download/openresty-1.15.8.2.tar.gz |
ngx_lua的使用
使用Lua编写Nginx脚本的基本构建块是指令。指令用于指定何时运行用户Lua代码以及如何使用结果。下图显示了执行指令的顺序。
先来解释下*的作用
1 | *:无 , 即 xxx_by_lua ,指令后面跟的是 lua指令 |
init_by_lua*
1 | 该指令在每次Nginx重新加载配置时执行,可以用来完成一些耗时模块的加载,或者初始化一些全局配置。 |
init_worker_by_lua*
1 | 该指令用于启动一些定时任务,如心跳检查、定时拉取服务器配置等。 |
set_by_lua*
1 | 该指令只要用来做变量赋值,这个指令一次只能返回一个值,并将结果赋值给Nginx中指定的变量。 |
rewrite_by_lua*
1 | 该指令用于执行内部URL重写或者外部重定向,典型的如伪静态化URL重写,本阶段在rewrite处理阶段的最后默认执行。 |
access_by_lua*
1 | 该指令用于访问控制。例如,如果只允许内网IP访问。 |
content_by_lua*
1 | 该指令是应用最多的指令,大部分任务是在这个阶段完成的,其他的过程往往为这个阶段准备数据,正式处理基本都在本阶段。 |
header_filter_by_lua*
1 | 该指令用于设置应答消息的头部信息。 |
body_filter_by_lua*
1 | 该指令是对响应数据进行过滤,如截断、替换。 |
log_by_lua*
1 | 该指令用于在log请求处理阶段,用Lua代码处理日志,但并不替换原有log处理。 |
balancer_by_lua*
1 | 该指令主要的作用是用来实现上游服务器的负载均衡器算法 |
ssl_certificate_by_*
1 | 该指令作用在Nginx和下游服务开始一个SSL握手操作时将允许本配置项的Lua代码。 |
需求
1 | http://192.168.200.133?name=张三&gender=1 |
实现代码
1 | location /getByGender { |
ngx_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环境
1 | 连接地址 |
步骤二:准备对应的API
1 | lua-resty-redis提供了访问Redis的详细API,包括创建对接、连接、操作、数据处理等。这些API基本上与Redis的操作一一对应。 |
步骤三:效果实现
1 | location / { |
步骤四:运行测试效果
ngx_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实现数据库的查询
步骤一:
准备MYSQL
1 | host: 192.168.200.111 |
创建一个数据库表及表中的数据。
1 | create database nginx_db; |
数据库连接四要素:
1 | driverClass=com.mysql.jdbc.Driver |
步骤二:API学习
1 | (1)引入"resty.mysql"模块 |
步骤三:效果实现
1 | location /{ |
问题:
1 | 1.如何获取返回数据的内容 |
使用lua-cjson处理查询结果
通过上述的案例学习,read_result()得到的结果res都是table类型,要想在页面上展示,就必须知道table的具体数据结构才能进行遍历获取。处理起来比较麻烦,接下来我们介绍一种简单方式cjson,使用它就可以将table类型的数据转换成json字符串,把json字符串展示在页面上即可。具体如何使用?
步骤一:引入cjson
1 | local cjson = require "cjson" |
步骤二:调用cjson的encode方法进行类型转换
1 | cjson.encode(res) |
步骤三:使用
1 | location /{ |
lua-resty-mysql实现数据库的增删改
优化send_query和read_result
本方法是send_query和read_result组合的快捷方法。
语法:
1 | res, err, errcode, sqlstate = db:query(sql[,rows]) |
有了该API,上面的代码我们就可以进行对应的优化,如下:
1 | location /{ |
综合小案例
使用ngx_lua模块完成Redis缓存预热。
分析:
(1)先得有一张表(users)
(2)浏览器输入如下地址
1 | http://191.168.200.133?username=TOM |
(3)从表中查询出符合条件的记录,此时获取的结果为table类型
(4)使用cjson将table数据转换成json字符串
(5)将查询的结果数据存入Redis中
1 | init_by_lua_block{ |