图片 12

thrift的lua达成,thriftlua完结

thrift的lua实现,thriftlua实现

  最近要进行系统升级,后台的数据是根据城市区分的。担心新系统的稳定性及新数据的准确性,计划部分城市采用新接口。接口的入参里没有城市信息,只有经纬度坐标,需要调用一个thrift接口来根据坐标获取城市信息。

  如果直接修改代码逻辑,则会造成新旧版本的耦合,不仅完全上线时要再次修改,而且还要增加一次测试流程,这样成本就有些高了。这时就想到能不能用nginx+lua对新旧版本接口做灰度发布。

图片 1

  步骤:

    1、安装thrift

        2、生成客户的代码

    3、编译lua调用thrift需要的库

        4、实现客户端代码

        5、测试

  

最近要进行系统升级,后台的数据是根据城市区分的。担心新系统的稳定性及新数据的准确性,计划部分城市采用新接口。接口的入参里没有城市信息,只有经纬度坐标,需要调用一个thrift接口来根据坐标获取城市信息。

OpenResty 官网:

1、安装thrift

  thrift最初由facebook开发用做系统内各语言之间的RPC通信,其实它与webservice有很多相似的地方。

  首先有一个定义数据类型和接口的文件,xxx.thrift(在webservic里面对应的是xxx.wsdl),然后用程序去生成对应的客户端/服务器代码.

  thrift的官方网站

  最新版本的是thrift-0.9.3.tar.gz(截止2016/11/28)。

  安装的步骤官网上有,:

1 ./configure && make && make install

  安装过程可能会遇到缺依赖包,不是大问题,装一下就行了。

  如果直接修改代码逻辑,则会造成新旧版本的耦合,不仅完全上线时要再次修改,而且还要增加一次测试流程,这样成本就有些高了。这时就想到能不能用nginx+lua对新旧版本接口做灰度发布。

OpenResty
是一个nginx和它的各种三方模块的一个打包而成的软件平台。最重要的一点是它将lua/luajit打包了进来,使得我们可以使用lua脚本来进行web的开发。有了lua,我们可以借助于nginx的异步非阻塞的功能,达到使用 lua 异步并发访问后端的 MySQL, PostgreSQL,
Memcached, Redis等等服务
。特别是特有的
ngx.location.capture_multi 功能让人印象深刻,其可以达到极大的减少浏览器的http连接数量,并且可以异步并发的访问后台
Java/PHP/Python 等等接口
。OpenResty
架构的web可以轻松超越Node.js的性能,并且对后端语言没有限制,你可以使用Java/PHP/Python等等各种语言。OpenResty(nginx+lua)可以替代node.js的前端渲染的功能。

2、生成客户的代码

  thrift文件内容,服务端提供 location_match.thrift:

 1 /**
 2 * File: location_service.thrift
 3 */
 4 /** 入参数据结构 **/
 5 struct Location_point
 6 {
 7     1: double x;
 8     2: double y;
 9 }
10 /** 出参数据结构 **/
11 struct Citycode_Response
12 {
13     1:string retCode;
14     2:i16 cityCode;
15 }
16 
17 /** 接口定义 **/
18 service LocationmatchService
19 {
20     Citycode_Response find_citycode(1:Location_point location_point)
21 }

根据此文件生成lua客户端代码:

1 thrift --gen lua location_match.thrift

会在当前目录生成一个目录:gen-lua

里面有3个文件:

 location_match_constants.lua:应该是个接口文件,里面有说明,大概意思是说,不懂就不要改

 location_match_LocationmatchService.lua: 接口的定义

 location_match_ttypes.lua:出入参数结构定义

 这个过程非常像axis根据wsdl生成webservice代码的过程。上面的3个文件不需要改的。

图片 2

OpenResty (aka. ngx_openresty) is a full-fledged web application server
by bundling the standard Nginx core, lots of 3rd-party Nginx
modules, as well as most of
their external dependencies.

3、编译lua调用thrift需要的库

lua调用thrift服务需要一些库文件,一部分是c语言实现的动态链接库*.so文件,一部分是lua语言的函数库*.lua。这些库的源码文件在官网下载的包里有(thrift-0.9.3.tar.gz),解压后在lib目录下有各个语言的库。lua在lib/lua下。

