Nginx+lua+OpenResty高性能实践

Nginx

Nginx是一个高性能的Web服务器和反向代理的软件

Web服务器:就是运行我们wen服务的容器,提供web功能,类似tomcat也提供累死的功能。

代理:就是软件架构和网络设计中,一个重要的概念,有两种代理:正向代理和反向代理。

正向代理

用户设置代理服务器。

所有的请求都由代理服务器发出,无法判断代理了多少用户端,叫正向代理。

image-20240817175155512

反向代理

在服务器端设置代理,素有请求,由服务器端接受,然后再由代理服务器发送到后方的服务器。这么依赖,所有请求,都由一个服务器接受,无法判断代理的多少服务端。这是反向代理、

利用反向代理,就可以即将请求分发到系统内部的多个节点上,从而减少每个节点的并发数。而这些节点在外界看来就是一个系统,表现出唯一的ip,也就是代理服务器的IP.

image-20240817175525249

是用来做负载均衡用的

Nginx安装

官网:nginx

点击documentation然后点击installing nginx

然后打开centos虚拟器

image-20240817180405725

1
yum install yum-utils

image-20240817180454280

安装完毕

然后继续根据官方文档

image-20240817180731632

image-20240817180932465

然后在官方文档的目录下面发现没有nginx.repo的这个文件

1
touch nginx.repo

然后touch一下,新建这个文件

然后

1
2
vi nginx.repo 

开始编写配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

然后继续跟着官方文档下载nginx

1
yum install nginx

image-20240817181434139

这个是nginx的版本

1
nginx-1:1.26.2-1.el8.ngx.x86_64 

Nginx启动及验证

在/usr/sbin目录下输入

1
./nginx

然后查看进程

1
ps -ef | grep nginx

image-20240817182353996

访问一下验证nginx本机是否成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@localhost sbin]# curl localhost:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

通过浏览器访问一下

先查看ip

image-20240817183431505

192.168.1.38:80访问nginx欢迎页

image-20240817183512570

记得关闭防火墙

Nginx常用命令

nginx命令存放在/usr/sbin里面

  • 查看版本号

    1
    nginx -v
  • 关闭nginx

1
./nginx -s stop
  • 启动nginx
1
./nginx
  • 重载配置命令

首先配置文件在哪

image-20240817184132787

找到conf文件

回到/usr/sbin目录下

1
nginx -s reload

更多命令在官网查看

1
https://nginx.org/en/docs/beginners_guide.html

Nginx配置文件介绍

image-20240817191332661

进入conf文件

指令的种类:简单指令,块指令

简单指令:

参数名 参数值 ;

整体分类:

全局块:就是最开始的简单指令。从配置文件开始到events

events块:配置服务器和用户网络连接相关的参数

http块:

包含全局块和server块可以是n个server块

location块也可以是多个

Nginx反向代理-单台机器

目的:

1、在浏览器访问一个地址:

1
www.cpf.com:2080

2、Nginx接受上面的请求

3、转发请求到tomcat上

4、tomcat响应一个页面,页面中有:“tomcat hello”。

步骤:

1、安装Nginx,并启动。

浏览器访问:http:1923.168.1.41:80/得到

image-20240817234525243

2、准备一个tomcat

查看服务器上是否有tomcat

1
ps aux |grep tomcat

安装tomcat

image-20240817235049939

image-20240817235146609

3、在tomcat的webapp里面创建一个index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost apache-tomcat-9.0.90]# cd webapps
[root@localhost webapps]# ll
total 4
drwxr-x---. 16 root root 4096 Jul 4 23:14 docs
drwxr-x---. 7 root root 99 Jul 4 23:14 examples
drwxr-x---. 6 root root 79 Jul 4 23:14 host-manager
drwxr-x---. 6 root root 114 Jul 4 23:14 manager
drwxr-x---. 3 root root 223 Jul 4 23:14 ROOT
[root@localhost webapps]# cd ROOT/
[root@localhost ROOT]# ll
total 164
-rw-r-----. 1 root root 27235 Jun 14 22:45 asf-logo-wide.svg
-rw-r-----. 1 root root 713 Jun 14 22:45 bg-button.png
-rw-r-----. 1 root root 1918 Jun 14 22:45 bg-middle.png
-rw-r-----. 1 root root 1401 Jun 14 22:45 bg-nav.png
-rw-r-----. 1 root root 3103 Jun 14 22:45 bg-upper.png
-rw-r-----. 1 root root 21630 Jun 14 22:45 favicon.ico
-rw-r-----. 1 root root 12234 Jun 14 22:45 index.jsp
-rw-r-----. 1 root root 6901 Jun 14 22:45 RELEASE-NOTES.txt
-rw-r-----. 1 root root 5584 Jun 14 22:45 tomcat.css
-rw-r-----. 1 root root 67795 Jun 14 22:45 tomcat.svg
drwxr-x---. 2 root root 21 Jul 4 23:14 WEB-INF
[root@localhost ROOT]# vi index.html

启动tomcat,并且查看是否能从本地访问

image-20240818000058056

在外面访问一下

image-20240818000200203

也是可以的

重点来了:

1、在浏览器访问一个地址:localhost:2080

2、Nginx接受上面的请求

3、转发请求到tomcat上

4、tomcat响应一个页面,页面中有:“tomcat hello”。

通过nat网络分发,将本机2080转到虚拟机上的nginx的80端口

image-20240818001657239

点击修改虚拟网络设置

然后添加

image-20240818002344829

这样主机访问2080端口就能映射到虚拟机的80端口了

测试一下

VMware做NAT端口转发(全网最详细步骤)_vmware端口转发-CSDN博客

通过nat网络分发将本机2080端口转到虚拟机上的nginx(80)端口。

然后请求域名

然后在本机的Windows上的C:\Windows\System32\drivers\etc

下的hosts文件

转发修改nginx配置文件

1
2
3
4
5
6
7
8
9
32     server{
33 listen 80;
34 server_name localhost;
35 location / {
36 proxy_pass http://localhost:8080;
37 }
38 }
39 }

重新加载配置文件

1
./nginx -s reload

流程:

image-20240818012207105

Nginx多态代理

目的:

1、浏览器访问:http://www.cpf.com:9091/beijing,通过nginx,跳转到一个tomcat上(http:localhost:8081),在浏览器上显示beijing

