目标
基于openresty 和 lua 参考apisix的后台实现一个自己的网关
那首先要搭建一个apisix 啊
我们希望实现的功能
在后台管理界面能够新增删除修改location upstream
并能够对这个upstream locatoin 进行对应的lua 代码脚本的编写集成,还能同时挂载多个的
最后这个还能挂载consul的对应的自动发现。
第三方资料:
官网:http://openresty.org/cn/dynamic-routing-based-on-redis.html
lua: https://www.runoob.com/lua/lua-tutorial.html
任务分解
- 环境搭建
- 技术研究 怎么通过 lua 发送 http 连接 redis 连接mysql
- 怎么连接consul
- lua 连接redis demo
- 连接mysql sql demo
读取所有的维护好的upstream location 结构
怎么记录日志
发送http请求 结合consul来做
连接consul
提供接口增删改查 upstream
提供接口增删改查 location
无刷新reload balance
提供服务对用的所有ip和端口接口
容器化部署
用openresty +lua 实现 限流,鉴权,mock ,返回值修改,脚本
鉴权
限流
interface接口维护模块开发
param参数
测试结果记录模块
日志问题排查手法
环境安装
在 Windows 上搭建环境
从 1.9.3.2 版本开始,OpenResty 正式对外同时公布维护了 Windows 版本,其中直接包含了编译好的最新版本 LuaJIT。由于 Windows 操作系统自身相对良好的二进制兼容性,使用者只需要下载、解压两个步骤即可。
打开 http://openresty.org,选择左侧的 Download
连接,这时候我们就可以下载最新版本的 OpenResty 版本(例如笔者写书时的最新版本:ngx_openresty-1.9.7.1-win32.zip)。下载本地成功后,执行解压缩,就能看到下图所示目录结构:
双击图中的 LuaJIT.exe,即可进入命令行模式,在这里我们就可以直接完成简单的 Lua 语法交互了。
在 Linux、Mac OS X 上搭建环境
到 LuaJIT 官网 http://luajit.org/download.html,查看当前最新开发版本,例如笔者写书时的最新版本:http://luajit.org/download/LuaJIT-2.1.0-beta1.tar.gz。
# wget http://luajit.org/download/LuaJIT-2.1.0-beta1.tar.gz
# tar -xvf LuaJIT-2.1.0-beta1.tar.gz
# cd LuaJIT-2.1.0-beta1
# make
# sudo make install
大家都知道,在不同平台,可能都有不同的安装工具来简化我们的安装。为什么我们这给大家推荐的是源码这么原始的方式?笔者为了偷懒么?答案:是的。当然还有另外一个原因,就是我们安装的是 LuaJIT 2.1 版本。
从实际应用性能表现来看,LuaJIT 2.1 虽然目前还是 beta 版本,但是生产运行稳定性已经很不错,并且在运行效率上要比 LuaJIT 2.0 好很多(大家可自行爬文了解一下),所以作为 OpenResty 的默认搭档,已经是 LuaJIT 2.1 很久了。但是针对不同系统的工具包安装工具,他们当前默认绑定推送的都还是 LuaJIT 2.0,所以这里就直接给出最符合我们最终方向的安装方法了。
由于LuaJIT 2.1 目前还是beta版本,所以在make install后,并没有进行luajit的符号连接,可以执行下面的指令将luajit-2.1.0-beta1和luajit进行软连接,从而可以直接使用luajit命令
ln -sf luajit-2.1.0-beta1 /usr/local/bin/luajit
验证 LuaJIT 是否安装成功
# luajit -v
LuaJIT 2.1.0-beta1 -- Copyright (C) 2005-2015 Mike Pall.
http://luajit.org/
如果想了解其他系统安装 LuaJIT 的步骤,或者安装过程中遇到问题,可以到 LuaJIT 官网查看:http://luajit.org/install.html
第一个“Hello World”
安装好 LuaJIT 后,开始我们的第一个 hello world 小程序。首先编写一个 hello.lua 文件,写入内容后,使用 LuaJIT 运行即可。
# cat hello.lua
print("hello world")
# luajit hello.lua
hello world
Lua 编辑器选择
一个好用趁手的编辑器可以为我们带来极大的工作效率提升,lua本身并不挑编辑器只是一个存文本. 但是如果有代码提示,方便的goto跳转,在我们理解别人的代码效率上将会有极大的提升.
我从最初的记事本编辑,vi,到后来的UE自定义语法高亮和函数列表,以及scite等寻找和尝试过能找到的绝大部分的lua编辑器. 我想在编辑器选择上面(linux下的不熟= =)应该比较有发言权.这里我主要讲我的环境是如何的.
选择过程我就不详述了,这里只讲解如果在你自己的windows上配置好ide
下载idea并配置
idea是一个java语言非常受好评的编辑器,但是并不是只支持java.
目前通过开放的插件编写已经支持绝大部分语言且使用的非常好用顺手,相信使用过的都会深有感受的.下载地址
其中Community版本是免费的,下载完后双击安装即可.
安装完成后打开File->Settings->Plugins在其中输入emmylua点击右边的install安装并重启idea至此一个包含lua语法提示和调整的编辑器环境就配置好了.
有关emmylua的详细帮助文档看这里
插件基本用法
1.方法提示
你可以在Setting里面配置鼠标移动到方法上后一定时间自动弹出2.快速跳转
在任何已经被定义的方法上按住Ctrl+鼠标点击该方法就可以自动打开和跳转到方法定义上面,非常方便
3.方法提示
在你输入识别的全局或者局部变量上面按点会自动出现可选方法做提示,不用记住所有的方法.进阶配置
由于emmylua并没有自带openresty的库函数,所以我们需要自己写函数提示,这里我提供我自己写的供你们下载和丰富.请丢到你的lualib根目录中
下面是一个简单的库函数定义示例
---语法: pid = ngx.worker.pid()
---
---语法: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*
---
---这个函数返回一个Lua数字,它是当前 Nginx 工作进程的进程 ID (PID)。这个 API 比 ngx.var.pid 更有效,ngx.var.VARIABLE API 不能使用的地方(例如 init_worker_by_lua),该 API 是可以的。
---@return number
function ngx.worker.pid()
end
方法提示不一定要使用独立的文件定义,可以直接在库里面定义,如: 至于里面的含义就要去这里看和理解拉.
总之如果你的库都定义好了方法提示,在你理解源码的时候将会非常方便快速.相信我
lua基础知识
基本类型
String 库
Lua 字符串库包含很多强大的字符操作函数。字符串库中的所有函数都导出在模块 string 中。在 Lua 5.1 中,它还将这些函数导出作为 string 类型的方法。这样假设要返回一个字符串转的大写形式,可以写成 ans = string.upper(s)
, 也能写成 ans = s:upper()
。为了避免与之前版本不兼容,此处使用前者。
Lua 字符串总是由字节构成的。Lua 核心并不尝试理解具体的字符集编码(比如 GBK 和 UTF-8 这样的多字节字符编码)。
需要特别注意的一点是,Lua 字符串内部用来标识各个组成字节的下标是从 1 开始的,这不同于像 C 和 Perl 这样的编程语言。这样数字符串位置的时候再也不用调整,对于非专业的开发者来说可能也是一个好事情,string.sub(str, 3, 7) 直接表示从第三个字符开始到第七个字符(含)为止的子串。
string.byte(s [, i [, j ]])
返回字符 s[i]、s[i + 1]、s[i + 2]、······、s[j] 所对应的 ASCII 码。i
的默认值为 1,即第一个字节,j
的默认值为 i 。
示例代码
print(string.byte("abc", 1, 3))
print(string.byte("abc", 3)) -- 缺少第三个参数,第三个参数默认与第二个相同,此时为 3
print(string.byte("abc")) -- 缺少第二个和第三个参数,此时这两个参数都默认为 1
-->output
97 98 99
99
97
由于 string.byte
只返回整数,而并不像 string.sub
等函数那样(尝试)创建新的 Lua 字符串, 因此使用 string.byte
来进行字符串相关的扫描和分析是最为高效的,尤其是在被 LuaJIT 2 所 JIT 编译之后。
string.char (...)
接收 0 个或更多的整数(整数范围:0~255),返回这些整数所对应的 ASCII 码字符组成的字符串。当参数为空时,默认是一个 0。
示例代码
print(string.char(96, 97, 98))
print(string.char()) -- 参数为空,默认是一个0,
-- 你可以用string.byte(string.char())测试一下
print(string.char(65, 66))
--> output
`ab
AB
此函数特别适合从具体的字节构造出二进制字符串。这经常比使用 table.concat
函数和 ..
连接运算符更加高效。
string.upper(s)
接收一个字符串 s,返回一个把所有小写字母变成大写字母的字符串。
示例代码
print(string.upper("Hello Lua")) -->output HELLO LUA
string.lower(s)
接收一个字符串 s,返回一个把所有大写字母变成小写字母的字符串。
示例代码
print(string.lower("Hello Lua")) -->output hello lua
string.len(s)
接收一个字符串,返回它的长度。
示例代码
print(string.len("hello lua")) -->output 9
使用此函数是不推荐的。应当总是使用 #
运算符来获取 Lua 字符串的长度。
由于 Lua 字符串的长度是专门存放的,并不需要像 C 字符串那样即时计算,因此获取字符串长度的操作总是 O(1)
的时间复杂度。
string.find(s, p [, init [, plain]])
在 s 字符串中第一次匹配 p 字符串。若匹配成功,则返回 p 字符串在 s 字符串中出现的开始位置和结束位置;若匹配失败,则返回 nil。 第三个参数 init 默认为 1,并且可以为负整数,当 init 为负数时,表示从 s 字符串的 string.len(s) + init + 1 索引处开始向后匹配字符串 p 。 第四个参数默认为 false,当其为 true 时,只会把 p 看成一个字符串对待。
示例代码
local find = string.find
print(find("abc cba", "ab"))
print(find("abc cba", "ab", 2)) -- 从索引为2的位置开始匹配字符串:ab
print(find("abc cba", "ba", -1)) -- 从索引为7的位置开始匹配字符串:ba
print(find("abc cba", "ba", -3)) -- 从索引为5的位置开始匹配字符串:ba
print(find("abc cba", "(%a+)", 1)) -- 从索引为1处匹配最长连续且只含字母的字符串
print(find("abc cba", "(%a+)", 1, true)) --从索引为1的位置开始匹配字符串:(%a+)
-->output
1 2
nil
nil
6 7
1 3 abc
nil
对于 LuaJIT 这里有个性能优化点,对于 string.find 方法,当只有字符串查找匹配时,是可以被 JIT 编译器优化的,有关 JIT 可以编译优化清单,大家可以参考 http://wiki.luajit.org/NYI,性能提升是非常明显的,通常是 100 倍量级。这里有个的例子,大家可以参考 https://groups.google.com/forum/m/#!topic/openresty-en/rwS88FGRsUI。
string.format(formatstring, ...)
按照格式化参数 formatstring,返回后面 ...
内容的格式化版本。编写格式化字符串的规则与标准 c 语言中 printf 函数的规则基本相同:它由常规文本和指示组成,这些指示控制了每个参数应放到格式化结果的什么位置,及如何放入它们。一个指示由字符 %
加上一个字母组成,这些字母指定了如何格式化参数,例如 d
用于十进制数、x
用于十六进制数、o
用于八进制数、f
用于浮点数、s
用于字符串等。在字符 %
和字母之间可以再指定一些其他选项,用于控制格式的细节。
示例代码
print(string.format("%.4f", 3.1415926)) -- 保留4位小数
print(string.format("%d %x %o", 31, 31, 31))-- 十进制数31转换成不同进制
d = 29; m = 7; y = 2015 -- 一行包含几个语句,用;分开
print(string.format("%s %02d/%02d/%d", "today is:", d, m, y))
--1、%d就是普通的输出了
--2、% 2d是将数字按宽度为2,采用右对齐方式输出,若数据位数不到2位,则左边补空格。如下:
--3、% 02d,和% 2d差不多,只不过左边补0
--4、%.2d从执行效果来看,和% 02d一样
-->output
3.1416
31 1f 37
today is: 29/07/2015
string.match(s, p [, init])
在字符串 s 中匹配(模式)字符串 p,若匹配成功,则返回目标字符串中与模式匹配的子串;否则返回 nil。第三个参数 init 默认为 1,并且可以为负整数,当 init 为负数时,表示从 s 字符串的 string.len(s) + init + 1 索引处开始向后匹配字符串 p。
示例代码
print(string.match("hello lua", "lua"))
print(string.match("lua lua", "lua", 2)) --匹配后面那个lua
print(string.match("lua lua", "hello"))
print(string.match("today is 27/7/2015", "%d+/%d+/%d+"))
-->output
lua
lua
nil
27/7/2015
string.match
目前并不能被 JIT 编译,应 尽量 使用 ngx_lua 模块提供的 ngx.re.match
等接口。
string.gmatch(s, p)
返回一个迭代器函数,通过这个迭代器函数可以遍历到在字符串 s 中出现模式串 p 的所有地方。
示例代码
s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do --匹配最长连续且只含字母的字符串
print(w)
end
-->output
hello
world
from
Lua
t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%a+)=(%a+)") do --匹配两个最长连续且只含字母的
t[k] = v --字符串,它们之间用等号连接
end
for k, v in pairs(t) do
print (k,v)
end
-->output
to Lua
from world
此函数目前并不能被 LuaJIT 所 JIT 编译,而只能被解释执行。应 尽量 使用 ngx_lua 模块提供的 ngx.re.gmatch
等接口。
string.rep(s, n)
返回字符串 s 的 n 次拷贝。
示例代码
print(string.rep("abc", 3)) --拷贝3次"abc"
-->output abcabcabc
string.sub(s, i [, j])
返回字符串 s 中,索引 i 到索引 j 之间的子字符串。当 j 缺省时,默认为 -1,也就是字符串 s 的最后位置。i 可以为负数。当索引 i 在字符串 s 的位置在索引 j 的后面时,将返回一个空字符串。
示例代码
print(string.sub("Hello Lua", 4, 7))
print(string.sub("Hello Lua", 2))
print(string.sub("Hello Lua", 2, 1)) --看到返回什么了吗
print(string.sub("Hello Lua", -3, -1))
-->output
lo L
ello Lua
Lua
如果你只是想对字符串中的单个字节进行检查,使用 string.char
函数通常会更为高效。
string.gsub(s, p, r [, n])
将目标字符串 s 中所有的子串 p 替换成字符串 r。可选参数 n,表示限制替换次数。返回值有两个,第一个是被替换后的字符串,第二个是替换了多少次。
示例代码
print(string.gsub("Lua Lua Lua", "Lua", "hello"))
print(string.gsub("Lua Lua Lua", "Lua", "hello", 2)) --指明第四个参数
-->output
hello hello hello 3
hello hello Lua 2
此函数不能为 LuaJIT 所 JIT 编译,而只能被解释执行。一般我们推荐使用 ngx_lua 模块提供的 ngx.re.gsub
函数。
string.reverse (s)
接收一个字符串 s,返回这个字符串的反转。
示例代码
print(string.reverse("Hello Lua")) --> output: auL olleH
字符串相关
java String。定义 简单字符 含特殊字符 相加。查找 分割
local str1 = 'hello world'
local str2 = "hello lua"
local str3 = [["add\name",'hello']]
local str4 = [=[string have a [[]].]=]
print(str1) -->output:hello world
print(str2) -->output:hello lua
print(str3) -->output:"add\name",'hello'
print(str4) -->output:string have a [[]].print(string.find("haha",'ah') ) -->2. 索引是从1开始的
local strval = "123123123123"
print(strval)
print(strval.."123123")
print(string.find("haha",'ah') ) -->2. 索引是从1开始的
number
(java的int double float )
print(type("hello world")) -->output:string
print(type(print)) -->output:function
print(type(true)) -->output:boolean
print(type(360.0)) -->output:number
print(type(360)) -->output:number
print(type(360.123123)) -->output:number
print(type(nil)) -->output:nil
string
function
boolean
number
number
number
nil
local order = 3.99
local score = 98.01
print(math.floor(order)) -->output:3
print(math.ceil(score)) -->output:99
float 转 int 取整
local order = 3.99
local score = 98.01
print(math.floor(order)) -->output:3
print(math.ceil(score)) -->output:99
date
日期时间函数
在 Lua 中,函数 time、date 和 difftime 提供了所有的日期和时间功能。
在 OpenResty 的世界里,不推荐使用这里的标准时间函数,因为这些函数通常会引发不止一个昂贵的系统调用,同时无法为 LuaJIT JIT 编译,对性能造成较大影响。推荐使用 ngx_lua 模块提供的带缓存的时间接口,如 ngx.today
, ngx.time
, ngx.utctime
, ngx.localtime
, ngx.now
, ngx.http_time
,以及 ngx.cookie_time
等。
所以下面的部分函数,简单了解一下即可。
os.time ([table])
如果不使用参数 table 调用 time 函数,它会返回当前的时间和日期(它表示从某一时刻到现在的秒数)。如果用 table 参数,它会返回一个数字,表示该 table 中 所描述的日期和时间(它表示从某一时刻到 table 中描述日期和时间的秒数)。table 的字段如下:
字段名称 | 取值范围 |
---|---|
year | 四位数字 |
month | 1--12 |
day | 1--31 |
hour | 0--23 |
min | 0--59 |
sec | 0--61 |
isdst | boolean(true表示夏令时) |
对于 time 函数,如果参数为 table,那么 table 中必须含有 year、month、day 字段。其他字缺省时段默认为中午(12:00:00)。
示例代码:(地点为北京)
print(os.time()) -->output 1438243393
a = { year = 1970, month = 1, day = 1, hour = 8, min = 1 }
print(os.time(a)) -->output 60
os.difftime (t2, t1)
返回 t1 到 t2 的时间差,单位为秒。
示例代码:
local day1 = { year = 2015, month = 7, day = 30 }
local t1 = os.time(day1)
local day2 = { year = 2015, month = 7, day = 31 }
local t2 = os.time(day2)
print(os.difftime(t2, t1)) -->output 86400
os.date ([format [, time]])
把一个表示日期和时间的数值,转换成更高级的表现形式。其第一个参数 format 是一个格式化字符串,描述了要返回的时间形式。第二个参数 time 就是日期和时间的数字表示,缺省时默认为当前的时间。使用格式字符 "*t",创建一个时间表。
示例代码:
local tab1 = os.date("*t") --返回一个描述当前日期和时间的表
local ans1 = "{"
for k, v in pairs(tab1) do --把tab1转换成一个字符串
ans1 = string.format("%s %s = %s,", ans1, k, tostring(v))
end
ans1 = ans1 .. "}"
print("tab1 = ", ans1)
local tab2 = os.date("*t", 360) --返回一个描述日期和时间数为360秒的表
local ans2 = "{"
for k, v in pairs(tab2) do --把tab2转换成一个字符串
ans2 = string.format("%s %s = %s,", ans2, k, tostring(v))
end
ans2 = ans2 .. "}"
print("tab2 = ", ans2)
-->output
tab1 = { hour = 17, min = 28, wday = 5, day = 30, month = 7, year = 2015, sec = 10, yday = 211, isdst = false,}
tab2 = { hour = 8, min = 6, wday = 5, day = 1, month = 1, year = 1970, sec = 0, yday = 1, isdst = false,}
该表中除了使用到了 time 函数参数 table 的字段外,这还提供了星期(wday,星期天为 1)和一年中的第几天(yday,一月一日为 1)。 除了使用 "*t" 格式字符串外,如果使用带标记(见下表)的特殊字符串,os.date 函数会将相应的标记位以时间信息进行填充,得到一个包含时间的字符串。 表如下:
格式字符 | 含义 |
---|---|
%a | 一星期中天数的简写(例如:Wed) |
%A | 一星期中天数的全称(例如:Wednesday) |
%b | 月份的简写(例如:Sep) |
%B | 月份的全称(例如:September) |
%c | 日期和时间(例如:07/30/15 16:57:24) |
%d | 一个月中的第几天[01 ~ 31] |
%H | 24小时制中的小时数[00 ~ 23] |
%I | 12小时制中的小时数[01 ~ 12] |
%j | 一年中的第几天[001 ~ 366] |
%M | 分钟数[00 ~ 59] |
%m | 月份数[01 ~ 12] |
%p | “上午(am)”或“下午(pm)” |
%S | 秒数[00 ~ 59] |
%w | 一星期中的第几天[1 ~ 7 = 星期天 ~ 星期六] |
%x | 日期(例如:07/30/15) |
%X | 时间(例如:16:57:24) |
%y | 两位数的年份[00 ~ 99] |
%Y | 完整的年份(例如:2015) |
%% | 字符'%' |
示例代码:
print(os.date("today is %A, in %B"))
print(os.date("now is %x %X"))
-->output
today is Thursday, in July
now is 07/30/15 17:39:22
数组
key = ""
function PrintTable(table , level)
level = level or 1
local indent = ""
for i = 1, level do
indent = indent.." "
end
if key ~= "" then
print(indent..key.." ".."=".." ".."{")
else
print(indent .. "{")
end
key = ""
for k,v in pairs(table) do
if type(v) == "table" then
key = k
PrintTable(v, level + 1)
else
local content = string.format("%s%s = %s", indent .. " ",tostring(k), tostring(v))
print(content)
end
end
print(indent .. "}")
end
--定义
local a = { 1, 2, 3, 4}
--下标访问
print(a[1], a[2], a[3], a[4])
--删除一个元素
table.remove (a , 3)
print(a)
--添加一个元素
table.insert (a, 3 ,3)
--打印长度
print("Test1 " .. table.getn(a))
--合并
b={5,6,7,8}
for k,v in pairs(b) do
table.insert (a,table.getn(a)+1, v)
a[k] = v
end
PrintTable(a,14)
map
key = ""
function PrintTable(table , level)
level = level or 1
local indent = ""
for i = 1, level do
indent = indent.." "
end
if key ~= "" then
print(indent..key.." ".."=".." ".."{")
else
print(indent .. "{")
end
key = ""
for k,v in pairs(table) do
if type(v) == "table" then
key = k
PrintTable(v, level + 1)
else
local content = string.format("%s%s = %s", indent .. " ",tostring(k), tostring(v))
print(content)
end
end
print(indent .. "}")
end
--定义
local a = { a=1, b=2, c=3, d=4}
PrintTable(a,3)
--下标访问
print("下标访问")
print(a["a"], a["b"], a["c"], a["d"])
--删除一个元素
print("删除一个元素")
--table.remove (a , "a")
a["a"]=nil
PrintTable(a,3)
--添加一个元素
print("添加元素")
--table.insert (a, 1 ,5)
a["f"]=3
PrintTable(a,3)
--打印长度
print("Test1 " .. table.getn(a))
--合并
b={5,6,7,8}
for k,v in pairs(b) do
--table.insert (a,table.getn(a)+1, v)
a[k] = v
end
PrintTable(a,14)
boolean 相关
布尔类型,可选值 true/false;Lua 中 nil 和 false 为“假”,其它所有值均为“真”。比如 0 和空字符串就是“真”;C 或者 Perl 程序员或许会对此感到惊讶。
local a = true
local b = 0
local c = nil
if a then -->a =true
print("a") -->output:a
else
print("not a") --这个没有执行
end
if b then -->b=0
print("b") -->output:b
else
print("not b") --这个没有执行
end
if c then -->c =nil
print("c") --这个没有执行
else
print("not c") -->output:not c
end
对象
你需要先熟悉下table
先介绍下--元表
在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。
因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。
例如,使用元表我们可以定义Lua如何计算两个table的相加操作a+b。
当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。
setmetatable(table, metatable):对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
getmetatable(table):此方法用于获取表的元表对象。
__index 元方法
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。
我们可以在使用 lua 命令进入交互模式查看:
other = { foo = 3 }
t = setmetatable({a=1,b=3}, { __index = other })
print(t.a)
print(t.foo)
--output
1
3
animal = { age = 3 }
dog= { legs= 4}
xiaodog = setmetatable(dog,
{__index =animal}
)
print(xiaodog.age)
print(xiaodog.legs)
如果index 是function的话 还能自定义
other = { foo = 3 }
y = setmetatable({a=1,b=3},
{__index = function(self, key) --重载函数
if key == "key2" then
return "metatablevalue"
end
end
}
)
print(y.a)
print(y.key2)
--output
1
metatablevalue
重载添加方法
local set1 = {10, 20, 30} -- 集合
local set2 = {20, 40, 50} -- 集合
-- 将用于重载__add的函数,注意第一个参数是self
local union = function (self, another)
print("over write add method")
local set = {}
local result = {}
-- 利用数组来确保集合的互异性
for i, j in pairs(self) do set[j] = true end
for i, j in pairs(another) do set[j] = true end
-- 加入结果集合
for i, j in pairs(set) do table.insert(result, i) end
return result
end
setmetatable(set1, {__add = union}) -- 重载 set1 表的 __add 元方法
local set3 = set1 + set2 ---会触发重载的方法。因为覆盖__add方法
for _, j in pairs(set3) do
io.write(j.." ") -->output:30 50 20 40 10
end
发现在调用+法的时候进入了我们自己的方法里
over write add method
30 50 20 40 10
面向对象特征
- 1) 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
- 2) 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 3) 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- 4)抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
Lua 中面向对象
我们知道,对象由属性和方法组成。LUA中最基本的结构是table,所以需要用table来描述对象的属性。
lua 中的 function 可以用来表示方法。那么LUA中的类可以通过 table + function 模拟出来。
至于继承,可以通过 metetable 模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)。
Lua 中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。对象有他们的成员函数,表也有:
Account = {balance = 0}
function Account.withdraw (v)
Account.balance = Account.balance - v
end
这个定义创建了一个新的函数,并且保存在Account对象的withdraw域内,下面我们可以这样调用:
Account.withdraw(100.00)
print(Account.balance)
结果
-100
这里插入说下.号和:号的区别
.号的话要显示的传参数
:号的话可以缺省传参数, 函数里的self.x 就可以调用到自身的变量.
Account = {balance = 0}
function Account.show (self)
print(self.balance)
end
function Account:show1 ()
print(self.balance)
end
Account.show(Account); --用点好必须要显示传参数
Account:show1();
一个简单实例
以下简单的类包含了三个属性: area, length 和 breadth,printArea方法用于打印计算结果:
-- 元类
Rectangle = {area = 0, length = 0, breadth = 0}
-- 派生类的方法 new
function Rectangle:new (o,length,breadth)
o = o or {}
setmetatable(o, self) ------这里相当于吧自己的一些属性全部拷贝到对象里
self.__index = self
self.length = length or 0
self.breadth = breadth or 0
self.area = length*breadth;
return o
end
-- 派生类的方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
创建对象
创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
r = Rectangle:new(nil,10,20)
访问属性
我们可以使用点号(.)来访问类的属性:
print(r.length)
访问成员函数
我们可以使用冒号 : 来访问类的成员函数:
r:printArea()
内存在对象初始化时分配。
完整实例
以下我们演示了 Lua 面向对象的完整实例:
-- 元类
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)。 ---side是变长
o = o or {}
setmetatable(o, self)。 ----重载
self.__index = self
side = side or 0
self.area = side*side; ---面积
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
local t={"attr1":10}
myshape = Shape:new(t,10)
print(mishap.attr1)
执行以上程序,输出结果为:
面积为 100
Lua 继承
继承是指一个对象直接使用另一对象的属性和方法。可用于扩展基础类的属性和方法。
以下演示了一个简单的继承实例:
-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
接下来的实例,Square 对象继承了 Shape 类:
Square = Shape:new()
-- Derived class method new
function Square:new (o,side)
o = o or Shape:new(o,side)
setmetatable(o, self)
self.__index = self
return o
end
完整实例
以下实例我们继承了一个简单的类,来扩展派生类的方法,派生类中保留了继承类的成员变量和方法:
实例
-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
Square = Shape:new()
-- 派生类方法 new
function Square:new (o,side)
o = o or Shape:new(o,side)
setmetatable(o, self)
self.__index = self
return o
end
-- 派生类方法 printArea
function Square:printArea ()
print("正方形面积为 ",self.area)
end
-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()
Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
o = o or Shape:new(o)
setmetatable(o, self)
self.__index = self
self.area = length * breadth
return o
end
-- 派生类方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()
执行以上代码,输出结果为:
面积为 100
正方形面积为 100
矩形面积为 200
函数重写
Lua 中我们可以重写基础类的函数,在派生类中定义自己的实现方式:
-- 派生类方法 printArea
function Square:printArea ()
print("正方形面积 ",self.area)
end
内部变量与 全局变量
Lua 中的局部变量要用 local 关键字来显式定义,不使用 local 显式定义的变量就是全局变量:
g_var = 1 -- global var
local l_var = 2 -- local var
判断是否为空
http请求
文件读写
file = io.input("test1.txt") -- 使用 io.input() 函数打开文件
repeat
line = io.read() -- 逐行读取内容,文件结束时返回nil
if nil == line then
break
end
print(line)
until (false)
io.close(file)
file = io.open("test2.txt", "a") -- 使用 io.open() 函数,以添加模式打开文件
file:write("\nhello world") -- 使用 file:write() 函数,在文件末尾追加内容
file:close()
网络请求
创建线程
线程池
环境搭建 -安装openResty
参考:https://blog.csdn.net/qq_30336433/article/details/88733988
安装 OpenResty
开始安装 192.168.213.7
参考: https://blog.csdn.net/qq_30336433/article/details/88733988
参考:https://gitee.com/iresty/apisix
https://www.runoob.com/w3cnote/openresty-intro.html
官网: http://openresty.org/cn/linux-packages.html
sudo yum install yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repoopen
相关依赖包的安装
apt-get install libreadline-dev libncurses5-dev libpcre3-dev \
libssl-dev perl make build-essential
yum安装
sudo yum install openresty
#命令行工具 resty
sudo yum install openresty-resty
源码方式安装
1 下载安装包
http://openresty.org/cn/download.html
2 并激活luajit
、http_iconv_module
并禁止 http_redis2_module
组件。
./configure --prefix=/opt/openresty\
--with-luajit\
--without-http_redis2_module \
--with-http_iconv_module
gmake
gmake install
3 进行安装gmake install
4 设置环境变量
打开文件 /etc/profile, 在文件末尾加入export PATH=$PATH:/opt/openresty/nginx/sbin
source /etc/profile 生效
然后下载lua包管理器
http://luarocks.github.io/luarocks/releases/luarocks-3.0.4.tar.gz
下载完后解压
./configure --prefix=/usr/local/openresty/luajit --with-lua=/usr/local/openresty/luajit --lua-suffix=luajit --with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1
./configure --prefix=/usr/local/openresty/luajit --with-lua=/usr/local/openresty/luajit --lua-suffix=luajit --with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1
make build
make install
目录结构
安装完成之后的目录结构如下
/usr/local/openresty/
bin #安装的主目录
luajit
lualib
nginx
pod
site
查看错误
tail -fn 200 /usr/local/openresty/nginx/logs/access.log
tail -fn 200 /usr/local/openresty/nginx/logs/error.log
目录和服务启动关闭
openresty是nginx的luaJit的扩展,openresty的启动、停止、启动操作,实际执行nginx的操作就可以了。
启动命令openresty
重启 openresty -s reload
停止 openresty - stop
启动:nginx -c <configuration file>
快速停止nginx:nginx -s stop
完整有序的停止nginx:nginx -s quit
修改配置后重新加载生效:nginx -s reload
重新打开日志文件:nginx -s reopen
实际测试中发现代码改了之后为什么只有停止 再开启才有效
luarocket包管理器简单应用
http://openresty.org/cn/using-luarocks.html
helloworld
content_by_lua
nginx 如何嵌入 lua 脚本。方法就是在nginx的配置文件nginx.conf 中使用 content_by_lua 或者 cotent_by_lua_file 指令:
content_by_lua 一般在很简单的lua脚本时使用:
location /lua {
set $test "hello, world.";
content_by_lua '
ngx.header.content_type = "text/plain";
ngx.say(ngx.var.test);
';
}
看到页面有输出 hello.world
content_by_lua_file
cotent_by_lua_file 适应于复杂的 lua 脚本,专门放入一个文件中:
location /lua2 {
#lua_code_cache off;
content_by_lua_file lua/hello.lua;
}
创建lua脚本
/usr/local/openresty/lualib/lua/hello.lua
内容如下
ngx.header.content_type = "text/plain"; --//不加会浏览器下载
ngx.say('hello ngx_lua!!!!');
发送http请求
/us r/local/openresty/lualib/lua/http.lua
-- 引入 http 包
ngx.header.content_type = "text/plain";
local http = require "resty.http"
local httpc = http:new()
local cjson = require "cjson"
local my_json = [[{"my_array":[]}]] --定义个json 字符串
local t = cjson.decode(my_json) --转成table
local res, err = httpc:request_uri("http://127.0.0.1/lua", { --请求之前的接口
method = "POST",
body = "a=1&b=2",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
})
if not res then
ngx.say("fail to request ", err)
end
ngx.say(res.body)
ngx.say(res.status)
ngin x.conf 增加内容
vi /usr/local/openresty/nginx/conf/nginx.conf
location /http {
content_by_lua_file /usr/local/openresty/lualib/lua/http.lua;
}
通过url请求
127.0.0.1/http 测试 没有问题;
如果把之前的域名改成 http://www.baidu.com
值得注意的是 域名无法解析
需要在nginx.conf里增加resolve 8.8.8.8; 增加dns 解析.
连接redis
应用方向快速的查询 置换token
github 地址:https://github.com/openresty/lua-resty-redis
upstream ip指定了端口和ip地址
location /xxxxx{
proxy_pass: xxx-srv:/
}
local redis = require "resty.redis" --依赖包
local red = redis:new() ---创建客户端
red:set_timeout(6000) -- 1 sec
local ok,err=red.connect(red,'192.168.213.91',6379) --连接地址和端口
if not ok then
ngx.say("failed to connect:",err) ---如果连接错误打印
return
end
ok, err = red:auth("Awifi@123") --输入redis 密码
if not ok then
ngx.say("failed to connect: ", err) --密码验证失败
return
end
ok, err = red:set("dog", "an animal") --设置key dog
if not ok then
ngx.say("failed to set dog: ", err) --
return
end
ngx.say("set result: ", ok)
local res, err = red:get("dog") --取出刚才设置的值
if not res then
ngx.say("failed to get dog: ", err)
return
end
if res == ngx.null then
ngx.say("dog not found.")
return
end
ngx.say("dog: ", res) ---打印redis 中dog的值
red:init_pipeline() --批量提交。
red:set("cat", "Marry") ----
red:set("horse", "Bob")
red:get("cat")
red:get("horse")
local results, err = red:commit_pipeline()
if not results then
ngx.say("failed to commit the pipelined requests: ", err)
return
end
for i, res in ipairs(results) do
if type(res) == "table" then
if res[1] == false then
ngx.say("failed to run command ", i, ": ", res[2])
else
-- process the table value
end
else
-- process the scalar value
end
end
-- put it into the connection pool of size 100,
-- with 10 seconds max idle time
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.say("failed to set keepalive: ", err)
return
end
-- or just close the connection right away:
-- local ok, err = red:close()
-- if not ok then
-- ngx.say("failed to close: ", err)
-- return
-- end
连接mysql
把upstream 通过后台接口维护
把location 通过后台接口维护
连接consul
通过http方式获取所有service 对应的ip端口 11.20 - 11.22
并能监听变化产生 11.20 - 11.22
把自己也注册到consul上 11.20 - 11.22
vi /usr/local/openresty/nginx/servers/test_openresty.conf
#lua_package_path "/usr/local/openresty/lualib/?.lua;;";
#lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
#lua_package_path "/usr/local/lib/lua/5.3/?.lua;;";
#lua_package_cpath "/usr/local/share/lua/5.3/?.so;;";
lua_shared_dict upstream_list 10m;
# 第一次初始化
init_by_lua_block {
local upstreams = require "upstreams";
upstreams.update_upstreams();
}
# 定时拉取配置
init_worker_by_lua_block {
local upstreams = require "upstreams";
local handle = nil;
handle = function ()
--TODO:控制每次只有一个worker执行
upstreams.update_upstreams();
ngx.timer.at(5, handle);
end
ngx.timer.at(5, handle);
}
upstream push_socket_proxy {
server 0.0.0.1:3000 ; #占位server
#hash $remote_addr consistent;
balancer_by_lua_block {
local balancer = require "ngx.balancer";
local upstreams = require "upstreams";
local tmp_upstreams = upstreams.get_upstreams();
#local ip_port = tmp_upstreams[math.random(1, table.getn(tmp_upstreams))];
local ip_port ={}
balancer.set_current_peer(ip_port.ip, ip_port.port);
}
#hash $remote_addr consistent;
#server 127.0.0.1:6999 wight=5 max_fails=3 fail_timeout=30s;
}
server {
listen 39999 ;
proxy_connect_timeout 5s;
proxy_timeout 5s;
proxy_pass push_socket_proxy;
}
vi /usr/local/openresty/lualib/upstreams.lua
local http = require "socket.http"
local ltn12 = require "ltn12"
local cjson = require "cjson"
local _M = {}
_M._VERSION="0.1"
function _M:update_upstreams()
local resp = {}
http.request{
url = "http://192.168.212.74:8500/v1/catalog/service/opf-message-push-online-message", sink = ltn12.sink.table(resp)
}
local resp = table.concat(resp);
local resp = cjson.decode(resp);
local upstreams = {}
for i, v in ipairs(resp) do
upstreams[i] = {ip=v.ServiceAddress, port=v.ServicePort}
end
ngx.shared.upstream_list:set("api_list", cjson.encode(upstreams))
end
function _M:get_upstreams()
local upstreams_str = ngx.shared.upstream_list:get("api_list");
local tmp_upstreams = cjson.decode(upstreams_str);
print(upstreams_str);
return tmp_upstreams;
end
改造成 resty.http 版本
local http = require("resty.http")
local httpc = http:new()
local ltn12 = require "ltn12"
local cjson = require "cjson"
local _M = {}
_M._VERSION="0.1"
function _M:update_upstreams()
local resp,err = httpc:request_uri("http://192.168.212.74:8500",
{
method = "GET", ---请求方式
path="/v1/catalog/service/opf-message-push-online-message",
--path="/search_product.htm?q=ipone",
--query="q=iphone", ---get方式传参数
--body="name='jack'&age=18", ---post方式传参数
--path="/search_product.htm", ----路径
---header参数
--headers = {["User-Agent"]="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36"}
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
})
if not resp then
print("请求consul 失败!!!!!!!!!!!!!!!")
return
end
local resp = cjson.decode(resp);
local upstreams = {}
for i, v in ipairs(resp) do
upstreams[i] = {ip=v.ServiceAddress, port=v.ServicePort}
end
ngx.shared.upstream_list:set("api_list", cjson.encode(upstreams))
end
function _M:get_upstreams()
local upstreams_str = ngx.shared.upstream_list:get("api_list");
local tmp_upstreams = cjson.decode(upstreams_str);
print(upstreams_str);
return tmp_upstreams;
end
return _M
开始测试
vi /usr/local/openresty/nginx/conf/nginx.conf
location /luahttp{
default_type text/plain;
# content_by_lua_file /usr/local/openresty/lualib/https/http.lua;
content_by_lua_file /usr/local/openresty/lualib/upstreams-test.lua ;
}
debug version
--local http = require "resty.http"
--local httpc = http.new()
local ltn12 = require "ltn12"
local cjson = require "cjson"
local _M = {}
_M._VERSION="0.1"
function _M:update_upstreams()
local upstreams = {}
upstreams[0] = {ip="127.0.0.1", port=6999}
upstreams[1] = {ip="192.168.224.239", port=6999}
ngx.shared.upstream_list:set("api_list", cjson.encode(upstreams))
end
function _M:get_upstreams()
local upstreams_str = ngx.shared.upstream_list:get("api_list");
local tmp_upstreams = cjson.decode(upstreams_str);
print(upstreams_str);
return tmp_upstreams;
end
return _M
debug版本 nginx.conf
stream {
log_format proxy '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time ["$upstream_addr"] '
'"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
access_log logs/access_8000.log proxy ;
upstream push_socket_proxy {
server 127.0.0.1:6999 ; #占位server
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local ok,err = balancer.set_current_peer("127.0.0.1", 6999);
-- if not ok then
-- ngx.log(ngx.ERR, "failed to set the current peer: ", err)
-- return ngx.exit(1000)
-- end
-- ngx.log(ngx.ERROR,"success to set the current peer")
-- end
}
# hash $remote_addr consistent;
}
server {
listen 39999 ;
proxy_connect_timeout 5s;
proxy_timeout 5s;
proxy_pass push_socket_proxy;
}
#include /usr/local/openresty/nginx/servers/*.conf;
}
参考资料:
https://www.bilibili.com/video/BV1tb411j7vr?p=18
基于openresty的 无reload balance : https://segmentfault.com/a/1190000022646856?utm_source=tag-newest
为什么我们需要网关 自定义 的一些转发功能 具体的可以参考kong api 能干什么
实际上我有个疑问为什么 要接consul 就为了服务主动发现服务吗
也就是如果我的服务起了 nginx 就主动转到注册上的服务上
是这个意思吧 那还要feign 干什么 相当于 他承担蓝了 feign 的作用 去发现可用的ip 和端口
也就是我在nginx里 可以不用配置upstream
upstream smarthome-app-srv {
server 192.168.213.91:8100;
}
列如这里我配置的upstream smarthome-app-srv服务
哪是否我可以不配置这个东西呢 ,让nginx 实时去consul 注册中心去发现这个服务对应的ip地址和端口呢.
我这里百度到了一篇文章
https://segmentfault.com/a/1190000022646856?utm_source=tag-newest
从consul service
一个name 对应好几台服务器 ip 端口
要确保安装了unzip
yum install unzip
./luarocks install luasocket
到了这一步我们需要来了解下所有的consul api 接口
https://blog.csdn.net/jijiuqiu6646/article/details/89456328
consul 所有接口
/v1/agent/checks : 返回本地agent注册的所有检查(包括配置文件和HTTP接口)
/v1/agent/services : 返回本地agent注册的所有 服务
/v1/agent/members : 返回agent在集群的gossip pool中看到的成员
/v1/agent/self : 返回本地agent的配置和成员信息
/v1/agent/join/<address> : 触发本地agent加入node
/v1/agent/force-leave/<node>>: 强制删除node
/v1/agent/check/register : 在本地agent增加一个检查项,使用PUT方法传输一个json格式的数据
/v1/agent/check/deregister/<checkID> : 注销一个本地agent的检查项
/v1/agent/check/pass/<checkID> : 设置一个本地检查项的状态为passing
/v1/agent/check/warn/<checkID> : 设置一个本地检查项的状态为warning
/v1/agent/check/fail/<checkID> : 设置一个本地检查项的状态为critical
/v1/agent/service/register : 在本地agent增加一个新的服务项,使用PUT方法传输一个json格式的数据
/v1/agent/service/deregister/<serviceID> : 注销一个本地agent的服务项
/v1/catalog/register : 注册一个新节点、服务或检查
/v1/catalog/deregister : 取消注册节点、服务或检查
/v1/catalog/datacenters : 列出已知数据中心
/v1/catalog/nodes : 列出给定DC中的节点
/v1/catalog/services : 列出给定DC中的服务
/v1/catalog/service/<service> : 列出给定服务中的节点
/v1/catalog/node/<node> : 列出节点提供的服务
curl monitor.odc.consul.cn/v1/catalog/nodes --user edsp:edsp | python -m json.tool| grep "Node" | awk -F'"' '{print 2}' ## 分隔出服务名
https://www.jianshu.com/p/1571affd1e2d
https://gitee.com/iresty/apisix
http://139.217.190.60/user/login?redirect=%2F
服务网关
++++++++++++++++++++++++++++++++++++++++++++++++++0000000000000000000000000000000000000000000
2020年10月25日 服务网关设计
常用命令
cd /usr/local/openresty/
tail -fn 200 /usr/local/openresty/nginx/logs/error.log
vi /usr/local/openresty/nginx/conf/nginx.conf
自研 a网关
https://blog.csdn.net/molaifeng/article/details/106150688
http 参数
定义一个接口可以动态添加stream 和location
通过给定的参数
获取url参数 ngx.var.arg_xx与ngx.req.get_uri_args["xx"]两者都是为了获取请求uri中的参数,例如
?strider=1 为了获取输入参数strider,以下两种方法都可以:
local strider = ngx.var.arg_strider
local strider = ngx.req.get_uri_args["strider"] 第二种测式会报错
差别在于,当请求uri中有多个同名参数时,ngx.var.arg_xx的做法是取第一个出现的值,ngx.req_get_uri_args["xx"]的做法是返回一个table,该table里存放了该参数的所有值,例如,当请求uri为:
?strider=1&strider=2&strider=3&strider=4
ngx.var.arg_strider的值为"1",而ngx.req.get_uri_args["strider"]的值为table ["1", "2", "3", "4"]。
因此,ngx.req.get_uri_args属于ngx.var.arg_的增强。
获取post参数
ngx.req.read_body()
local postargs = ngx.req.get_post_args()
动态增删改查stream
思路:
1 数据录入
1.1 添加stream信息
数据格式为
location url 括号里的内容 proxy_pass stream名称
stream 维护在一个表里 location 的表里有stream 字段
1.2 添加location 路由匹配信息
1.3 location 绑定 stream
后台动态添加模块或者脚本
2数据读取和缓存 即更新生效
3删除
参考https://www.jianshu.com/p/1d56d5e1fc43
/usr/local/openresty/lualib/push/push.lua: in main chunk, client: 192.168.213.91, server: localhost, request: "GET /alloc HTTP/1.0", host: "192.168.213.7"
2020/11/24 14:53:00 [error] 27802#27802: *263 lua entry thread aborted: runtime error: content_by_lua(nginx.conf:155):3: attempt to call field 'get_uri_args' (a nil value)
stack traceback:
coroutine 0:
启动过程
新建一个 test_openresty.conf的文件在 nginx/conf目录下内容如下
# 设置纯 Lua 扩展库的搜寻路径(';;' 是默认路径):
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
# 设置 C 编写的 Lua 扩展模块的搜寻路径(也可以用 ';;'):
#https://www.cnblogs.com/mentalidade/p/6958326.html
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
lua_shared_dict upstream_list 10m;
# 第一次初始化
init_by_lua_block {
local upstreams = require "upstreams";
upstreams.update_upstreams();
}
# 定时拉取配置
init_worker_by_lua_block {
local upstreams = require "upstreams";
local handle = nil;
handle = function ()
--TODO:控制每次只有一个worker执行
ngx.log("")
upstreams.update_upstreams();
ngx.timer.at(5, handle);
end
ngx.timer.at(5, handle);
}
upstream api_server {
#server 0.0.0.1 down; #占位server
server 192.168.213.91:2025;
balancer_by_lua_block {
local balancer = require "ngx.balancer";
local upstreams = require "upstreams";
local tmp_upstreams = upstreams.get_upstreams(); -- get all the upstreams upstreams.lua
print("\r\n")
ngx.log(ngx.INFO,"tmp_upstreams:"..tmp_upstreams)
local ip_port = tmp_upstreams[math.random(1, table.getn(tmp_upstreams))];
ngx.log(ngx.ERR,"ip"..ip_port.ip);
balancer.set_current_peer(ip_port.ip, ip_port.port);
}
}
server {
listen 8000;
server_name localhost;
charset utf-8;
location / {
proxy_pass http://api_server;
access_log /usr/local/etc/openresty/logs/api.log main;
}
}
nginx: [error] init_by_lua error: /usr/local/openresty/lualib/upstreams.lua:17: attempt to concatenate local 'resp' (a table value)
stack traceback:
/usr/local/openresty/lualib/upstreams.lua:17: in function 'update_upstreams'
init_by_lua:3: in main chunk
代码
cjson的问题
如果在lua脚本里要做json 转换的话 就需要json 包
git clone https://github.com/openresty/lua-cjson.git
cd lua-cjson
make && make install
不行
2020/12/22 11:44:33 [error] 25736#25736: 1 lua entry thread aborted: runtime error: /usr/local/openresty/lualib/upstreams.lua:5: module 'cjson' not found:
no field package.preload['cjson']
no file '/usr/local/openresty/lualib/cjson.lua'
no file '/usr/local/openresty/lualib/.lua'
no file '/usr/local/openresty/site/lualib/cjson.ljbc'
no file '/usr/local/openresty/site/lualib/cjson/init.ljbc'
no file '/usr/local/openresty/lualib/cjson.ljbc'
no file '/usr/local/openresty/lualib/cjson/init.ljbc'
no file '/usr/local/openresty/site/lualib/cjson.lua'
no file '/usr/local/openresty/site/lualib/cjson/init.lua'
no file '/usr/local/openresty/lualib/cjson.lua'
no file '/usr/local/openresty/lualib/cjson/init.lua'
no file './cjson.lua'
no file '/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/cjson.lua'
no file '/usr/local/share/lua/5.1/cjson.lua'
no file '/usr/local/share/lua/5.1/cjson/init.lua'
no file '/usr/local/openresty/luajit/share/lua/5.1/cjson.lua'
no file '/usr/local/openresty/luajit/share/lua/5.1/cjson/init.lua'
no file '/usr/local/openresty/lualib/*.so'
stack traceback:
coroutine 0:
[C]: in function 'require'
/usr/local/openresty/lualib/upstreams-test.lua:6: in main chunk, client: 192.168.255.200, server: localhost, request: "GET /luahttp HTTP/1.1", host: "192.168.213.7"
cd /usr/local/openresty/luajit/bin
./luajit
local cjson = require "cjson"
换了一台装就好了 原装的机器没有问题
单独调试luajit
cd /usr/local/openresty/luajit
./luajit
2020/12/22 15:56:51 [error] 3231#3231: *542221 lua entry thread aborted: runtime error: error loading module 'upstreams' from file '/usr/local/openresty/lualib/upstreams.lua':
/usr/local/openresty/lualib/upstreams.lua:7: '=' expected near 'upstream_list'
stack traceback:
coroutine 0:
[C]: in function 'require'
content_by_lua(nginx.conf:164):2: in main chunk, client: 192.168.255.200, server: somename, request: "GET /luahttp HTTP/1.1", host: "192.168.213.1:8000"
vi /usr/local/openresty/lualib/upstreams.lua
容器化网关
Dockerfile
FROM openresty/openresty:alpine
MAINTAINER likegadfly@163.com
RUN git clone https://github.com/ledgetech/lua-resty-http.git
直接拷贝文件到 openresty 的 lualib
add ./http_headers.lua /usr/local/openresty/lualib/resty/
add ./http.lua /usr/local/openresty/lualib/resty/
add ./nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
add ./upstreams.lua /usr/local/openresty/lualib/upstreams.lua
RUN mkdir -p /usr/local/openresty/nginx/servers
&& mkdir -p /usr/local/openresty/lualib/resty/
add ./tcp_consul.conf /usr/local/openresty/nginx/servers/tcp_consul.conf;
add lua-resty-http/lib/resty/http_headers.lua /usr/local/openresty/lualib/resty/
add lua-resty-http/lib/resty/http.lua /usr/local/openresty/lualib/resty/
EXPOSE 80/tcp 39999/tcp
常用命令
docker build -t openresty/zzw:0.0.5 -f ./Dockerfile .
docker run -p 8080:8080 -p 39999:39999 openresty/zzw:0.0.5
docker exec -it 1aa990db33fa /bin/sh
cd /usr/local/openresty
netstat -apn | grep nginx
查看8080端口和39999端口是否正常
docker start d4a7778f6fcc
docker logs -f --tail=200 1aa990db33fa
必要日志配置
log_format proxy '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time "$upstream_addr" '
'"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
access_log logs/access_8000.log proxy ;
open_log_file_cache off;
常用文件链接
ln -s /usr/local/openresty/nginx/logs/access_8000.log access_8000.log
ln -s /usr/local/openresty/nginx/logs/error.log error.log
ln -s /usr/local/openresty/lualib/upstreams.lua upstreams.lua
ln -s /usr/local/openresty/nginx/servers/test_openresty.conf test_openresty.conf
ln -s /usr/local/openresty/nginx/conf/nginx.conf nginx.conf
ln -s /usr/local/openresty/nginx/servers/tcp_consul.conf tcp_consul.conf
ln -s /usr/local/openresty/nginx/logs/access.log access.log
首先编辑nginx.conf文件
增加stream
stream {
log_format proxy '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time ["$upstream_addr"] '
'"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
access_log logs/access_8000.log proxy ;
include /usr/local/openresty/nginx/servers/*.conf;
}
然后编辑 servers/tcp_consul.conf
sudo mkdir /usr/local/openresty/nginx/servers
vi /usr/local/openresty/nginx/servers/tcp_consul.conf;
lua_shared_dict upstream_list 10m;
init_by_lua_block {
local upstreams = require "upstreams";
upstreams.update_upstreams();
}
# 定时拉取配置
init_worker_by_lua_block {
local upstreams = require "upstreams";
local handle = nil;
handle = function ()
--TODO:控制每次只有一个worker执行
upstreams.update_upstreams();
ngx.timer.at(5, handle);
end
ngx.timer.at(5, handle);
}
upstream push_socket_proxy {
server 0.0.0.1:3000 ; #占位server
#hash $remote_addr consistent;
balancer_by_lua_block {
local balancer = require "ngx.balancer";
local upstreams = require "upstreams";
local tmp_upstreams = upstreams.get_upstreams();
local ip_port = tmp_upstreams[math.random(1, table.getn(tmp_upstreams))];
balancer.set_current_peer(ip_port.ip, ip_port.port);
}
#hash $remote_addr consistent;
#server 127.0.0.1:6999 wight=5 max_fails=3 fail_timeout=30s;
}
server {
listen 39999 ;
proxy_connect_timeout 1s;
proxy_timeout 3s;
proxy_pass push_socket_proxy;
}
再编辑
vi /usr/local/openresty/lualib/upstreams.lua
local cjson = require "cjson"
local _M = {}
--lua_shared_dict upstream_list 10m;
_M._VERSION="0.1"
local http = require "resty.http"
local httpc = http:new()
function _M:update_upstreams()
local resp,err = httpc:request_uri("http://192.168.212.74:8500",
{
method = "GET", ---请求方式
path="/v1/catalog/service/opf-message-push-online-message",
--path="/v1/catalog/service/smarthome-app-srv",
--path="/search_product.htm?q=ipone",
--query="q=iphone", ---get方式传参数
--body="name='jack'&age=18", ---post方式传参数
--path="/search_product.htm", ----路径
---header参数
--headers = {["User-Agent"]="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36"}
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
})
if not resp then
print("请求consul 失败!!!!!!!!!!!!!!!")
return
end
print("请求http请求返回结果为",resp)
--ngx.log("请求http请求返回结果为"..cjson.encode(resp))
--local resp = cjson.decode(resp);
local upstreams = {}
for i, v in ipairs(resp) do
upstreams[i] = {ip=v.ServiceAddress, port=v.ServicePort}
end
ngx.shared.upstream_list:set("api_list", cjson.encode(upstreams))
end
function _M:get_upstreams()
local upstreams_str = ngx.shared.upstream_list:get("api_list");
local tmp_upstreams = cjson.decode(upstreams_str);
print(upstreams_str);
return tmp_upstreams;
end
return _M
再拷贝 http.lua等文件 用于支持 resty.http
git clone https://github.com/ledgetech/lua-resty-http.git
cd lua-resty-http/lib/resty
直接拷贝文件到 openresty 的 lualib
cp lua-resty-http/lib/resty/http_headers.lua /usr/local/openresty/lualib/resty/
cp lua-resty-http/lib/resty/http.lua /usr/local/openresty/lualib/resty/
nginx -s reload
然后重启
如果报错
020/12/22 20:22:52 [error] 8761#8761: init_by_lua error: /usr/local/openresty/lualib/resty/http.lua:133: no request found
stack traceback:
[C]: in function 'ngx_socket_tcp'
/usr/local/openresty/lualib/resty/http.lua:133: in function 'new'
/usr/local/openresty/lualib/upstreams.lua:2: in main chunk
[C]: in function 'require'
init_by_lua:2: in main chunk
直接测试
location /restyhttp {
resolver 8.8.8.8;
content_by_lua '
-- 引入 http 包
local http = require "resty.http"
local httpc = http:new()
local cjson = require "cjson"
cjson.decode("{'a':'1'}")
local res, err = httpc:request_uri("http://baidu.com", {
method = "POST",
body = "a=1&b=2",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
})
if not res then
ngx.say("fail to request ", err)
end
-- ngx.say(res.body) 直接输出响应体
-- ngx.say(res.status)
';
}
我需要一个类 既能测试 http 也能测试 json的 现在就写
然后后台在下载一个 docker
弄好了之后
重要文件拷贝
cp /usr/local/openresty/lualib/upstreams.lua upstreams.lua
cp /usr/local/openresty/nginx/servers/tcp_consul.conf tcp_consul.conf
cp /usr/local/openresty/nginx/conf/nginx.conf nginx.conf
docker build -t openrestyzzw:1.15.8.2 -f ./Dockerfile .
docker run -p 81:81 -p 39999:39999 e54e429bfbcd
docker run -p 81:81 -p 39999:39999 217883216b97
docker build -t openresty:1.15.8.2 -f ./Dockerfile .
docker 容器化碰到的问题点
如何把一些变量能让别人修改.
现在先罗列出那些需要暴露的变量
执行
docker save -o openresty.tar openresty/openresty:alpine
将镜像打包传输给配置管理原上传到公司文件系统
然后将所有用到的文件整理到git
运维需要注意的问题
上线问题
需要暴露80http 端口 和81 tcp 端口 在docker file 里有
需要确认opf-message-push服务的tcp端口是3000
需要确认consul的地址这个写死在了upstream.lua里
需要确认网关的域名 这个要配置在nginx中.conf
构建的时候如果出现问题
Get https://alpha-harbor.51iwifi.com/v2/: x509: certificate has expired or is not yet valid
如果是证书的问题需要编辑 daemo.json
文件:vi /etc/docker/daemon.json
{ "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"] }
测试
http://192.168.212.74:8500/v1/catalog/service/opf-message-push-online-message
查看是否有内容
[{"ID":"d98f1ebf-e2ae-636c-de2e-aa35619378ce","Node":"client:192.168.212.74:8500","Address":"192.168.217.87","Datacenter":"data-center-1","TaggedAddresses":{"lan":"192.168.217.87","wan":"192.168.217.87"},"NodeMeta":{"consul-network-segment":""},"ServiceKind":"","ServiceID":"opf-message-push-online-message-192.168.200.19-19003","ServiceName":"opf-message-push-online-message","ServiceTags":["order"],"ServiceAddress":"192.168.200.19","ServiceWeights":{"Passing":1,"Warning":1},"ServiceMeta":{},"ServicePort":19003,"ServiceEnableTagOverride":false,"ServiceProxyDestination":"","ServiceProxy":{},"ServiceConnect":{},"CreateIndex":83808059,"ModifyIndex":83808059}]
准备一个server.py 监听端口
准备一个client.py 发送tcp 请求
进入容器
telnet 127.0.0.1 39999 看地址通不通
定义一个employee对象 id 编号。年龄 姓名。生日 薪资 性别。
local Employee={
id="a12", --id
code="a012", --编号
age=12, --年龄
birth={ year = 1970, month = 1, day = 1, hour = 8, min = 1 }, --生日
salary=2000, --薪水
sex="female" --性别
}
function Employee:new(o)
o = o or {} --新建继承者。 o是实际继承者
setmetatable(o,self) -- 对象o调用不存在的成员时都会去self中查找,而这里的self指的就是Object
self.__index = self --
return o
end
function Employee:work(){
print("employee working");
}
简单说下 __index和setmetatable
去访问o的age属性的时候,但是发现o没有这个属性,就会去查看o的元表是否存在,如果o的元表存在就会找他的__index中去查找是否有age属性.
员工对象继承 (工作)
Extends
local Develop = Employee:new()
function Develop:work(){
print("Develop name:"..self.name.." is coding");
}
local develop1=Develop:new();
develop1.name="张三"
print("develop1 work test")
develop1:work();
local develop2=Develop:new();
develop2.name="张三"
print("develop2 work test")
develop2.work();
local Test = Employee:new()
function Test:work(){
print("Testor name:"..self.name.." is testing");
}
local test1=Test:new();
test1.name="李四"
print("test1 work test")
test1:work();
开发人员 测试 产品 主管
开发: 写代码。 单元测试
测试: 测试
产品: 写需求。
主管: 下发任务 工作。(工作可以重载)
employee={name="员工",work=function()
End
,meeting=function()
print("正在开会")
End
}
支持重载 重写。
需求: 主函数 分别打印 开发 测试 主管的行为。和 员工信息(每个角色的人员信息。)
薪资 带2为小数.
对象 命名要加业务前缀
方法要fun_开头
对象obj_开头
属性 attr_开头
attr_int
attr_bit
attr_bool
attr_string
attr_array
attr_map
类型转化:
时间戳 日期 、字符串转时间戳 转日期
字符串转数字类型
json 序列化 反序列化
if else switch case。do while. For. while。
异常处理
返回值 errorCode。errorMsg 封装成对象
包模块、包、引用、常量、提取常量类 、
第一期
第二期:
通过lua调用 redis。consul。接口事例。
结合openresty
get post 请求。