Nginx+lua+OpenResty高性能实践 Nginx Nginx是一个高性能的Web服务器和反向代理的软件
Web服务器:就是运行我们wen服务的容器,提供web功能,类似tomcat也提供累死的功能。
代理:就是软件架构和网络设计中,一个重要的概念,有两种代理:正向代理和反向代理。
正向代理 用户设置代理服务器。
所有的请求都由代理服务器发出,无法判断代理了多少用户端,叫正向代理。
反向代理 在服务器端设置代理,素有请求,由服务器端接受,然后再由代理服务器发送到后方的服务器。这么依赖,所有请求,都由一个服务器接受,无法判断代理的多少服务端。这是反向代理、
利用反向代理,就可以即将请求分发到系统内部的多个节点上,从而减少每个节点的并发数。而这些节点在外界看来就是一个系统,表现出唯一的ip,也就是代理服务器的IP.
是用来做负载均衡用的
Nginx安装 官网:nginx
点击documentation然后点击installing nginx
然后打开centos虚拟器
安装完毕
然后继续根据官方文档
然后在官方文档的目录下面发现没有nginx.repo的这个文件
然后touch一下,新建这个文件
然后
开始编写配置文件
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
这个是nginx的版本
1 nginx-1:1.26.2-1.el8.ngx.x86_64
Nginx启动及验证 在/usr/sbin目录下输入
然后查看进程
访问一下验证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
192.168.1.38:80访问nginx欢迎页
记得关闭防火墙
Nginx常用命令 nginx命令存放在/usr/sbin里面
首先配置文件在哪
找到conf文件
回到/usr/sbin目录下
更多命令在官网查看
1 https://nginx.org/en/docs/beginners_guide.html
Nginx配置文件介绍
进入conf文件
指令的种类:简单指令,块指令
简单指令:
参数名 参数值 ;
整体分类:
全局块:就是最开始的简单指令。从配置文件开始到events
events块:配置服务器和用户网络连接相关的参数
http块:
包含全局块和server块可以是n个server块
location块也可以是多个
Nginx反向代理-单台机器 目的:
1、在浏览器访问一个地址:
2、Nginx接受上面的请求
3、转发请求到tomcat上
4、tomcat响应一个页面,页面中有:“tomcat hello”。
步骤:
1、安装Nginx,并启动。
浏览器访问:http:1923.168.1.41:80/得到
2、准备一个tomcat
查看服务器上是否有tomcat
安装tomcat
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,并且查看是否能从本地访问
在外面访问一下
也是可以的
重点来了:
1、在浏览器访问一个地址:localhost:2080
2、Nginx接受上面的请求
3、转发请求到tomcat上
4、tomcat响应一个页面,页面中有:“tomcat hello”。
通过nat网络分发,将本机2080转到虚拟机上的nginx的80端口
点击修改虚拟网络设置
然后添加
这样主机访问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 }
重新加载配置文件
流程:
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的配置(重点)
重新加载conf
然后配置好网络NAT端口转发接可以了
nginx负载均衡 定义:
通俗:将负载变得均衡
负载(请求、工作任务)、均衡(算法,中间件)。
负载均衡实验目的 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 upstream myServers{ server localhost:8081; server localhost:8082; } server{ listen 9002; server_name www.cpf.com; location / { proxy_path http://myServers; } }
然后加载配置文件启动nginx
然后去虚拟机上设置NAT映射
负载均衡算法和权重 1、Round Robin轮询
什么也不写默认轮询,就是轮着来
2、Least Connection(最小连接数)
3、IP Hash
根据客户端的ip地址计算出来的hash值,第一次请求只要请求到一个服务器之后就会一直访问这个服务器,保存sessionId
把一台服务标记下线
4、Generic Hash
用这个consistent hash算法生成hash值
5、暂时不管
6、Random
首先随机选两个,然后再随机选两个中的其中一个算法然后选中一个
服务权重
在后面添加一个weight参数默认值是1
动静分离 什么是动静分离
1、单独存放静态资源。主流方式。
2、静态资源和动态资源混在一起。(不推荐)
准备静态资源 在nginx文件上创建两个目录
/data/www/*.html
/data/images/*.jpg
配置Nginx.conf
访问: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高可用组成
准备2台Nginx服务器 根据这两个设置
clone一台虚拟机,然后修改ip
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同理
然后设置端口转发规则
做端口转发的设置
然后用XShell连接
由于128和129之间作为主服务器和从服务器,所以要进行彼此之间的连通性的测试
测试成功说明两台机器是互通的
检查2台服务器上的nginx 先测试两台服务器上是否有nginx服务
设置静态资源
html中内容
查看nginx.conf配置
编辑端口转发
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安装
修改配置文件和启动
配置文件路径
修改配置文件
主服务器的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 ./
总结来说,当用户访问Keepalived管理的虚拟IP时,实际上是访问到了承载VIP的主节点上的Nginx服务器,Nginx再根据其配置将请求透明地转发给后端真实服务器进行处理,从而实现了负载均衡和服务高可用。
然后做端口映射9005对虚拟ip9003端口的映射
修改一下静态资源页面好区分
把主停掉!看看啥情况
1 systemctl stop keepalived
这样就实现了高可用
Lua基础 特点:轻量、小巧。C语言开发。开源。
设计目的:嵌入到应用程序当中,提高灵活的扩展和定制化的功能。
lua+nginx,lua+redis。
Windows安装lua 大部分在linux使用
检查是否有Lua
1 2 3 C:\Users\pc>lua 'lua' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
进入官网The Programming Language Lua
安装双击一直下一步
然后就安装成功了
Linux安装lua
系统自带lua 也可以根据导航使用新版lua
lua编程方式 交互式:
就是
脚本式:
lua基本语法 注释
标识符:
类似于:java当中的变量,属性名,方法名
以字母(a-z,A-Z)、下划线开头,后面加上0个或多个字母、下划线、数字。
不要用下划线+大写字母。
保留字不能用。
全局变量
1 2 3 4 5 print (a) a=1 print (a) 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
删除
1 2 3 4 myTab.key1 = nil for k,v in paris(myTab) do print (k.."-" ..v) end
如何判断变量是否为nil
1 2 3 4 print (x) print (x == nil ) print (type (x) == nil ) print (type (x) == 'nil' )
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个字节)只有这一个是双精度
string 字符串用单引号或双引号来表示
1 2 3 4 5 6 7 8 9 10 print ("双引号字符串" )print ('单引号字符串' )i = [[ 我是中国人, 我爱祖国 ]] print (i);
用两个中括号,中间是可以换行的字符串
如果是字符串和数字进行数学运算,优先把字符串转成数字。
1 2 3 4 print ('1+2' ) print ('error' + 1 ) print ('error' .. 1 )
计算字符串长度
1 2 testLength = "abcd" print ('长度为:' ..#testLength)
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
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 testPrint (tab ,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) print (d)
变量赋值 1 2 3 4 5 6 7 a = 变量值。 a,b = 1 ,2 a = 1 +2
多变量赋值:还可以用于函数的返回,参数值互换
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 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) end testSelect(1 ,2 ,3 ,4 ,5 )
运算符 算数运算符
1 2 3 4 5 6 7 + 加 - 减 * 乘 / 除 % 取余 ^ 乘幂 - 负号
关系运算符
1 2 3 4 5 6 == 等于 ~= 不等于 > 大于 < 小于 >= 大于等于 <= 小于等于
逻辑运算符
其他运算符
数组 数组:相同元素的集合。
索引用整数表示:从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的元素会被忽略,一直到第一个不连续的数字索引为止(不含)。
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" } myTable = {} myTable[1 ] = "1" myTable = nil myTab = {} print ("myTab的类型是:" ..type (myTab)) mayTab[1 ] = "1" myTab["a" ] = "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 = {} 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) e = getmetatable (a)
允许我们改变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 tab1 = {"a" ,"b" ,"c" } newTab = {} newTab[5 ] = "e" metaTab1 = {__index = newTab} setmetatable (tab1,metaTab1)print (tab1[5 ]) tab1 = {"a" ,"b" ,"c" } metaTab1 = { __index = function (tab,key) if (key == 5 ) then return "index--5" end end } setmetatable (tab1,metaTab1)print (tab1[5 ])
请求表中的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) 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)
为表添加操作符 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) print (arg ) 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) print (arg ) 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 )
协程启动和停止 wrap
1 2 3 4 5 6 7 co = coroutine .wrap ( function (a) print ("参数值是" ..a) coroutine .yield (); end ) co(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 ) coroutine .resume (testAdd,1 ,2 )
协程中的返回值 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 ) print (r1) r1,r2 = coroutine .resume (testAdd,1 ,2 ) print (r2)
协程状态 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)) coroutine .yield () return a+b,a-b end ) print (coroutine ,status (testAdd)) r1 = testAdd = coroutine .resume (testAdd,1 ,4 ) print (coroutine ,status (testAdd)) r1 = testAdd = coroutine .resume (testAdd,1 ,4 ) print (coroutine ,status (testAdd)) r1 = testAdd = coroutine .resume (testAdd,1 ,4 ) print (coroutine ,status (testAdd))
协程协作 协程的唯一标识
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 ))
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" )
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")
第一次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) 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= {} 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,生效于 工作进程。
模型:一个请求、一个协程。
nginx+lua+redis实践 概述
nginx、Lua访问redis的三种方式:
1、HttpRedis模块
指令少、功能单一,适合简单的缓存。只支持get、select命令。
2、HttpRedis2Moudle模块
功能强大、比较灵活。
3、lua-resty-redis库
OpenResty。api。适合复杂业务,节省内存。
OpenResty:基于nginx开源版本的一个扩展版本。集成了大量精良的lua库。
OpenResty安装 进入yum资源文件
安装wget
下载资源库
1 wget https://openresty.org/package/centos/openresty.repo
检验资源库
得到文件
安装文件
启动openresty
1 2 /usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/
然后配置端口转发默认80端口
测试成功
初始测试lua
重新编写.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
修改端口转发
然后访问
redis安装 我们也是用yum来安装
首先安装epel-release
这个是可扩展的安装包,针对企业级linux
1 yum install epel-release
然后再安装redis
启动redis
启动redis客户端
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
查找客户端的命令
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服务器的设置。下面是对每个部分的解释:
worker_processes 1;
:这行指定了Nginx工作进程的数量。在这个例子中,只有一个工作进程。
events { ... }
:这个块定义了事件处理的相关设置。
worker_connections 1024;
:这行设置了每个工作进程允许的最大并发连接数为1024。
http { ... }
:这个块包含了HTTP服务器的配置。
include mime.types;
:这行包含了一个名为mime.types的文件,该文件定义了不同文件扩展名对应的MIME类型。
default_type application/octet-stream;
:这行设置了默认的MIME类型为application/octet-stream,适用于未知的文件类型。
sendfile on;
:这行启用了sendfile功能,允许高效地发送文件。
keepalive_timeout 65;
:这行设置了长连接的超时时间为65秒。
server { ... }
:这个块定义了一个虚拟主机的配置。
listen 80;
:这行指定了服务器监听的端口号为80。
server_name localhost;
:这行设置了服务器的名称为localhost。
root html;
:这行设置了服务器的根目录为html文件夹。
index index.html;
:这行设置了默认的索引文件为index.html。
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的位置。
location @fetch { ... }
:这个块定义了一个名为@fetch的位置,用于处理错误页面的请求。
root html;
:这行设置了错误页面的根目录为html文件夹。
先停止nginx
1 2 /usr/local/openresty/nginx/sbin/nginx -s stop
检查是否停掉
指定配置文件重新启动
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的键值对。
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>
扩展:
用于降级。
HttpRedis2Moudle使用 也是先备份conf然后编写
1 2 [root @localhost conf ] [root @localhost 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 /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 ] [root @localhost conf ] [root @localhost conf ]
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; } } }
指定配置文件的启动命令
修改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;
重新加载配置文件
配置端口转发进行访问测试
修改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;
然后重新加载并启动服务
分析OpenResty响应信息 目的:为了修改以后的响应信息。
以默认的方式打开nginx
然后配置端口转发访问虚拟机80端口然后访问
那么这个页面哪里来的呢?
在nginx里的html文件夹中的index.html
那为什么是展示这个页面呢?
在conf文件夹中的nginx.conf里面查看
server的新写法
openresty获取请求参数 备份一份新的配置文件
编写配置文件
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 ] [root @localhost 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
配置端口转发,访问测试
将请求参数写入redis 重新编写lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 local headers = ngx.req.get_headers();for k,v in pairs (headers)do ngx.say("[header] key:" ,k," value : " ,v); end
然后启动nginx
然后利用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 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
postman测试
nginx+lua获取body体参数 老操作
修改nginx配置文件
然后写lua文件
1 2 3 4 5 6 ngx.req.read_body(); local body = ngx.req.get_body_data();ngx.say(body);
然后重启
然后测试
nginx+lua+redis限流实战 先跑通基本环境,再实现具体业务
基本环境准备 编写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 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秒内不能被访问。
编写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();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限制。
流程图
放爬虫需求&步骤分析 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 ~] -bash : /usr/bin/reids-cli : No such file or directory[root @localhost ~] 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
核心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 local ip = ngx.var.remote_addr;ngx.log (ngx.INFO,"request ip is " ..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