1、浏览器访问:http://www.cpf.com:9091/shanghai,通过nginx,跳转到一个tomcat上(http:localhost:8081),在浏览器上显示shanghai

准备两个tomcat:

安装两个tomcat这里就不演示了

然后修改nginx的配置(重点)

image-20240818021002928

重新加载conf

然后配置好网络NAT端口转发接可以了

nginx负载均衡

定义:

通俗:将负载变得均衡

image-20240818021931856

负载(请求、工作任务)、均衡(算法,中间件)。

负载均衡实验目的

1、通过浏览器多次访问一个地址(http:www.cpf.com:9002/load-balance)。

2、nginx接受上面的请求并且进行转发。

3、那么每个请求的响应是来自于不同的tomcat提供的。(2台tonmcat,8081,8082)。

两台tomcat,不同响应的内容:‘8081’和‘8082’

tomcat准备

准备2个tomcat,并做好响应的页面,启动,测试

配置nginx.conf文件

1
2
3
4
5
6
7
8
9
10
11
12
13
#server list 列出tomcat的服务和端口
upstream myServers{
server localhost:8081;
server localhost:8082;
}
#监听9002的端口如果是访问默认地址就转发到上面的两个服务去
server{
listen 9002;
server_name www.cpf.com;
location / {
proxy_path http://myServers;
}
}

然后加载配置文件启动nginx

然后去虚拟机上设置NAT映射

image-20240818024833007

负载均衡算法和权重

1、Round Robin轮询

什么也不写默认轮询,就是轮着来

2、Least Connection(最小连接数)

image-20240818031140187

3、IP Hash

image-20240818031347378

根据客户端的ip地址计算出来的hash值,第一次请求只要请求到一个服务器之后就会一直访问这个服务器,保存sessionId

image-20240818031706339

把一台服务标记下线

4、Generic Hash

image-20240818032031902

用这个consistent hash算法生成hash值

5、暂时不管

6、Random

image-20240818032234549

首先随机选两个,然后再随机选两个中的其中一个算法然后选中一个

服务权重

在后面添加一个weight参数默认值是1

image-20240818032525196

动静分离

什么是动静分离

image-20240818040517676

1、单独存放静态资源。主流方式。

2、静态资源和动态资源混在一起。(不推荐)

准备静态资源

在nginx文件上创建两个目录