这里需要注意几个问题:1、官网的包里c语言编译会报错(当然可能是个别现象),具体错误没记住,大致上是说
relink libluasocket.so
时候报错。这个错误还好说,改下Makefile.am调整顺序即可,参见
2、调用thrift服务一定要注意两个关键点传输协议和IO方式(阻塞/非阻塞),传输协议有二进制流传输、json串和压缩数据传输格式等,其实主要规定了数据在传输过程中的格式,官网的包中只有二进制传输的协议TBinaryProtocol.lua。3、官网的包中socke是根据依赖lua实现的,而现实使用openresty是基于luajit的。luajit的socket是ngx.socket.tcp(),可能会造成某些冲突。

这里采用
这篇文章中提到的源码实现,没有使用thrift官网的代码。代码下载地址
linux。

这里采用的nginx是openresty.下载和安装参见.

  步骤:

By taking advantage of various well-designed Nginx modules, OpenResty
effectively turns the nginx server into a powerful web app server, in
which the web developers can use the Lua
programming language to script various existing nginx C modules and Lua
modules and construct extremely high-performance web applications
that are capable to handle 10K+ connections.

4、实现客户端代码

test_cln.lua:

 1 require('TSocket')   
 2 require('TCompactProtocol')
 3 require('TTransport') 
 4 require('location_match_LocationmatchService')
 5 require('location_match_ttypes')
 6 require('TFramedTransport')
 7 module("test_cln",package.seeall) 
 8 function demoFunc()
 9   local opt = {
10         host='127.0.0.1',
11         port=9090
12   }
13   local socket = TSocket:new(opt)
14   local ttable = {
15      trans = socket
16   }
17   local transport = TFramedTransport:new(ttable)
18   
19   
20   local protocol = TCompactProtocol:new{
21         trans = transport
22   }
23 
24   client = LocationmatchServiceClient:new{
25         protocol = protocol
26   }
27   local location_point = Location_point:new{
28         x=114.2901961,
29         y=22.76033004,
30   }
31  socket:open()
32  res = ""
33  local ret = client:find_citycode(location_point)
34  res= tostring(ret.cityCode)
35  ngx.log(ngx.ERR,res..'   ~~~~~~~'..tostring(ret.cityCode))
36   return res
37 end

 

实现上与:
time out/TTransportException: closed/TTransportException: connection
reset by peer.

要把第二步生成的lua文件、第三步编译的动态链接库和lua文件都放到lualib目录下。test_cln.lua也要防止这个目录下。

nginx.conf:

1         location / {
2             content_by_lua_file /usr/local/nginx/nginx/conf/luascript/test.lua; 
3             root   html;
4             index  index.html index.htm;
5         }

 

test.lua:

1 local cln = require "test_cln"
2 ngx.say(cln.demoFunc());

 

    1、安装thrift

OpenResty aims to run your server-side web app completely in the Nginx
server, leveraging Nginx’s event model to do non-blocking I/O not only with the HTTP
clients, but also with remote
backends like MySQL, PostgreSQL, Memcached, and Redis.

5、测试

[[email protected]1 sbin]# curl http://127.0.0.1/
1438

 

返回城市代码成功。

 

总结:采用openresty(nginx的一个版本,有luajit插件)实现接口的灰度发布,lua调用thrift,安装thrift,生成客户的代码,下载开源的thrift-lua依赖的库,thrift需要协议和io阻塞方式客户端与服务端一致。

 

最近要进行系统升级,后台的数据是根据城市区分的。担心新系统的稳定性及新数据的准确性,计划部分城市采…

        2、生成客户的代码

1. 安装OpenResty

    3、编译lua调用thrift需要的库

先安装依赖:yum install readline-devel pcre-devel openssl-devel gcc

        4、实现客户端代码

解压: tar zxvf ngx_openresty-1.9.3.1.tar.gz

        5、测试

建立一个软连接:ln -s ngx_openresty-1.9.3.1 openresty

  

进入目录:cd openresty

1、安装thrift

  thrift最初由facebook开发用做系统内各语言之间的RPC通信,其实它与webservice有很多相似的地方。

  首先有一个定义数据类型和接口的文件,xxx.thrift(在webservic里面对应的是xxx.wsdl),然后用程序去生成对应的客户端/服务器代码.

  thrift的官方网站

  最新版本的是thrift-0.9.3.tar.gz(截止2016/11/28)。

  安装的步骤官网上有,:

