OpenResty + Lua访问Redis,实现高并发访问时的毫秒级响应_老A技术联盟的博客-程序员秘密

技术标签: nginx  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

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;
# 重启容器
[email protected] lua $ docker restart openresty
openresty
#查看输出日志
[email protected] 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服务在运行了

[email protected] 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.pngimage.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

image.png

总结

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

  • 此处小虫同学再叨叨几句,对于一个新的东西,不管是事物、技术、社会现象,如果你想了解它,那么最好是这样做
    • 它是一个什么

    • 为什么会出现

    • 出现带来了哪些影响(好的,坏的)

    • 怎么处理和使用

    • 同质化产品类比

    • 总结经验

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

  • 凡事清楚为什么这么做。任何事都要做到有理有据。

  • 你不用什么都精通,但是你要知道。知道很重要!

  • 做选择时,你不要选择目标太高的,那是好高骛远。也不要选择目标太低的,容易让人不思进取,你要选择虽然超出你现在的能力范围,但是通过努力你可以获得的!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_39962403/article/details/112538722

智能推荐

黑马程序员——java反射_Kevin_CLJ的博客-程序员秘密

反射概述:Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法Java反射机制提供的功能在运行时判断任意一个对象所属的类在运行时构造任意一个类的对象在运行时判断任意一个类所具有的成员变量和方法在运行时调用任意一个对象的成员变量和方法生成动态代理

Java基本数据类型变量的输入与输出_java输出数字变量_LSlong127的博客-程序员秘密

Java中含有一下8种基本数据类型数据类型关键字在内存中占用的字节数取值范围默认值布尔型boolean1个字节(8位)true,falsefalse字节型byte1个字节(8位)-128 ~ 1270字符型char2个字节(16位)0 ~ 216-1‘\u0000’短整型short2个字节(16位)-215 ~ 215-1...

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb5 in position 1: invalid start byte解决方案_计算法的博客-程序员秘密

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb5 in position 1: invalid start byteimport pandas as pdf = open('cus_loss.csv',encoding="utf-8")# f = open('cus_loss.csv')cus_loss = pd.rea...

Android 改造——SDK从29升级至30引发的一系列问题_android sdk30_呆瓜0302的博客-程序员秘密

SDK29代表支持在安卓10上运行SDK30代表支持在安卓11上运行Android 11 将强制执行分区存储,Android 无法创建和访问自定义目录Android 11 中的存储机制更新:官方文档描述原本的缓存无法访问,如何迁移老数据?官方推荐方法:迁移原有缓存文件——解决SDK从29升级至30后原有缓存无法访问的问题Android 存储用例和最佳做法:官方推荐做法Toast.View已弃用官方文档链接地址:Toast这将意味着在Android 11 上不建议自定义弹出消息的格式

win32下串口读写设置操作(转)_win32 stopbits设置1.5 参数错误_Stef若木的博客-程序员秘密

 在工业控制中,工控机(一般都基于Windows平台)经常需要与智能仪表通过串口进行通信。串口通信方便易行,应用广泛。一般情况下,工控机和各智能仪表通过RS485总线进行通信。RS485的通信方式是半双工的,只能由作为主节点的工控PC机依次轮询网络上的各智能控制单元子节点。每次通信都是由PC机通过串口向智能控制单元发布命令,智能控制单元在接收到正确的命令后作出应答。  在Win32下,可以使用两种...

java split 转义 问号_浅谈Java转义符\\|_我们的美学的博客-程序员秘密

看一段程序String t = "a||b||c||d";String[] temp = t.split("\\|\\|");System.out.println(temp.length);主要是:"\\|\\|" 代表什么意思?开始有点蒙,后来仔细一看明白了,原来是这样的:\\会转义成反斜杠,反斜杠本身就是转义符,所有就成了“\|”,在进行转义就是|,所以\\|实际上是“|”。更深层次研究:ja...

随便推点

每天一个linux命令目录_Jlins的博客-程序员秘密

开始详细系统的学习linux常用命令,坚持每天一个命令,所以这个系列为每天一个linux命令。学习的主要参考资料为: 1.《鸟哥的linux私房菜》 2.http://codingstandards.iteye.com/blog/786653 3.linux命令五分钟系列  4.其他互联网资料,google,baidu等搜索引擎 一. 文件目录操作命令:      1.每天一个

go语言文件逆向认知篇(ida7.6直接绕行)_ida pro golang_qaxd的博客-程序员秘密

1.go语言内置一些复杂的数据类型,并支持类型的组合与方法绑定,这些复杂类型数据在汇编层独特的表示方式和用法。go二进制文件的string数据不是传统的以0x00结尾的C-String,而是用(startAddress,Length)两个元素表示一个string数据;比如一个slice数据要由(StartAddress,Length,Capacity)三个元素表示。在汇编代码中,给一个函数传一个string类型的参数,其实要传两个值;给一个函数传一个slice类型的参数,其实要传3个值。2.独特的调用约定

Linux 查找当前目录下 包含特定字符串 的所有文件_查找当前目录下所有文件内容含有字符串的_CD-xiao的博客-程序员秘密

@[TOC]Linux 查找当前目录下 包含特定字符串 的所有文件使用 Linux 经常会遇到这种情况:只知道文件中包含某些特定的字符串,但是不知道具体的文件名。需要根据“特定的字符串”反向查找文件。示例(路径文件如下):./miracle/luna/a.txta.txt 文件中的内容如下Hello, World!查找当前目录下,包含“Hello”字符串的所有文件【方式1】(其中,r 表示递归, n 表示查询结果显示行号):grep -rn "Hello" ./【方式2】find .

困难负样本挖掘方法——OHEM_Tony Wey的博客-程序员秘密

Online Negative Example Mining论文链接背景:在模型训练中,大量的训练数据中会存在一些难以区分的负样本,找到这样的负样例再进行针对性地训练,能够对模型精度有一定的提升。在 two-stage 的目标检测方法中,经过区域生成算法或者网络生成的 region proposals 通常会经过正负样本的筛选和比例平衡后,才送入之后的检测网络进行训练。但是正负样本的定义和训练的比例是需要人为定义的。如果要筛选 hard mining。通常使用的都是 hard negative min

【vant】van-steps自定义图标_朱迪33的博客-程序员秘密

背景:要自定义步骤条的图片,但是官网上并没有找到相关示例,官网的自定义步骤条icon使用的是属性及vant的icons,我想要的是随便写什么,那就要用官网提供的slot解决:1、官网(vant官网)截图2、实现:直接上代码,注意template的使用位置,内部使用div,用了背景图,so,完全定制。<van-steps direction="vertical" :active="active"> <!--这里使用循环--> .

UniswapV2核心合约学习(1)— UniswapV2Factory.sol_AiMateZero的博客-程序员秘密

Uniswap是当前以太坊上最流行的去中心化交易所,它使用恒定乘积算法来自动做市,任何人都可以在上面创建自己的ERC20代币交易对。本文为个人学习UniswapV2智能合约源码的系列记录文章。

推荐文章

热门文章

相关标签