OpenResty + Lua访问Redis,实现高并发访问时的毫秒级响应

什么是OpenResty

天下武功,为快不破。Nginx 的看家本领就是速度,Lua 的拿手好戏亦是速度,这两者的结合在速度上无疑有基因上的优势。最先将 Nginx,Lua 组合到一起的是 OpenResty,它有一个 ngx_lua 模块,将 Lua 嵌入到了 Nginx 里面。本教程从环境搭建到实战讲解,逐步向读者展示如何使用 Nginx+Lua 框架进行开发。


image.png

使用场景

先看一下官网的解释

在 Lua 中混合处理不同 Nginx 模块输出(proxy, drizzle, postgres, Redis, memcached 等)。
在请求真正到达上游服务之前,Lua 中处理复杂的准入控制和安全检查。
比较随意的控制应答头(通过 Lua)。
从外部存储中获取后端信息,并用这些信息来实时选择哪一个后端来完成业务访问。
在内容 handler 中随意编写复杂的 web 应用,同步编写异步访问后端数据库和其他存储。
在 rewrite 阶段,通过 Lua 完成非常复杂的处理。
在 Nginx 子查询、location 调用中,通过 Lua 实现高级缓存机制。
对外暴露强劲的 Lua 语言,允许使用各种 Nginx 模块,自由拼合没有任何限制。该模块的脚本有充分的灵活性,同时提供的性能水平与本地 C 语言程序无论是在 CPU 时间方面以及内存占用差距非常小。所有这些都要求 LuaJIT 2.x 是启用的。其他脚本语言实现通常很难满足这一性能水平。

不擅长的场景

这里官网并没有给出答案,我根据我们的应用场景给大家列举,并简单描述一下原因:
有长时间阻塞调用的过程
例如通过 Lua 完成系统命令行调用
使用阻塞的Lua API完成相应操作
单个请求处理逻辑复杂,尤其是需要和请求方多次交互的长连接场景
Nginx的内存池 pool 是每次新申请内存存放数据
所有的内存释放都是在请求退出的时候统一释放
如果单个请求处理过于复杂,将会有过多内存无法及时释放
内存占用高的处理
受制于Lua VM的最大使用内存 2G 的限制
这个限制是单个Lua VM,也就是单个Nginx worker
两个请求之间有交流的场景
例如你做个在线聊天,要完成两个用户之间信息的传递
当前OpenResty还不具备这个通讯能力(后面可能会有所完善)
与行业专用的组件对接
最好是 TCP 协议对接,不要是 API 方式对接,防止里面有阻塞 TCP 处理
由于OpenResty必须要使用非阻塞 API ,所以传统的阻塞 API ,我们是没法直接使用的
获取 TCP 协议,使用 cosocket 重写(重写后的效率还是很赞的)
每请求开启的 light thread 过多的场景
虽然已经是light thread,但它对系统资源的占用相对是比较大的
这些适合、不适合信息可能在后面随着 OpenResty 的发展都会有新的变化,大家拭目以待。

我工作中遇到的使用场景

  • 场景一 接触lua,是因为我们的网站是一个内容类型的网站,使用了很多的缓存,比如cdn,nginx,redis用来缓解服务器的压力,但是与此同时也会有一些问题,比如需要动态展示的数据,不能实时展示了,比如点赞量,浏览量。只要不主动刷新缓存(而且有这么多层缓存~~),这些本来应该是动态的数据就不会变。那这么动态修改这些数据呢?第一个想到的肯定是通过ajax异步调取,但是从哪获取数据?你会用php、java等语言写一个接口给前端调用吗?如果这样的话就会造成资源浪费,这些非关键数据却需要消耗大量的服务器资源,高并发下更可能会拖累整个网站。这是第一个问题。
  • 第二个使用场景,还是以点赞和浏览量为栗子,用户每点一次赞或者 每浏览一次,或者每评论一次,都要发一次nginx请求去记录数据,如果我们也通过传统的方式,比如nginx通过9000端口调用php处理信息吗?有没有更好的方式呢?
  • 当然有,本文基于上述场景,使用 nginx+lua+redis 实现高性能的接口(前端页面效果略了,只对接口演示)

安装OpenResty

  • docker 拉取镜像
    docker pull openresty/openresty
  • 创建容器,暴露8000端口(注意路径改成自己的)
docker run -d --name openresty -p 8000:80 -v /Users/zhangguofu/app/docker/openresty/conf.d:/etc/nginx/conf.d:Z -v /Users/zhangguofu/app/docker/openresty/data:/data openresty/openresty
  • 这里再重复一次用到的docker命令
  • -d 后台运行
  • --name xxx 容器的名字
  • -p 宿主机端口:容器暴露端口
  • -v 挂载宿主机目录:容器的目录
  • -z选项指明bind mount的内容在多个容器间是共享的
  • -Z选项指明bind mount的内容是私有不共享的

