今天这篇文章就随意谈谈lua。最近在看nginx+lua,刚好学了lua,就被这门语言的简练给吸引了。lua语言可以大大减轻一个人的心智负担,除了变量,操作符,语句以及函数这些最基础的语言功能外,剩下最重要的就是数据结构table(数组本质上也是table的特例)了。不用去记那么多的语法以及黑魔法,简直大快人心。
我现在对Lua使用感兴趣部分就是nginx+lua和redis内嵌lua;特别是前者,nginx+lua+redis构建高性能应用。lua+nginx可以使nginx不需要重新编译的情况下添加功能,特别是,如果只是修改lua文件,nginx都不需要重新加载配置文件,极大便利了nginx开发;而redis内嵌lua,让redis服务器有了计算能力,而且减小带宽传输(多个命令)以及原子执行多个命令达到cas效果。
下面就说说最近的一些内容和心得,主要以下三部分:
- 通过lua脚本操作redis;
- nginx+lua示例;
- lua面向对象实现;
- 总结;
【版权声明】博客内容由罗道文的私房菜拥有版权,允许转载,但请标明原文链接http://luodw.cc/2017/03/24/lua/#more
通过lua脚本操作redis
之前在学习redis以及看源码时,由于不懂lua,所以redis内嵌Lua模块就跳过去了,如今学了Lua,因此想把这块知识补上。redis对Lua的支持,主要有以下以下7个命令,
- EVAL
- EVALSHA
- SCRIPT DEBUG
- SCRIPT EXISTS
- SCRIPT FLUSH
- SCRIPT KILL
- SCRIPT LOAD 具体这些命令怎么使用,以及实现原理如何,可以看链接Lua脚本-Redis设计与实现和redis官网。这里主要介绍下EVAL命令。
EVAL命令格式为
EVAL script numkeys key [key ...] arg [arg ...]
这里的
- script即为lua脚本或lua脚本文件;
- key一般指lua脚本操作的键,在lua脚本文件中,通过KEYS[i]获取;
- arg指外部传递给lua脚本的参数,可以通过ARGV[i]获取;
下面通过两个简单的示例展示eval命令的用法。首先是脚本语句,来自redis官网,如下
这个示例中lua脚本为一个return语句,返回了lua一个数组,这个数组四个元素分别是通过外部传入lua脚本。因为redis内嵌了Lua虚拟机,因此redis接收到这个lua脚本之后,然后交给lua虚拟机执行。当lua虚拟机执行结束,即将执行结果返回给redis,redis将结果按自己的协议转换为返回给客户端的回复,最后再通过TCP将回复发回给客户端。
通过这个例子也可以看出,在lua脚本中可以通过KEYS[i]来获取外部传入的键值,通过ARGV[i]来获取外部传入的参数。
下面给出一个复杂点的lua脚本,在给出脚本之前,先看下redis中有哪些需要的数据
下面lua脚本的作用就是通过一次网络请求获取'jom','lily','tom'三个人的个人信息
在命令行上执行eval命令,即可以得到结果,如下:
试想,如果没有使用lua脚本,那么上述功能需要在应用程序中先发送ZRANGE命令,获取所有姓名;获取到所有姓名之后,在遍历所有姓名,针对每个姓名访问redis获取给人信息,至少四次网络IO。而管道在这种场景上也派不上用场。由此可知,lua脚本在这种场景下提高redis性能是有多大的帮助了。
需要特别注意的是people(键)后面的逗号左右都要留空格,不然会报错!
ngx+lua示例
ngx+lua是我最近非常感兴趣的一门技术,因为在带来高性能的同时,并没有提高编程的复杂性。如果是用c语言开发nginx第三方模块,需要了解nginx内部的数据结构以及接口,难度和心智上都有挑战。本小节也是通过简单的ngx+lua示例来展示在lua是如何在nginx中使用的。
开发环境可以通过极客学院教程安装。即安装OpenResty框架。安装的目录在/usr/servers,测试目录在/usr/example。
第一个示例展示如何获在lua脚本中获取http请求所有信息,这个示例来自开涛的博客,可以很好理解在lua中如何获取nginx请求信息。在example.conf中添加如下路径映射
然后在/usr/example/lua/文件下新建lua_test.lua文件,复制如下代码
由代码可以看出,lua脚本中主要信息来自于ngx.var和ngx.req这两个属性。在执行程序之前,先nginx需要先reload一次,最后我们就可以通过curl命令来访问nginx
通过代码和结果一一比对,可以看出已经正确获取请求信息。我觉得这是一个很好的入门示例,因为处理http请求,获取http请求头信息是非常有必要的,而上述例子则展示接口信息。
下面另一个示例是展示ngx+lua存取redis。同样在example.conf添加路径映射
然后在/usr/example/lua/文件夹下新建redis.lua,复制如下代码:
OpenResty将常用的lua包都已经打包好,包括redis客户端,memcache客户端,mysql客户端以及cjson等等,非常方便ngx+lua的开发。在执行程序之前,先reload一次配置文件,执行结果如下:
最后可以通过redis-cli客户端验证上述三个键值对是否插入成功。由这个例子可以展开讨论,如下
- 因为OpenResty提供了redis,mysql以及模板渲染,因此利用ngx+lua可以直接部署一个小型网站;
- 可以在nginx本地搭一个redis缓存;当数据请求时,先访问本地的redis缓存,如果redis缓存需要的数据,直接直接在nginx层返回;如果redis没有缓存需要的数据,则转向web服务器。这样可以减轻后端服务器和数据库的压力,以及缩短请求时间。
lua面向对象实现
lua没有提供面像对象的实现,但是可以通过table来模拟。所以可知table在lua中是多么核心的数据结构。这里,我用redis客户端来说明lua是如何模拟面像对象的实现。
上述new方法即相当于类的构造函数,因此,我们可以通过如下方式新建一个对象
通过redis:new()方法返回一个{sock=sock}表格,然后设置这个表格的元表为resty.redis模块中的_M表格;__index元表格的意思就是当在red中访问一个不存在的属性时,则可以到元表中查询;因此当red调用下述函数时:
因为red中并不存在connect方法,因此red到它的_M中查找connect方法,并执行;其实_M和red在lua中是两个不同的对象,就是因为red的元表为_M,因此可以把_M当作是对象red的模板,也就是类的概念。
总结
本文主要介绍了lua在redis和nginx的应用,以及lua面向对象的实现。多了解一些编程语言,可以更好对编程语言的理解。后面还是深入再看看ngx+lua的开发。