1 ./configure && make && make install

  安装过程可能会遇到缺依赖包,不是大问题,装一下就行了。

编译:

2、生成客户的代码

  thrift文件内容,服务端提供 location_match.thrift:

图片 3

 1 /**
 2 * File: location_service.thrift
 3 */
 4 /** 入参数据结构 **/
 5 struct Location_point
 6 {
 7     1: double x;
 8     2: double y;
 9 }
10 /** 出参数据结构 **/
11 struct Citycode_Response
12 {
13     1:string retCode;
14     2:i16 cityCode;
15 }
16 
17 /** 接口定义 **/
18 service LocationmatchService
19 {
20     Citycode_Response find_citycode(1:Location_point location_point)
21 }

图片 4

根据此文件生成lua客户端代码:

1 thrift --gen lua location_match.thrift

会在当前目录生成一个目录:gen-lua

里面有3个文件:

 location_match_constants.lua:应该是个接口文件,里面有说明,大概意思是说,不懂就不要改

 location_match_LocationmatchService.lua: 接口的定义

 location_match_ttypes.lua:出入参数结构定义

 这个过程非常像axis根据wsdl生成webservice代码的过程。上面的3个文件不需要改的。

./configure \
             --with-cc-opt="-I/usr/local/include" \
             --with-ld-opt="-L/usr/local/lib" \
             --prefix=/opt/openresty 

... ...
Configuration summary
  + using system PCRE library
  + using system OpenSSL library
  + md5: using OpenSSL library
  + sha1: using OpenSSL library
  + using system zlib library

  nginx path prefix: "/opt/openresty/nginx"
  nginx binary file: "/opt/openresty/nginx/sbin/nginx"
  nginx configuration prefix: "/opt/openresty/nginx/conf"
  nginx configuration file: "/opt/openresty/nginx/conf/nginx.conf"
  nginx pid file: "/opt/openresty/nginx/logs/nginx.pid"
  nginx error log file: "/opt/openresty/nginx/logs/error.log"
  nginx http access log file: "/opt/openresty/nginx/logs/access.log"
  nginx http client request body temporary files: "client_body_temp"
  nginx http proxy temporary files: "proxy_temp"
  nginx http fastcgi temporary files: "fastcgi_temp"
  nginx http uwsgi temporary files: "uwsgi_temp"
  nginx http scgi temporary files: "scgi_temp"

3、编译lua调用thrift需要的库

lua调用thrift服务需要一些库文件,一部分是c语言实现的动态链接库*.so文件,一部分是lua语言的函数库*.lua。这些库的源码文件在官网下载的包里有(thrift-0.9.3.tar.gz),解压后在lib目录下有各个语言的库。lua在lib/lua下。

这里需要注意几个问题:1、官网的包里c语言编译会报错(当然可能是个别现象),具体错误没记住,大致上是说
relink libluasocket.so
时候报错。这个错误还好说,改下Makefile.am调整顺序即可,参见
2、调用thrift服务一定要注意两个关键点传输协议和IO方式(阻塞/非阻塞),传输协议有二进制流传输、json串和压缩数据传输格式等,其实主要规定了数据在传输过程中的格式,官网的包中只有二进制传输的协议TBinaryProtocol.lua。3、官网的包中socke是根据依赖lua实现的,而现实使用openresty是基于luajit的。luajit的socket是ngx.socket.tcp(),可能会造成某些冲突。

这里采用
这篇文章中提到的源码实现,没有使用thrift官网的代码。代码下载地址
linux。

这里采用的nginx是openresty.下载和安装参见.

其中 –prefix=/opt/openresty 指定了安装目录,不指定的话默认会安装到
/usr/local/openresty 目录下。“

4、实现客户端代码

test_cln.lua:

