lua基础教学

目标

基于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)。下载本地成功后,执行解压缩,就能看到下图所示目录结构:

img

双击图中的 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
img

新建一个lua项目 在File->Project Structure里面配置好modules和lib,如下图.
img

img

至此一个包含lua语法提示和调整的编辑器环境就配置好了.

有关emmylua的详细帮助文档看这里

插件基本用法

1.方法提示

你可以在Setting里面配置鼠标移动到方法上后一定时间自动弹出
img

也可以按Ctrl+q手动弹出,效果如下(= =目前我使用的版本文档中的换行显示还有问题)
img

2.快速跳转

在任何已经被定义的方法上按住Ctrl+鼠标点击该方法就可以自动打开和跳转到方法定义上面,非常方便

3.方法提示

在你输入识别的全局或者局部变量上面按点会自动出现可选方法做提示,不用记住所有的方法.
img

进阶配置

由于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

方法提示不一定要使用独立的文件定义,可以直接在库里面定义,如:
img

至于里面的含义就要去这里看和理解拉.

总之如果你的库都定义好了方法提示,在你理解源码的时候将会非常方便快速.相信我

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 并激活luajithttp_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);
                ';
        }

访问 http://127.0.0.1/lua

看到页面有输出 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 4 }' curl monitor.odc.consul.cn/v1/agent/self --user edsp:edsp | python -m json.tool #读取consul配置 curl monitor.odc.consul.cn/v1/agent/reload --user edsp:edsp | python -m json.tool # 重新加载配置 curl monitor.odc.consul.cn/v1/catalog/services/linux:metrics --user edsp:edsp | python -m json.tool curl monitor.odc.consul.cn/v1/catalog/services --user edsp:edsp | python -m json.tool ## 获取所有的服务 curl monitor.odc.consul.cn/v1/catalog/services --user edsp:edsp | python -m json.tool | grep 'metrics'| awk -F'"' '{print2}' ## 分隔出服务名

http://apisix.iresty.com

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 请求。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容