文件配置

  • 在/etc/nginx/conf.d 目录创建 nginx.conf文件,写入以下内容
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
# 包含lua配置文件
include /etc/nginx/conf.d/lua.conf;
  • 继续编写/etc/nginx/conf.d/lua.conf
server {
    listen       80;
    server_name  _;
 # 代理到lua脚本
    location /lua {
        default_type 'text/html';
        content_by_lua_file /etc/nginx/conf.d/conf/lua/test.lua;
    }
}
  • 编写/etc/nginx/conf.d/conf/lua/test.lua脚本测试一下吧
# 在文件中写入迁入的代码
ngx.say("hello world");

重启容器

# 重启 docker
docker restart openresty

# 查看 日志
docker logs -f openresty

在宿主机访问8000端口

image.png

修改业务逻辑

  • 修改 test.lua
ngx.header['Content-Type']="text/html; charset=utf-8";
local info=ngx.req.get_uri_args()["id"];
ngx.say(info);
return;
# 重启容器
zhangguofu@zhangguofudeMBP lua $ docker restart openresty
openresty
#查看输出日志
zhangguofu@zhangguofudeMBP lua $ docker logs -f openresty
2021/01/12 06:46:01 [warn] 1#1: conflicting server name "_" on 0.0.0.0:80, ignored
nginx: [warn] conflicting server name "_" on 0.0.0.0:80, ignored
172.17.0.1 - - [12/Jan/2021:06:46:18 +0000] "GET / HTTP/1.1" 200 1097 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
2021/01/12 06:46:18 [error] 6#6: *2 open() "/usr/local/openresty/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: _, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:8000", referrer: "http://127.0.0.1:8000/"
172.17.0.1 - - [12/Jan/2021:06:46:18 +0000] "GET /favicon.ico HTTP/1.1" 404 561 "http://127.0.0.1:8000/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
172.17.0.1 - - [12/Jan/2021:06:46:25 +0000] "GET /lua HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
2021/01/12 06:51:41 [warn] 1#1: conflicting server name "_" on 0.0.0.0:80, ignored
  • 访问8000端口


    image.png

lua访问redis

  • 此刻,我已经有一个redis服务在运行了
zhangguofu@localhost bin $  ./redis-cli  -h 192.168.9.195 -p 16379 --raw
192.168.9.195:16379>
192.168.9.195:16379>
192.168.9.195:16379> keys *
name
192.168.9.195:16379>

192.168.9.195:16379> set auds 200
OK
192.168.9.195:16379> get auds
200
  • 编写test.lua脚本访问redis
ngx.header['Content-Type']="text/html; charset=utf-8";
local info=ngx.req.get_uri_args()["id"];
local redis = require "resty.redis"
local red = redis:new()

local ok, err = red:connect("192.168.9.195", 16379)

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

ok, err = red:incr(info)
if not ok then
    ngx.say("failed to set dog: ", err)
    return
end

local views=red:get(info)

ngx.say("get result: ", views)
  • 访问8000端口


    image.png

    image.png
192.168.9.195:16379> get auds
205
  • 当然,也可以组装成json 返回
ngx.header['Content-Type']="text/html; charset=utf-8";
local info=ngx.req.get_uri_args()["id"];
local redis = require "resty.redis"
local red = redis:new()
-- 引入json 包
local cjson = require "cjson.safe";

local ok, err = red:connect("192.168.9.195", 16379)

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

ok, err = red:incr(info)
if not ok then
    ngx.say("failed to set dog: ", err)
    return
end

local views=red:get(info)
data_end1={}
data_end1['code']=200;
data_end1['views']=views;
ngx.say(cjson.encode(data_end1))
image.png

总结

这里面用到了lua语言,其实语言对于程序员来说,语言就是一条路而已,通过这条路到达我们的目的地,即实现我们的功能,但是条条大路通罗马,语言很多,能够精通一两个语言,甚至为某个语言开发几个包文件,我觉得就很了不起了。但是我们要不断拓宽自己的知识面,为什么?一个功能,实习生能实现,你也能实现,,甚至一个小白谷歌一下也能写出来,但是差距在哪里?你用了更高效的方式,更贴合需求的方案。

  • 此处小虫同学再叨叨几句,对于一个新的东西,不管是事物、技术、社会现象,如果你想了解它,那么最好是这样做
    • 它是一个什么
    • 为什么会出现
    • 出现带来了哪些影响(好的,坏的)
    • 怎么处理和使用
    • 同质化产品类比
    • 总结经验

顺带附赠历任领导教导我的话

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

推荐阅读更多精彩内容