图片 5

 1 require('TSocket')   
 2 require('TCompactProtocol')
 3 require('TTransport') 
 4 require('location_match_LocationmatchService')
 5 require('location_match_ttypes')
 6 require('TFramedTransport')
 7 module("test_cln",package.seeall) 
 8 function demoFunc()
 9   local opt = {
10         host='127.0.0.1',
11         port=9090
12   }
13   local socket = TSocket:new(opt)
14   local ttable = {
15      trans = socket
16   }
17   local transport = TFramedTransport:new(ttable)
18   
19   
20   local protocol = TCompactProtocol:new{
21         trans = transport
22   }
23 
24   client = LocationmatchServiceClient:new{
25         protocol = protocol
26   }
27   local location_point = Location_point:new{
28         x=114.2901961,
29         y=22.76033004,
30   }
31  socket:open()
32  res = ""
33  local ret = client:find_citycode(location_point)
34  res= tostring(ret.cityCode)
35  ngx.log(ngx.ERR,res..'   ~~~~~~~'..tostring(ret.cityCode))
36   return res
37 end

图片 6

 

实现上与:
time out/TTransportException: closed/TTransportException: connection
reset by peer.

要把第二步生成的lua文件、第三步编译的动态链接库和lua文件都放到lualib目录下。test_cln.lua也要防止这个目录下。

nginx.conf:

1         location / {
2             content_by_lua_file /usr/local/nginx/nginx/conf/luascript/test.lua; 
3             root   html;
4             index  index.html index.htm;
5         }

 

test.lua:

1 local cln = require "test_cln"
2 ngx.say(cln.demoFunc());

 

编译安装: make && make install

5、测试

[root@hadoop-1 sbin]# curl http://127.0.0.1/
1438

 

返回城市代码成功。

 

总结:采用openresty(nginx的一个版本,有luajit插件)实现接口的灰度发布,lua调用thrift,安装thrift,生成客户的代码,下载开源的thrift-lua依赖的库,thrift需要协议和io阻塞方式客户端与服务端一致。

[root@localhost src]# cd /opt/openresty/
[root@localhost openresty]# ls
bin  luajit  lualib  nginx

可以看到 /opt/openresty 目录下四个文件夹,其中包括了 luajit,nginx。

启动openresty: /opt/openresty/nginx/sbin/nginx -c
/opt/openresty/nginx/conf/nginx.conf -p /opt/openresty/nginx/

[root@localhost src]# ps -elf|grep nginx
1 S root      2076     1  0  80   0 - 34999 -      21:24 ?        00:00:00 nginx: master process /opt/openresty/nginx/sbin/nginx -c /opt/openresty/nginx/conf/nginx.conf -p /opt/openresty/nginx/
5 S nobody    2077  2076  0  80   0 - 35045 -      21:24 ?        00:00:00 nginx: worker process                                    
0 S root      2079  1678  0  80   0 -  1088 -      21:24 pts/1    00:00:00 grep nginx

验证可以访问: curl 127.0.0.1

2. content_by_lua 和
content_by_lua_file

nginx 如何嵌入 lua 脚本。方法就是在nginx的配置文件nginx.conf 中使用
content_by_lua 或者 cotent_by_lua_file 指令:

1) 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.

2)cotent_by_lua_file 适应于复杂的 lua 脚本,专门放入一个文件中:

        location /lua2 {
            #lua_code_cache off;
            content_by_lua_file lua/hello.lua;
        }

路径相对于 /opt/openresty/nginx

[root@localhost lua]# pwd
/opt/openresty/nginx/lua
[root@localhost lua]# cat hello.lua
ngx.say('hello ngx_lua!!!!');

本例子中 hello.lua 只包含一句: ngx.say(‘hello ngx_lua!!!!’);

访问 /lua2 :

[root@localhost lua]# curl localhost/lua
hello ngx_lua!!!!

可以看到访问成功。

在 nginx.conf 文件的 server {.. …} 中加入 lua_code_cache off;
可以方便调试lua脚本,修改lua脚本之后,不需要 reload nginx.

openresty 中的 nginx 嵌入 luajit 的原理:

图片 7

每一个nginx的进程中都嵌入了一个
luajit的虚拟机,来执行lua脚本。nginx将lua脚本的执行交给了luajit vm.

3. ngx_lua 的指令 和 API

上面我们说到 nginx 嵌入 lua 脚本可以使用 content_by_lua 和
content_by_lua_file,它们其实是指令(Directives),类似的指令还有很多,

具体参见:

图片 8

这些指令都是 nginx 访问 lua 脚本的入口。