/data/www/*.html

/data/images/*.jpg

配置Nginx.conf

image-20240818041851042

访问:www.cpf.com:9003/index.html 转换成 /data/www/index.html

访问: www.cpf.com:9003/images/2.jpg 转换成: /data/images/2.jpg

然后配置NAT端口转发

高可用

什么是高可用

HA(Hight Availability)

系统一直能提供服务,那么可用性是100%,

如果系统每运行100个时间单位,有1个时间单位无法提供服务。可用性99%。

如何保证高可用

集群化、冗余。

核心:冗余,自动故障转移。

nginx高可用组成

image-20240818092415736

准备2台Nginx服务器

根据这两个设置

image-20240818104010532

image-20240818104041167

clone一台虚拟机,然后修改ip

image-20240818094312479

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
#BOOTPROTO=dhcp
BOOTPROTO=static

IPADDR="192.168.10.128"
NETMASK="255.255.255.0"
GATEWAY="192.168.10.2"
DNS1="114.114.114.114"

DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens160
UUID=8eed64d7-3cd2-40cc-8e5d-f1a5567668e3
DEVICE=ens160
ONBOOT=yes

然后

1
2
nmcli c reload ens160
nmcli c up ens160

这样就设置好了另一台192.168.10.129同理

然后设置端口转发规则

image-20240818105717414

做端口转发的设置

image-20240818105958062

然后用XShell连接

image-20240818110522652

由于128和129之间作为主服务器和从服务器,所以要进行彼此之间的连通性的测试

image-20240818110810422

测试成功说明两台机器是互通的

检查2台服务器上的nginx

先测试两台服务器上是否有nginx服务

image-20240818111052880

设置静态资源

image-20240818112126213

html中内容

image-20240818112141061

查看nginx.conf配置

image-20240818112348852

编辑端口转发

image-20240818113932638

1
2
3
4
5
6
7
8
9
10
11
server{
listen 9003;
server_name www.cpf.com;
location / {
root /data/www;
}
location /images/ {
root /data;
}
}

如果是NAT,做好端口映射

keepalived安装

1
yum install keepalived

修改配置文件和启动

image-20240818182127122

配置文件路径

修改配置文件

主服务器的conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
! Configuration File for keepalived

global_defs {
}



vrrp_instance VI{
state MASTER
interface ens160
virtual_router_id 51
priority 100 #优先级
advert_int 1 #每隔几秒发送心跳
#人证规则
authentication{
auth_type PASS
auth_pass 1111
}
#虚拟ip
virtual_ipaddress{
192.168.10.139
}
}


}
1
systemctl start keepalived

启动keepalived

开启从配置

拷贝

1
scp root@192.168.10.128:/etc/keepalived/keepalived.conf ./

image-20240818183831888

总结来说,当用户访问Keepalived管理的虚拟IP时,实际上是访问到了承载VIP的主节点上的Nginx服务器,Nginx再根据其配置将请求透明地转发给后端真实服务器进行处理,从而实现了负载均衡和服务高可用。

然后做端口映射9005对虚拟ip9003端口的映射

修改一下静态资源页面好区分

image-20240818190450855

把主停掉!看看啥情况

1
systemctl stop keepalived

image-20240818190606009

这样就实现了高可用

Lua基础

特点:轻量、小巧。C语言开发。开源。

设计目的:嵌入到应用程序当中,提高灵活的扩展和定制化的功能。

lua+nginx,lua+redis。

Windows安装lua

大部分在linux使用

检查是否有Lua

1
2
3
C:\Users\pc>lua
'lua' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

进入官网The Programming Language Lua

image-20240818194311162

image-20240818194405770

image-20240818200453451

安装双击一直下一步

然后就安装成功了

Linux安装lua

image-20240818204049308

系统自带lua 也可以根据导航使用新版lua

image-20240818204629201

lua编程方式

交互式:

就是

image-20240818210117783

脚本式:

image-20240818210159228

lua基本语法

注释

1
2
3
4
-- 单行注释
--[[
多行注释
]]--

标识符:

类似于:java当中的变量,属性名,方法名

以字母(a-z,A-Z)、下划线开头,后面加上0个或多个字母、下划线、数字。

不要用下划线+大写字母。

保留字不能用。

全局变量

1
2
3
4
5
print(a) --nil
a=1
print(a) --1
a=nil
print(a) --销毁变量

数据类型

nil

没有任何有效值,就是一个nil,类似于null。

删除的作用。

1
2
3
4
myTab = {key1 = "value1",key2 = "value2"}
for k,v in paris(myTab) do
print(k.."-"..v)
end

image-20240818213505418

删除

1
2
3
4
myTab.key1 = nil
for k,v in paris(myTab) do
print(k.."-"..v)
end

image-20240818213631235

如何判断变量是否为nil

1
2
3
4
print(x) --nil
print(x == nil) -- true
print(type(x) == nil) -- false
print(type(x) == 'nil') -- true

boolean

true,false(false\nil)(其他的都为true,包括0)

1
2
3
4
5
6
7
8
9
10
11
12
print(type(true))
print(type(false))
print(type(nil))

if false or nil then
print("nil 为 true")
else
print("nil 为 false") --√
end



number

双精度(8个字节)只有这一个是双精度

1
2
a = 10
print(type(a)) -- number

string

字符串用单引号或双引号来表示

1
2
3
4
5
6
7
8
9
10
print("双引号字符串")
print('单引号字符串')

i = [[
我是中国人,
我爱祖国
]]
print(i);
-- 我是中国人,
-- 我爱祖国

用两个中括号,中间是可以换行的字符串

如果是字符串和数字进行数学运算,优先把字符串转成数字。

1
2
3
4
print('1+2') -- 3
print('error' + 1 ) -- 报错

print('error' .. 1) -- 字符串连接

计算字符串长度

1
2
testLength = "abcd"
print('长度为:'..#testLength) -- 4

table

类比成java中的数组 map,链表,队列等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tab1 = {}
tab2 = {"a","b","c"}
tab3 = {key1 = 'value1',key2 = 'value2'}
for k,v in pairs(tab3) do
print(k..'='..v)
end

tab1["a_key"] = "a_value"
for k,v in pairs(tab1) do
print(k..'='..v)
end

for k,v in pairs(tab2) do
print(k..'='..v)
end
-- 1 a 2 b 3 c


table的key的索引从1 开始

function

阶乘:

1
2
3
4
5
6
7
8
9
10
function factorial(n)
if n == 0 then
return 1
else
return n * factorial (n - 1)
end
end
testFac = factorial
print(factorial(5))
print(testFac(5))

匿名函数

1
2
3
4
5
6
7
8
9
10
11
function testPrinttab,func)
for k,v in paris(tab) do
print(func(k,v))
end
end
tab1 = {"a","b"}
testPrint(tab1,
function(k,v)
return k.."="..v
end
)

thread:类洗浴线程,独立的栈,局部变量

userdata:用来存储c、c++

变量

先声明后使用。

三种类型:全局变量(默认),局部变量(作用范围:从声明开始到所在语句块的结束),表中的域

1
2
3
4
5
6
7
8
9
10
11
12
a = 5;
local b = 5;

function testInit()
c = 6;
local d = 7
end
testInit();

print(a,b)
print(c) -- 6
print(d) -- nil

变量赋值

1
2
3
4
5
6
7
a = 变量值。
a,b = 1,2
a = 1+2
-- 常用: x, y = y,x
-- a,b,c = 1,2,3,4
--变量个数>值的个数:按照变量的个数补足nil
-- 变量个数<值的个数:多余的值会被忽略

多变量赋值:还可以用于函数的返回,参数值互换

a,b = func();

尽量用局部变量。

索引

对table中元素的访问。

1
2
3
4
5
6
tab["key"]
tab.key

tab = {key1 = "value1",key2 = "value2"}
print(tab["key1"])
print(tab.key2)

while循环

while(循环条件)

do

业务代码:

对循环的控制语句

end

1
2
3
4
5
6
a = 1
while (a < 5)
do
print(a)
a = a + 1
end

for循环

数值for循环:

1
2
3
for var = exp1,exp2,exp3 do 
循环体
end

var的值,从exp1一直到exp2,步长是exp3(是可选的,默认是1)

1
2
3
for i = 1,10,2 do
print(i)
end

泛型for循环:

是通过迭代器进行的。

1
2
3
4
a = {"1","2","3"}
for k,v in pairs(a) do
print(k,v)
end

repeat until

repeat

循环体

until(条件)

1
2
3
4
5
a = 1
repeat
print(1)
a=a+1
until(a>5)

流程控制

if() then else

1
2
3
4
a = 1
if(1== a ) then
print(a)
end

函数

函数的定义

lua里面有很多内建的函数比如print()

功能:

1、完成指定的任务

2、计算并返回值(可以返回多个值)。

函数的范围(local,缺省)

function 函数民成(参数列表)

函数体:

return 结果

end

1
2
3
4
5
6
7
function test(num1,num2)
if (num1>num2) then
result = num1
else
result = num2
return result
end

函数可以作为参数进行传递

例子:重写:自定义打印函数

1
2
3
4
myPrint = function(p)
print("重写的打印函数",p)
end
myPrint("test")

多值返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
startIndex,endIndex = string.find("www.baidu.com","baidu")
print(startIndex,endIndex)

-- 例子:找出数组中最大的值以及索引
function testMax(a)
local iIndex = 1;
local iValue = a[iIndex];
for i,v in paris(a) do
if v > iValue then
iIndex = i;
iValue = v;
end
end
return iIndex,iValue
end

可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function add1(...)
local result = 0;
for i,v in paris({...}) do
result = result +v;
end
return result;
end
print(add1(1,2,3,4,5))
-- 如何确定可变参数的数量
-- 用#
-- 例子:求平均数
function average1(...)
local result = 0;
arg = {...}
for i,v in paris(arg) do
result = result +v;
end
-- num = #arg 1
-- num = select("#",arg) 1
-- num = select("#",...) 6
print("个数是"..num)
return result;
end

函数的参数中,有固定参数,也有可变参数。固定参数写前面。

例子:固定参数和可变参数结合

1
2
3
4
5
function fmtPrint(fmt,...)
io.write(string.format(fmt,...))
end

fmtPrint("%d\t%d\n%d",2,3,4)

例子:选取可变参数中的值

1
2
3
4
5
function testSelect(...)
a,b,c = select(3,...)
print(a,b,c) -- 3,4,5
end
testSelect(1,2,3,4,5)

运算符

算数运算符

1
2
3
4
5
6
7
+ 加
- 减
* 乘
/ 除
% 取余
^ 乘幂
- 负号

关系运算符

1
2
3
4
5
6
== 等于
~= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

逻辑运算符

1
2
3
and
or
not

其他运算符

1
2
.. 连接符
# 计算字符串或者表的长度

数组

数组:相同元素的集合。

索引用整数表示:从1开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
testArray = {"a","b"}
for i = 0,3,1 do
print(testArray[i])
end

print("--------------")

testArray2 = {}
for i = 1,3,1 do
testArray2[i] = {}
for j = 1,2,1 do
testArray2[i][j] = i*j
end
end

for i = 1,3 do
for j = 1,2 do
print(testArray2[i][j])
end
end

迭代器

1
2
3
4
5
6
7
8
9
10
11
-- 迭代器
a = {"a","b","c"}
for k,v in paris(a)
do
print(k,v)
end
a = {"a","b","c"}
for k,v in iparis(a)
do
print(k,v)
end

paris会遍历所有的key和值

iparis:只会从1开始,步长是1,中间不是数字作为key的元素会被忽略,一直到第一个不连续的数字索引为止(不含)。

image-20240822042235124

iparis适合遍历数组

for迭代器的结构:

for变量列表 in 迭代函数 ,状态常量,控制变量

do

循环体

end

1
2
3
4
5
6
7
8
9
10
11
12
-- 求平方
function square(iteratorMaxCount,currentNumber)
if currentNumber < iteratorMaxCount then
currentNumber = currentNumber + 1
return currentNumber,currentNumber*currentNumber
end
end

for i,n in square,9,0
do
print(i,n)
end

table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a={key1 = "a",key2 = "b"}
-- 不能用nil做索引
-- table
-- 初始化
myTable = {}
myTable[1] = "1"
-- 删除
myTable = nil

myTab = {}
print("myTab的类型是:"..type(myTab)) -- table

mayTab[1] = "1"
myTab["a"] = "a"
--[[
1
a
]]--

模块

模块的定义

从lua5.1开始,引入了模块机制,把一些公用的代码放到文件中,通过api的方式,让其他程序调用,这个文件,就是一个模块。

类似于java中的jar包。

lua中的模块,其实就是一个table(由遍历那个,函数等已知的lua元素组成)。最好在模块的结尾,需要返回一个table。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 模块 moudle.lua
moudle = {}

moudle.constant = "模块中的常量"

function moudle.func1)
print("这是函数1")
end

local function fun2()
print("这是私有函数2")
end

function moudle fun3()
func2()
end

return moudle

require函数

用require函数调用模块

require “模块名”

1
2
3
4
5
6
-- 调用模块
requir("moudle")

print(moudle.constant)

moudle.func1()

元表

元表的定义

1
2
3
4
5
6
7
-- 元表
a = {"a","b","c"} -- 普通表
b = {} -- 元表

c = setmetatable(a,b) -- 返回的值是普通表 a

e = getmetatable(a) --这里的返回值是元表 b

允许我们改变table的行为。

setmetatable(普通表,元表)

元表_index元方法

定义普通表。

给普通表设置元表,而原表中有(两个下划线)index,两个下划线index = i ,i中有我们访问的不存在的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- __index(两个下划线)
tab1 = {"a","b","c"} --普通表
newTab = {} -- 普通表,有一个五号元素e
newTab[5] = "e"
metaTab1 = {__index = newTab} -- 元表

setmetatable(tab1,metaTab1)
print(tab1[5]) -- e

-- __index=函数(表,key)

tab1 = {"a","b","c"} --普通表

metaTab1 = {
-- tab是tab1的值
-- key是5
__index = function(tab,key)
if(key == 5) then
return "index--5"
end
end
} -- 元表

setmetatable(tab1,metaTab1)
print(tab1[5]) -- e

请求表中的key值:

现在普通表中招,有返回,没有,看元表

如果元表有index,且(双下划线)index中有对应的key

如果没有,继续找index中的function

newindex元方法

对表进行更新时调用。上面是对表进行调用时调用

1
2
3
4
5
6
7
8
9
10
mytab2 = {"a","b"}
metatab2 = {
__newindex = function(tab,key,value)
-- tab[key] = value
-- 这样不能对普通表进行修改,会引起死循环
rawset(tab,key,value) --这样才能赋值成功

end
}
setmetatable(mytab2,metatab2)

表的用法,上面是函数的用法

1
2
3
4
5
6
7
8
mytab2 = {"a","b"}
mytab21 = {}
metatab2 = {
__newindex = mytab21

}
setmetatable(mytab2,metatab2)
--这样修改的值是在21中

为表添加操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tab3 = {"1","2"}
tab4 = {}
v = tab3 + tab4 --表格不能加但是可以用元表
metatab3 = {
__add = function(tab1,tab2)
local m = #tab1

for k,v in paris(tab2)
do
m = m + 1
tab1[m] = v
end
return tab1
end

}

setmetatable(tab3,metatab3)
1
2
3
4
5
6
7
__add 加法
__sub 减法
__mul 乘法
__div 除法
__mod 取余
__concat ..
__eq ==

call元方法

lua中,当表被当成函数调用时,会触发。

1
2
3
4
5
6
7
8
9
10
11
tab_a1 = {"a","b"}
tab_a2 = {"1","2"}

mrtatab_a = {
__call = function(tab,arg)
print(tab) -- tab_a1的表名
print(arg) -- 传输的参数6
end
}
setmetatable(tab_a1,metatable_a)
tab_a1(6)

tostring

用于修改表的输出行为。类似于java中的toString().重写toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
tab_a1 = {"a","b"}
tab_a2 = {"1","2"}

mrtatab_a = {
__call = function(tab,arg)
print(tab) -- tab_a1的表名
print(arg) -- 传输的参数6
end,

__tostring = function(tab)
local str = ""
for k,v in paris(tab) do
str = str .. v ..","
end
return str
end
}
setmetatable(tab_a1,metatable_a)
tab_a1(6)

ps每个元方法之间用逗号

协同程序

类似于 多线程的概念。

协程和线程的区别:

一个多线程的程序,可以同时运行多个线程,而协程,在某个时刻,只有一个协程运行。

线程由cpu调度,协程由代码调度。

1
2
3
4
5
6
7
-- 协同函数
testAdd = coroutine.create(
function(a,b)
print(a+b)
end
)
coroutine.resume(testAdd,1,2) -- 3

协程启动和停止

wrap

1
2
3
4
5
6
7
co = coroutine.wrap(
function(a)
print("参数值是"..a)
coroutine.yield(); -- 协程的暂停
end
)
co(1) -- 参数值是1

启动、停止

1
2
3
4
5
6
7
8
9
10
-- 协同函数
testAdd = coroutine.create(
function(a,b)
print(a+b)
conroutine.yield();
print(a-b)
end
)
coroutine.resume(testAdd,1,2) -- 3
coroutine.resume(testAdd,1,2) -- -1再次唤醒

协程中的返回值

1
2
3
4
5
6
7
8
9
10
11
-- 协同函数
testAdd = coroutine.create(
function(a,b)
print(a+b)
return a+b
end
)
r1 = coroutine.resume(testAdd,1,2) -- 3
print(r1) -- true
r1,r2 = coroutine.resume(testAdd,1,2) -- 3
print(r2) -- 3

协程状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 协同函数
testAdd = coroutine.create(
function(a,b)
print(coroutine,status(testAdd)) -- running
coroutine.yield()
return a+b,a-b
end
)
print(coroutine,status(testAdd)) -- suspend
r1 = testAdd = coroutine.resume(testAdd,1,4)
print(coroutine,status(testAdd)) -- suspend这是yield后的状态
r1 = testAdd = coroutine.resume(testAdd,1,4)
print(coroutine,status(testAdd)) -- dead
r1 = testAdd = coroutine.resume(testAdd,1,4)
-- 协程已经运行完不能再启动了
print(coroutine,status(testAdd)) -- false

协程协作

协程的唯一标识

1
2
3
4
5
testAdd = coroutine.create(
function(a,b)
print(coroutin.running) -- 打印协程的唯一标识
end
)

协程内部和外部协作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo(a)
print("foo参数是",a)
return coroutin.yield(a*2)
end

co = coroutine.create(
function(a,b)
print("第一次启动协程,参数是",a,b)
foo(a+1)
end
)

print("主程序",coroutin.resume(co,1,5))

image-20240822083537140

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(a)
print("foo参数是",a)
return coroutin.yield(a*2)
end

co = coroutine.create(
function(a,b)
print("第一次启动协程,参数是",a,b)
local r = foo(a+1)
print("第二启动协程,参数是",r)
end
)

print("主程序",coroutin.resume(co,1,5))
print("主程序",coroutin.resume(co,"r")

image-20240822083803956

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo(a)
print("foo参数是",a)
return coroutin.yield(a*2)
end

co = coroutine.create(
function(a,b)
print("第一次启动协程,参数是",a,b)
local r = foo(a+1)
print("第二启动协程,参数是",r)

coroutine.yield(a+b,a-b)
end
)

print("主程序",coroutin.resume(co,1,5))
print("主程序",coroutin.resume(co,"r")

image-20240822084011492

第一次resume传入的参数是function的参数

第一次yield的参数,是第一次resume的返回值,

第二次resume的参数,是第一次yield的返回值。

生产者消费者问题

思路:

1、生产者生产完产品,(自己停下来)等待消费者消费。

2、消费者消费完产品,(自己停下来)等待生产者生产。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function productor()
-- 定义生产的商品,用数字来替代
local i = 0
while i < 100
do
i = i + 1
print("生产了",i)
-- 通过协程实现
coroutine.yield(i)
end


end
function consumer()
while true
do
-- 从生产者获取产品
local status,result = coroutine.resume(po)
print("消费了",result)
if(result == 99) then
break
end
end
end

-- 程序开始
po = coroutine.create(productor)
consumer()

错误处理

语法错误&运行错误

语法错误:

程序无法运行

运行错误:

运行时发生的错误

错误的处理assert和error

1
2
3
4
function add(a,b)
--如果b是false就会输出b是nil
assert(b,"b是nil")
end

assert:

第一个参数为true,不输出第二个参数

第一个参数为false,输出第二个参数。

error

1
2
3
4
5
6
7
function add(a,b)
if(not b) then
error("报error了")
end
print("正常执行")
end
add(1)

当error和assert触发错误时,程序退出。

错误处理pcall

pcall函数有两个参数(要执行的函数,函数的参数列表)

如果函数执行没有问题,返回true,如果有问题返回false

1
2
3
4
5
6
7
8
9
10
11
12
function add(a,b)
c = a + b
print("正常执行")
end

if pcall(add,1,3) then
print("没问题")
else
print("错误")
end

print("主程序")

xpcall

1
2
3
4
5
6
7
8
9
function testXpcall()
c = 1+nil
end

function testErrorHandle(error)
print("我来处理错误",error)
end

xpcall(testXpcall,testErrorHandle)

面向对象定义方法

对象:属性+方法。

table,function。

1
2
3
4
5
6
student = {name = "张三",age = 18}
student.gotoSchool = function (name)
print(name.."上学")
end
print("学生姓名"..student.name)
student.gotoSchool(student.name)

对象new

技巧冒号

1、类比:一个类,实例化多个对象。

1
2
3
4
5
6
7
8
9
10
11
12
Student = {name = "默认名称"}
function Student:new()
-- 我们要返回的对象
s= {}
-- 用了冒号,self就是自己
setmetatable(s,{__index = self})
return s
end

s1 = Student:new()
s1.name = "李四"
s2 = Student:new()

nginx下lua的实现机制

nginx+lua概述

nginx:功能由模块提供,http模块,event模块,mail模块。

处理http请求的时候,可以用模块做一些功能:eg:登录校验,js合并,数据库访问,鉴权。

c或c++。

lua的解释器,集成到了nginx中:ngx_lua模块。

lua内部,内建了协程。

nginx启动流程&管理进程、工作进程

nginx启动流程

工作流程:启动流程,管理进程流程,工作进程流程。

启动流程

1、框架程序的启动。创建核心模块

2、模块的启动。模块的启动和初始化的过程。

1
2
3
4
5
6
7
8
9
10
11
12
./nginx
1、nginx接受启动参数、解析参数。
2、-s 判断是否有 。 如果有-s 重新加载新的配置文件
3、调用核心模块的create_conf方法。基于配置文件创建模块或数据结构(用于存储配置)做初始化
4、解析nginx.conf配置项,存到上面的数据结构中。
5、调用每个模块的init_conf方法,进行初始化
6、如果配置文件中,有关日志,缓存等的配置,对这些文件进行创建
7、按照配置监听端口,一般比如http模块,stream模块。
8、调用所有模块的init_moudle方法,根据配置信息进行初始化模块
9、如果文件配置,nginx为master模式,创建管理进程。
10、管理进程根据配置供的工作进程数,将所有进程分叉,让他们独立接受用户的请求。
11、管理进程调用模块的init_process方法,这样工作进程就启动了。工作进程进入自己 的消息循环中,开始等待处理用户的请求。

管理进程和工作进程

管理工作进程,自己实现:重启服务,平滑升级(-s reload),更换日志文件,动态加载配置。不处理用户的请求

工作进程:干活的,处理用户的请求,协调各个模块完成任务。由管理进程管理。

nginx+lua请求处理流程

ngx_lua,生效于 工作进程。

模型:一个请求、一个协程。

image-20240822204032517

nginx+lua+redis实践

概述

nginx、Lua访问redis的三种方式:

1、HttpRedis模块

指令少、功能单一,适合简单的缓存。只支持get、select命令。

2、HttpRedis2Moudle模块

功能强大、比较灵活。

3、lua-resty-redis库

OpenResty。api。适合复杂业务,节省内存。

OpenResty:基于nginx开源版本的一个扩展版本。集成了大量精良的lua库。

OpenResty安装

进入yum资源文件

1
cd /etc/yum.repos.d/

安装wget

1
yum install wget

下载资源库

1
wget https://openresty.org/package/centos/openresty.repo

检验资源库

image-20240822205453276

得到文件

安装文件

1
yum install openresty

启动openresty

1
2
/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/

然后配置端口转发默认80端口

测试成功

image-20240823000006669

初始测试lua

image-20240823044414966

重新编写.conf

1
2
3
4
5
6
7
server{
listen 8080;
location / {
default_type text/html;
content_by_lua 'ngx.say("hello")';
}
}

重启文件

1
2
/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/ -s reload

修改端口转发

然后访问

image-20240823045042151

redis安装

我们也是用yum来安装

首先安装epel-release

这个是可扩展的安装包,针对企业级linux

1
yum install epel-release

然后再安装redis

1
yum install redis

启动redis

1
2
systemctl start redis

启动redis客户端

1
2
cd /usr/bin
./redis-cli

Redis连接报错“NOAUTH Authentication required”解决方案_(error) noauth authentication required.-CSDN博客

1
2
3
4
5
6
127.0.0.1:6379> set akey avalue
OK
127.0.0.1:6379> get akey
"avalue"
127.0.0.1:6379> quit

查找客户端的命令

1
which redis-cli

httpredis使用

首先进入

1
cd /usr/local/openresty/nginx/conf

然后备份然后修改配置文件

1
cp nginx.conf nginx-httpredis.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
worker_processes  1;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
root html;
index index.html;
location / {
default_type text/plain;
set $redis_key "m";
redis_pass 127.0.0.1:6379;
error_page 404 = @fetch;
}
location @fetch {
root html;
}
}
}

这是一个Nginx配置文件的一部分,用于配置Web服务器的设置。下面是对每个部分的解释:

  1. worker_processes 1;:这行指定了Nginx工作进程的数量。在这个例子中,只有一个工作进程。
  2. events { ... }:这个块定义了事件处理的相关设置。
    • worker_connections 1024;:这行设置了每个工作进程允许的最大并发连接数为1024。
  3. http { ... }:这个块包含了HTTP服务器的配置。
    • include mime.types;:这行包含了一个名为mime.types的文件,该文件定义了不同文件扩展名对应的MIME类型。
    • default_type application/octet-stream;:这行设置了默认的MIME类型为application/octet-stream,适用于未知的文件类型。
    • sendfile on;:这行启用了sendfile功能,允许高效地发送文件。
    • keepalive_timeout 65;:这行设置了长连接的超时时间为65秒。
  4. server { ... }:这个块定义了一个虚拟主机的配置。
    • listen 80;:这行指定了服务器监听的端口号为80。
    • server_name localhost;:这行设置了服务器的名称为localhost。
    • root html;:这行设置了服务器的根目录为html文件夹。
    • index index.html;:这行设置了默认的索引文件为index.html。
  5. location / { ... }:这个块定义了一个位置匹配规则,用于处理以”/“开头的请求。
    • default_type text/plain;:这行设置了默认的内容类型为纯文本。
    • set $redis_key "m";:这行设置了一个名为$redis_key的变量,其值为”m”。
    • redis_pass 127.0.0.1:6379;:这行将请求转发到本地的Redis服务器(IP地址为127.0.0.1,端口号为6379)。
    • error_page 404 = @fetch;:这行定义了一个错误页面,当发生404错误时,将请求转发到名为@fetch的位置。
  6. location @fetch { ... }:这个块定义了一个名为@fetch的位置,用于处理错误页面的请求。
    • root html;:这行设置了错误页面的根目录为html文件夹。

先停止nginx

1
2
/usr/local/openresty/nginx/sbin/nginx -s stop

检查是否停掉

1
ps -ef | grep nginx

指定配置文件重新启动

1
/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/ -c /usr/local/openresty/nginx/conf/nginx-httpredis.conf

这是一个用于重新加载Nginx配置文件的命令。具体来说,它告诉Nginx使用指定的配置文件(nginx-openresty-lua-redis.conf)并重新加载配置。命令中的参数解释如下:

  • /usr/local/openresty/nginx/sbin/nginx: Nginx可执行文件的路径。
  • -p /usr/local/openresty/nginx/: 指定Nginx安装目录的路径。
  • -c /usr/local/openresty/nginx/conf/nginx-openresty-lua-redis.conf: 指定要使用的配置文件的路径。
  • -s reload: 告诉Nginx重新加载配置文件,而不是停止并重新启动服务。

测试

1、redis中没有key为m的键值对。

image-20240823064608611

2、我们通过redis,设置key为m的value是“mValue”

设置m的值

1
2
3
4
5
6
7
8
[root@localhost bin]# ./redis-cli
127.0.0.1:6379> get m
(nil)
127.0.0.1:6379> set m 'mValue'
OK
127.0.0.1:6379> get m
"mValue"
127.0.0.1:6379>

image-20240824231937777

扩展:

用于降级。

HttpRedis2Moudle使用

也是先备份conf然后编写

1
2
[root@localhost conf]# cp nginx-httpredis.conf nginx-httpRedis2Moudle.conf
[root@localhost conf]# vi nginx-httpRedis2Moudle.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
worker_processes  1;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
root html;
index index.html;
location /get {
redis2_query get "n1";
redis2_pass 127.0.0.1:6379;
}
location /set{
redis2_query set "n1" 'n1Value';
redis2_pass 127.0.0.1:6379;
}
}
}


关闭nginx

1
2
[root@localhost conf]# /usr/local/openresty/nginx/sbin/nginx -s stop

重新加载配置文件

1
2
[root@localhost conf]# /usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/ -c /usr/local/openresty/nginx/conf/nginx-httpRedis2Moudle.conf -s reload

以指定的配置文件启动

1
/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/ -c /usr/local/openresty/nginx/conf/nginx-httpRedis2Moudle.conf 

访问get和set方法进行测试

1
2
3
[root@localhost conf]# curl localhost/get
[root@localhost conf]# curl localhost/set
[root@localhost conf]# curl localhost/get

openresty-lua-redis

参考地址:openresty/lua-resty-redis: Lua redis client driver for the ngx_lua based on the cosocket API (github.com)

备份nginx.conf 重命名为nginx-openresty-lua-redis.conf

编写配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
worker_processes  1;
error_log logs/error.log;


events {
worker_connections 1024;
}


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


sendfile on;
keepalive_timeout 65;


server {
listen 8082;
server_name localhost;


location / {
default_type text/html;
content_by_lua_file /usr/local/openresty/nginx/lua/lua-openresty-redis.lua;
}


}

}

指定配置文件的启动命令

1
2
[root@localhost nginx]# ./sbin/nginx -p ./ -c conf/nginx-openresty-lua-redis.conf 

修改lua文件

1
[root@localhost ~]# cd /usr/local/openresty/nginx/lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 引用resty的redis
local redis = requier "resty.redis";
local red = redis:new();

-- 连接redis
local ok,err = red:connect("127.0.0.1",6379);
if not ok then
ngx.say("fail to connect",err);
end

ok,err = red:set("dKey","dValue");
if not ok then
ngx.say("faild to set dKey",err);
return;
end

ngx.say("set dKey success");
return;

重新加载配置文件

1
[root@localhost nginx]# ./sbin/nginx -p ./ -c conf/nginx-openresty-lua-redis.conf

配置端口转发进行访问测试

image-20240825094409442

修改lua文件读取redis中的key值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 引用resty的redis
local redis = require "resty.redis";
local red = redis:new();

-- 连接redis
local ok,err = red:connect("127.0.0.1",6379);
if not ok then
ngx.say("fail to connect",err);
end

ok,err = red:set("dKey","dValue");
if not ok then
ngx.say("faild to set dKey",err);
return;
end
ok,err = red:get("dKey")
if not ok then
ngx.say("dKey is null");
else
ngx.say("dKey's value is :"..ok)
end

return;

然后重新加载并启动服务

1
2
[root@localhost nginx]# ./sbin/nginx -p ./ -c conf/nginx-openresty-lua-redis.conf -s reload

分析OpenResty响应信息

目的:为了修改以后的响应信息。

以默认的方式打开nginx

1
[root@localhost nginx]# ./sbin/nginx -p ./

然后配置端口转发访问虚拟机80端口然后访问

image-20240825101214318

那么这个页面哪里来的呢?

在nginx里的html文件夹中的index.html

那为什么是展示这个页面呢?

在conf文件夹中的nginx.conf里面查看

image-20240825101451036

server的新写法

image-20240825102816913

image-20240825103156371

openresty获取请求参数

备份一份新的配置文件

1
[root@localhost conf]# cp nginx.conf nginx-param.conf

编写配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
worker_processes  1;

error_log logs/error.log;

pid logs/nginx.pid;


events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;
server{
listen 8081;
location / {
default_type text/html;
content_by_lua_file /usr/local/openresty/nginx/lua/lua_http_param.lua;
}
}

}

编写配置文件

1
2
3
4
[root@localhost conf]# cd /usr/local/openresty/nginx/lua/
[root@localhost lua]# vi lua_http_param.lua
[root@localhost lua]#

1
2
3
4
5
6
7
-- 获取get请求的参数
local args = ngx.req.get_uri_args();
for k,v in pairs(args)
do
ngx.say("key:",k," value:",v);
end

然后启动nginx

1
[root@localhost nginx]# ./sbin/nginx -p ./ -c conf/nginx-param.conf 

配置端口转发,访问测试

image-20240825124111553

将请求参数写入redis

重新编写lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 获取get请求的参数
local redis = require "resty.redis";
local red = redis:new();
red:connect("127.0.0.1",6379);
-- 省去连接错误的判断




local args = ngx.req.get_uri_args();
for k,v in pairs(args)
do
ngx.say("key:",k," value:",v);
red:set(k,v);
end

获取请求头参数

获取http请求中的header的参数

首先先修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37


worker_processes 1;

error_log logs/error.log;
pid logs/nginx.pid;

events {
worker_connections 1024;
}


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


sendfile on;
keepalive_timeout 65;


server {
listen 8082;
server_name localhost;


location / {
default_type text/html;
# content_by_lua_file /usr/local/openresty/nginx/lua/lua-openresty-redis.lua;
content_by_lua_file /usr/local/openresty/nginx/lua/lua-header-param.lua;
}


}

}

然后开始编写Lua文件

1
2
3
4
5
6
7
8
-- 获取header参数
local headers = ngx.req.get_headers();
for k,v in pairs(headers)
do
ngx.say("[header] key:",k," value : ",v);

end

然后启动nginx

1
2
[root@localhost sbin]# ./nginx -p ../../nginx/ -c /usr/local/openresty/nginx/conf/nginx-param.conf -s reload

然后利用postman

进行测试

获取post body 键值对 参数

首先修改nginx.conf的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36


worker_processes 1;

error_log logs/error.log;
pid logs/nginx.pid;

events {
worker_connections 1024;
}


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


sendfile on;
keepalive_timeout 65;


server {
listen 8082;
server_name localhost;


location / {
default_type text/html;
# content_by_lua_file /usr/local/openresty/nginx/lua/lua-openresty-redis.lua;
content_by_lua_file /usr/local/openresty/nginx/lua/lua-post-kv-param.lua;
}


}

}

然后编写Lua脚本

1
2
3
4
5
6
7
8
9
-- 获取post body kv 参数
-- 读取body
ngx.req.read_body();
local postArgs = ngx.req.get_post_args();
for k,v in pairs(postArgs)
do
ngx.say("[post] key:",k,"value:",v);
end

然后重启nginx

1
2
[root@localhost sbin]# ./nginx -p ../../nginx/ -c /usr/local/openresty/nginx/conf/nginx-param.conf -s reload

postman测试

image-20240826083013980

nginx+lua获取body体参数

老操作

修改nginx配置文件

然后写lua文件

1
2
3
4
5
6
-- 获取body体参数
-- 所有获取body的操作,这个很重要
ngx.req.read_body();
local body = ngx.req.get_body_data();
ngx.say(body);

然后重启

然后测试

image-20240826084446086

nginx+lua+redis限流实战

先跑通基本环境,再实现具体业务

基本环境准备

编写nginx的配置文件

1
[root@localhost nginx]# vi nginx-ip-limit.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
worker_processes 1;
error_log logs/error.log debug;

events{
worker_connections 1024;
}

http{

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

server{
listen 8083;
location / {
default_type text/html;

access_by_lua_file /usr/local/openresty/nginx/lua/ip-limit-access.lua;
log_by_lua_file /usr/local/openresty/nginx/lua/ip-limit-log.lua;
proxy_pass http://localhost:8080/;
}


}



}

然后启动tomcat

然后启动nginx

然后给8083做映射

然后访问8083端口就会由nginx转到8080端口然后返回tomcat页面

限流业务

需求:系统每秒限流2个请求,如果超过 (2个请求)则系统限制10秒内不能被访问。

image-20240826102338376

编写lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ngx.log(ngx.INFO,"ip limit access");

local redis = require "resty.redis";
local red = redis:new();

-- 连接redis
red:connect("127.0.0.1",6379);
-- 需要写连接成功的判断


-- 判断是否限流
limit = red:get("limit");
if limit == '1' then
return ngx.exit(503);
end

inc = red:incr("testLimit");

if inc <= 2 then

red:expire("testLimit",1);
else
red:set("limit",1);
red:expire("limit",10);
end

重启nginx

然后访问8083端口就实现了

放爬虫案例

通过nginx+lua防止爬虫一些恶意的请求,当爬虫影响到网站性能就要开始防止爬虫。

爬虫的种类:

1、善意的。百度,google。

2、恶意的。恶意窃取网站内容。

robots协议:

放爬虫的方法:限制爬虫ip。对我们系统的请求。

扩展:限制爬虫的方法:

1、限制user-agent。

2、限制ip。

3、添加验证码。

4、cookie限制。

流程图

image-20240827083322797

放爬虫需求&步骤分析

1、收集黑名单ip

2、存储到redis的set集合中。

3、nginx定期(2s)去从redis取黑名单的ip集合

4、当请求来的时候,进行判断。请求来源的ip是否在ip黑名单中。

reids黑名单准备

用set类型

key : ip-black-list 192.168.10.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost ~]# /usr/bin/reids-cli
-bash: /usr/bin/reids-cli: No such file or directory
[root@localhost ~]# /usr/bin/redis-cli
127.0.0.1:6379> sadd ip-black-list 192.168.10.1
(integer) 1
127.0.0.1:6379> sadd ip-black-list 192.168.11.2
(integer) 1
127.0.0.1:6379> smenbers ip-black-list
(error) ERR unknown command 'smenbers'
127.0.0.1:6379> smembers ip-black-list
1) "192.168.11.2"
2) "192.168.10.1"
127.0.0.1:6379>

nginx配置文件编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
worker_processes 1;
error_log logs/error.log debug;

events{
worker_connections 1024;



}



http{
## 定义共享空间
lua_shared_dict ip_black_list 1m;
include mime.types;
default_type application/octet-stream;


server {
listen 8083;
location / {
default_type text/html;
access_by_lua_file /usr/local/openresty/nginx/lua/black-list-access.lua;
proxy_pass http://localhost:8080/;
}
}


}

然后启动nginx

1
[root@localhost nginx]# ./sbin/nginx -p ./  -c conf/nginx-black-list.conf 

核心lua文件编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

ngx.log(ngx.INFO);
local ip_black_list = ngx.shared.ip_black_list;
-- 获取最近更新时间
local last_update_time = ip_black_list:get("last_update_time");

if last_update_time == null or last_update_time < (ngx.now() - 2) then
-- 黑名单最近更新时间过期了,我们需要重新获取
local redis = require "resty.redis";
local red = redis:new();

local ok,err = red:connect("127.0.0.1",6379);
if not ok then
ngx.log(ngx.INFO,"connect error");
else
local local_black_list , err = red:smembers("ip-black-list");
ip_black_list:flush_all();
for k,v in pairs(local_black_list)
do
ip_black_list:set(v,true);
end

ip_black_list:set("last_update_time",ngx.now());
end
end

-- 判断当前的ip是不是黑名单
-- 获取当前的ip地址
local ip = ngx.var.remote_addr;
ngx.log(ngx.INFO,"request ip is "..ip);
-- 判断当前ip是否为黑名单
if ip_black_list:get(ip) then
return ngx.exit(503);
end

Openresty 的命令,这个只能通过查阅github上的readme

https://github.com/openresty/lua-nginx-module#lua_shared_dict

另外如果是nginx的命令,可以看这个地址:

https://nginx.p2hp.com/en/docs/dirindex.html