ngx_lua API:

指令是 nginx 访问 lua
脚本的入口。那么lua脚本如何调用nginx中的函数呢?就是通过 ngx_lua 的API

具体介绍参见:

The various *_by_lua and
*_by_lua_file configuration directives serve
as gateways to the Lua API within the nginx.conf file
.
The NGINX Lua API described below can only
be called within the user Lua code run in the context of these
configuration directives.

The API is exposed to Lua in the form of two standard packages ngx and
ndk. These packages are in the default global scope within ngx_lua
and are always available within ngx_lua directives.

图片 9

其实nginx和Lua的交互开发主要就是指令和API,当然还有lua脚本的语法。指令是nginx访问lua的入口,API是lua调用nginx的函数,lua是脚本编程语言。

指令其实很简单,所以主要就是熟悉ngx_lua的 API 和Lua语法。

4. lua 访问 redis

lua-resty-redis 模块:
(有文档可以参考)

在nginx.conf中加入:

        location /redis_test{
            content_by_lua_file lua/redis_test.lua;
        }

redis_test.lua 内容:

[root@localhost lua]# cat redis_test.lua
local redis = require "resty.redis"
local red = redis:new()

red:set_timeout(1000)

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
        ngx.say("failed to connect: ", err)
        return
end

ngx.say("set result: ", ok)

local res, err = red:get("dog")
if not res then
        ngx.say("failed to get doy: ", err)
        return
end

if res == ngx.null then
        ngx.say("dog not found.")
        return
end

ngx.say("dog: ", res)

[root@localhost lua]#

访问:

[root@localhost lua]# curl localhost/redis_test
set result: 1
dog: an animal
[root@localhost lua]#

我们看到访问成功。

5. lua 访问mysql

openresty的mysql模块:lua-resty-mysql
:

在nginx.conf加入如下配置:

        location /mysql_test {
            content_by_lua_file lua/mysql_test.lua;
        }

mysql_test.lua脚本内容:

[root@localhost lua]# pwd
/opt/openresty/nginx/lua
[root@localhost lua]# cat mysql_test.lua
local mysql = require "resty.mysql"
local db, err = mysql:new()

if not db then
        ngx.say("failed to instantiate mysql: ", err)
        return
end

db:set_timeout(1000)

local ok, err, errno, sqlstate = db:connect{
        host = "127.0.0.1",
        port = 3306,
        database = "ngx_lua",
        user = "root",
        password="digdeep",
        max_packet_size = 1024 * 1024
}

if not ok then
        ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)
        return
end

ngx.say("connected to mysql.")

local res, err, errno, sqlstate = db:query("drop table if exists cats")
if not res then
        ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
        return
end

res, err, errno, sqlstate = db:query("create table cats " .. "(id int not null primary key auto_increment, "
                                        .. "name varchar(30))")
if not res then
        ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
        return
end

ngx.say("table cats created.")

res, err, errno, sqlstate = db:query("insert into cats(name) " .. "values (\'Bob\'),(\'\'),(null)")
if not res then
        ngx.say("bad request: ", err, ": ", errno, ": ", sqlstate, ".")
        return
end

ngx.say(res.affected_rows, " rows inserted into table cats ", "(last insert id: ", res.insert_id, ")")

res, err, errno, sqlstate = db:query("select * from cats order by id asc", 10)
if not res then
        ngx.say("bad result ", err, ": ", errno, ": ", sqlstate, ".")
        return
end

local cjson = require "cjson"
ngx.say("result: ", cjson.encode(res))

local ok, err = db:set_keepalive(1000, 100)
if not ok then
        ngx.say("failed to set keepalive: ", err)
        return
end

测试:

[root@localhost lua]# curl localhost/mysql_test
connected to mysql.
table cats created.
3 rows inserted into table cats (last insert id: 1)
result: [{"name":"Bob","id":1},{"name":"","id":2},{"name":null,"id":3}]

测试通过。

5. lua 的 capture 和
capture_multi(子查询)

capture_multi 是 openresty
一个十分强大的功能。它能极大的减少前端浏览器发送的http请求的数量,突破了浏览器对于同一个服务器并发请求数量的限制,因为他可以将前端的多个http请求减少为只要一个http请求到nginx,然后nginx使用capture_multi特性,对后端发起多个异步并发请求,然后统一将结果返回给前端。下面看一个例子:

首先在nginx.conf中加入下面的 location 配置,并且配置好 nginx 访问 php
的配置:

        location /capture {
            content_by_lua_file lua/capture.lua;
            #access_by_lua_file lua/capture.lua;
        }

        location ~ \.php$ {
            root           html;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }

capture.lua 的代码如下:

[root@localhost lua]# pwd
/opt/openresty/nginx/lua
[root@localhost lua]# cat capture.lua
local res1,res2,res3,res4 = ngx.location.capture_multi{
        {"/mysql_test", {args="t=1&id=1"}},
        {"/redis_test", {args="t=2&id=2"}},
        {"/lua", {args="t=3&id=3"}},
        {"/index.php", {args="t=3&id=3"}},
}

ngx.header.content_type="text/plain"
ngx.say(res1.body)
ngx.say(res2.body)
ngx.say(res3.body)
ngx.say(res4.truncated)
ngx.say(res4.status)
ngx.say(res4.header["Set-Cookie"])

--ngx.say(res4.body)

index.php 代码:

[root@localhost html]# pwd
/opt/openresty/nginx/html
[root@localhost html]# cat index.php
<?php
        echo phpinfo();
?>

访问:

[root@localhost html]# curl localhost/capture
connected to mysql.
table cats created.
3 rows inserted into table cats (last insert id: 1)
result: [{"name":"Bob","id":1},{"name":"","id":2},{"name":null,"id":3}]

set result: 1
dog: an animal

hello ngx_lua!!!!

false
200
nil

可以看到访问成功了。/mysql_test,/redis_test, /lua, /index.php
四个请求的结果都输出了。

注意:

ngx.location.capture_multi{… …}
中的多个异步并发请求可以是 nginx.conf
中配置的 location(比如 /mysql_test, /redis_test, /lua),也可以不是 location配置的路径,比如 index.php
就不是。index.php 就是一个简单的后台php 脚本。当然也可以是一个
java 实现的后台接口。

6. openresty的缓存 lua_shared_dict

定义一个缓存:

在nginx的配置文件 nginx.conf 的 http 端下面加入指令:

lua_shared_dict ngx_cache 128m;

就定义了一个 名称为 ngx_cache
大小为128m的内存用于缓存,注意该缓存是所有nginx work process所共享的。

在lua脚本中访问缓存:

local ngx_cache = ngx.shared.ngx_cache
local value = ngx_cache:get(key)

local succ, err, forcible = ngx_cache:set(key, value, exptime)

下面测试一下,首先在 nginx.conf的server端中加入:

        location /cache {
            content_by_lua_file lua/cache.lua;
        }

然后编写 cache.lua 脚本:

[root@localhost lua]# cat cache.lua
local redis = require "resty.redis"
local red = redis:new()

function set_to_cache(key, value, exptime)
        if not exptime then
                exptime = 0
        end
        local ngx_cache = ngx.shared.ngx_cache
        local succ, err, forcible = ngx_cache:set(key, value, exptime)
        return succ
end

function get_from_cache(key)
        local ngx_cache = ngx.shared.ngx_cache;
        local value = ngx_cache:get(key)
        if not value then
                value = get_from_redis(key)
                set_to_cache(key, value)
                return value
        end

        ngx.say("get from cache.")
        return value
end

function get_from_redis(key)
        red:set_timeout(1000)

        local ok, err = red:connect("127.0.0.1", 6379)
        if not ok then
                ngx.say("failed to connect: ", err)
                return
        end

        local res, err = red:get(key)
        if not res then
                ngx.say("failed to get doy: ", err)
                return ngx.null
        end

        ngx.say("get from redis.")
        return res
end

function set_to_redis(key, value)
        red:set_timeout(1000)
        local ok, err = red:connect("127.0.0.1", 6379)
        if not ok then
                ngx.say("failed to connect: ", err)
                return
        end

        local ok, err = red:set(key, value)
        if not ok then
                ngx.say("failed to set to redis: ", err)
                return
        end
        return ok
end

set_to_redis('dog', "Bob")
local rs = get_from_cache('dog')
ngx.say(rs)

测试:

[root@localhost ~]# curl localhost/cache
get from redis.
Bob
[root@localhost ~]# curl localhost/cache
get from cache.
Bob
[root@localhost ~]# curl localhost/cache
get from cache.
Bob

第一次从 redis中获取,以后每次都从cache中获取。

可以使用 ab 测试一下rps(Requests per second):

 ab -n 1000 -c 100 -k http://127.0.0.1/cache

7. 解决缓存失效风暴 lua-resty-lock

缓存失效风暴是指缓存因为时间过期而失效时,会导致所有的请求都去访问
后台的redis或者mysql,而导致CPU性能即刻增长的现象。所以关键是当缓存失效时,用lock保证只有一个线程去访问后台的redis或者mysql,然后更新缓存。需要使用到
lua-resty-lock 模块的加锁、解锁功能。

lua-resty-lock 文档:

首先在nginx.conf 的 http 端下面加入指令:

lua_shared_dict ngx_cache 128m;     # cache
lua_shared_dict cache_lock 100k;    # lock for cache

然后在nginx.conf的server端中加入:

        location /cache_lock {
            content_by_lua_file lua/cache_lock.lua;
        }

cache_lock.lua代码:

图片 10图片 11

[root@localhost lua]# cat cache_lock.lua
local redis = require "resty.redis"
local red = redis:new()
local resty_lock = require "resty.lock"
local ngx_cache = ngx.shared.ngx_cache

function set_to_cache(key, value, exptime)
        if not exptime then
                exptime = 0
        end
        local succ, err, forcible = ngx_cache:set(key, value, exptime)
        return succ
end

function get_from_cache(key)
        local ngx_cache = ngx.shared.ngx_cache;
        local value = ngx_cache:get(key)
        if not value then       -- cache miss
                local lock = resty_lock:new("cache_lock")
                local elapsed, err = lock:lock(key)
                if not elapsed then
                        return fail("failed to acquire the lock: ", err)
                end

                value = get_from_redis(key)
                if not value then
                        local ok, err = lock:unlock()
                        if not ok then
                                return fail("failed to unlock: ", err)
                        end
                        ngx.say("no value found")
                        return
                end

                local ok, err = ngx_cache:set(key, value, 1)
                if not ok then
                        local ok, err = lock:unlock()
                        if not ok then
                                return fail("failed to unlock: ", err)
                        end
                        return faile("failed to update ngx_cache: ", err)
                end

                local ok, err = lock:unlock()
                if not ok then
                        return faile("failed to unlock: ", err)
                end

                return value
        end

        ngx.say("get from cache.")
        return value
end

function get_from_redis(key)
        red:set_timeout(1000)

        local ok, err = red:connect("127.0.0.1", 6379)
        if not ok then
                ngx.say("failed to connect: ", err)
                return
        end

        local res, err = red:get(key)
        if not res then
                ngx.say("failed to get doy: ", err)
                return ngx.null
        end

        ngx.say("get from redis.")
        return res
end

function set_to_redis(key, value)
        red:set_timeout(1000)
        local ok, err = red:connect("127.0.0.1", 6379)
        if not ok then
                ngx.say("failed to connect: ", err)
                return
        end

        local ok, err = red:set(key, value)
        if not ok then
                ngx.say("failed to set to redis: ", err)
                return
        end
        return ok
end

set_to_redis('dog', "Bob")
local rs = get_from_cache('dog')
ngx.say(rs)

View Code

测试:

[root@localhost lua]# curl localhost/cache_lock
get from cache.
Bob
[root@localhost lua]# curl localhost/cache_lock
get from cache.
Bob

7. openresty 执行阶段

nginx的执行阶段分成了很多个阶段,所以第三方模块就可以在某个适当的阶段加入一些处理。openresty进行了简化成了7个阶段:

图片 12

7个阶段的执行顺序如下:

set_by_lua: 流程分支判断,判断变量初始哈

rewrite_by_lua: 用lua脚本实现nginx rewrite

access_by_lua: ip准入,是否能合法性访问,防火墙

content_by_lua: 内存生成

header_filter_by_lua:过滤http头信息,增加头信息

body_filter_by_lua: 内容大小写,内容加密

log_by_lua: 本地/远程记录日志

但是其实我们可以只用
content_by_lua,所有功能都在该阶段完成,也是可以的。