Nginx Lua开发实战.pdf
http://www.100md.com
2020年11月16日
![]() |
| 第1页 |
![]() |
| 第16页 |
![]() |
| 第28页 |
![]() |
| 第42页 |
![]() |
| 第81页 |
参见附件(2384KB,183页)。
Nginx Lua开发实战是一部讲解如何在Nginx中使用Lua开发应用系统的实战类著作,作者是一位拥有超过20年研发经验的资深技术专家,内容的权威性毋庸置疑。

编辑推荐
适读人群 :所有Web开发者。
(1)作者拥有20+年研发和管理经验,创办过两家公司,现任蛮牛科技CEO兼研发总监。
(2)作者在C++、软件研发、信息安全、物联网、云计算、分布式计算等领域有深厚积累。
(3)作者在Nginx和Lua领域有丰富的实践经验,本书中大量技术和经验都是初次对外呈现。
内容简介
这是一部讲解如何在Nginx中使用Lua开发应用系统的实战类著作,作者是一位拥有超过20年研发经验的资深技术专家,内容的权威性毋庸置疑。
Nginx作为互联网应用系统中的核心服务,被有广泛应用。Nginx通过配置可以实现负载均衡、反向代理等功能,还可以通过扩展开发更为复杂的业务逻辑。这其中,使用Lua语言开发是最方便和最流行的方法。本书以应用系统开发为主线,讲解了相关服务、模块和开发手册,并提供了大量真实的案例。
全书分为5个部分:
第一部分:Nginx基础篇(第1-5章)
首先,全面讲解了Nginx的基本操作,并讲解了MySQL、PostgreSQL、Redis、Memcached、MongoDB、OpenResty的基本操作;其次,分析了Nginx的工作流程和核心技术和架构。
第二部分:Lua脚本语言篇(第6-7章)
深入讲解了Lua脚本语言的语法和Lua通用库,旨在帮助读者掌握Lua的脚本语言,进行业务逻辑编写。
第三部分:Nginx开发技术篇(第8-10章)
讲解了Nginx应用系统开发中常用的相关知识,包括JSON格式、nginx.conf配置和Nginx下Lua实现机制。让读者掌握开发过程中Nginx的配置和使用,同时了解Lua的实现机制,从而掌握在开发中选择正确阶段的使用Lua代码。
第四部分 Nginx Lua开发实战篇(第11-26章)
讲解了Nginx下Lua常用模块以及示例代码,并提供了一个TCP私有服务器实例代码和一个WebSocket接入服务器实例代码。实战开发中,根据业务不同,会使用到非常多的模块,本章讲解了常用的20多个模块,可以*大程度让读者节约查找资料的时间,还提供了2个示例程序,用于理解整个开发流程和技术使用方法。
第五部分 开发手册篇(第27和28章)
提供了ngx-lua-module模块配置命令详解和ngx_lua 函数详解。模块命令和API函数是开发中经常使用到的资料,用于查找函数说明和选择参数。
作者简介
李明江(Leelin)
资深软件开发专家,安防领域技术专家,拥有超过20年的研发经验。创办过两家公司,现任杭州蛮牛技术有限公司CEO兼研发总监。
曾经在信雅达等国内多家上市公司担任研发要职,参与并主导了大量大型项目的研发过程。如南方电网广州亚运会大型安保系统总体研发和管理,担任总负责人;参与中国电信全球眼规范、国家电网安保平台规范、南方电网/国家电网视频监控系统规范、公安部3111规范等规范的制定。
在C++、分布式平台开发、物联网、云计算、APP开发、信息安全等领域有非常深厚的积累,擅长Nginx和Lua开发相关的技术,有非常丰富的实践经验。此外,因为有多年的带团队和创业经历,在团队组建、技术方向确立、核心体系搭建、团队建设、核心技术攻关等方面颇有心得。
Nginx+Lua优势
Nginx+Lua架构带来的改变还远不止节约时间和成本。从做大型系统的角度来看,它还会带来更多的东西:
调试方便:因为它不需要编译代码,相关访问模块是成熟稳定的,只需要调试新加的业务代码即可。大型系统特别是分布式系统,调试一个功能或代码的链条太长了,非常容易出错。
降低耦合:因为架构的限制,代码只能在必需的阶段管理器中开发,代码是一个个.lua文件,耦合性大大降低。
框架良好:因为先进的异步式多进程架构,可以充分利用系统资源。如果自行开发并维护这样一个框架,需要大量的人力、物力。
上手容易:Lua代码良好的结构和可读性,使其上手速度更快。团队成员经过快速培训就可以上手。
Nginx Lua开发实战截图




前言 前言
天下武功,为快不破。Nginx 的看家本领就是速度,Lua 的拿手好戏亦是速度,这两者的结合在速度上无疑有基
因上的优势。最先将 Nginx,Lua 组合到一起的是 OpenResty,它有一个 ngx_lua 模块,将 Lua 嵌入到了 N
ginx 里面。本教程从环境搭建到实战讲解,逐步向读者展示如何使用 Nginx+Lua 框架进行开发。
适用人群 适用人群
开发高速高并发网站的程序员。
学习前提 学习前提
学习本教程前,你需要了解 Java、Lua 等编程语言。
鸣谢:http:jinnianshilongnian.iteye.comcategory333854目录 目录
前言 前言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1
第 1 章 第 1 章 安装 Nginx+Lua 开发环境 安装 Nginx+Lua 开发环境 . . . . . . . . . . . . . . . . 5 5
安装环境 . . . . . . . . . . . . . . 7
配置环境 . . . . . . . . . . . . . . 10
HelloWorld. . . . . . . . . . . . . . 12
nginx+lua 项目构建 . . . . . . . . . . . . 15
第 2 章 第 2 章 Nginx+Lua 开发入门 Nginx+Lua 开发入门 . . . . . . . . . . . . . . . . . . . . 17 17
Nginx 入门 . . . . . . . . . . . . . . 18
第 2 章 第 2 章 Lua 入门 Lua 入门. . . . . . . . . . . . . . . . . . . . . . 19 19
Nginx Lua API . . . . . . . . . . . . . 21
Nginx Lua 模块指令. . . . . . . . . . . . 27
第 3 章 第 3 章 RedisSSDB+Twemproxy 安装与使用 RedisSSDB+Twemproxy 安装与使用. . . . . . . . . . . . . . 37 37
Redis 安装与使用. . . . . . . . . . . . 39
SSDB 安装与使用. . . . . . . . . . . . 41
Twemproxy 安装与使用. . . . . . . . . . . 43
Redis 设置 . . . . . . . . . . . . . 45
Twemproxy 设置. . . . . . . . . . . . 51
第 4 章 第 4 章 Lua 模块开发 Lua 模块开发 . . . . . . . . . . . . . . . . . . . . 56 56
第 5 章 第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 常用 Lua 开发库 1-redis、mysql、http 客户端. . . . . . . . . . . . 60 60
Redis 客户端 . . . . . . . . . . . . . 62
Mysql 客户端 . . . . . . . . . . . . . 67
Http 客户端. . . . . . . . . . . . . . 72
第 6 章 第 6 章 JSON 库、编码转换、字符串处理 JSON 库、编码转换、字符串处理 . . . . . . . . . . . . . . 76 76JSON 库. . . . . . . . . . . . . . 77
第 7 章 第 7 章 常用 Lua 开发库 3-模板渲染 常用 Lua 开发库 3-模板渲染 . . . . . . . . . . . . . . . . 90 90
模板位置 . . . . . . . . . . . . . . 92
使用示例 . . . . . . . . . . . . . . 96
第 8 章 第 8 章 HTTP 服务 HTTP 服务. . . . . . . . . . . . . . . . . . . . . . 99 99
架构 . . . . . . . . . . . . . . 101
实现 . . . . . . . . . . . . . . 105
后台逻辑 . . . . . . . . . . . . . . 106
前台逻辑 . . . . . . . . . . . . . . 107
项目搭建 . . . . . . . . . . . . . . 108
Redis+Twemproxy 配置. . . . . . . . . . . 109
Mysql+Atlas 配置 . . . . . . . . . . . . 111
Java+Tomcat 安装 . . . . . . . . . . . . 115
Java+Tomcat 逻辑开发. . . . . . . . . . . 117
Nginx+Lua 逻辑开发 . . . . . . . . . . . . 123
第 9 章 第 9 章 Web 开发实战2——商品详情页 Web 开发实战2——商品详情页 . . . . . . . . . . . . . . . . 127 127
技术选型 . . . . . . . . . . . . . . 130
核心流程 . . . . . . . . . . . . . . 131
项目搭建 . . . . . . . . . . . . . . 108
数据存储实现 . . . . . . . . . . . . . 133
动态服务实现 . . . . . . . . . . . . . 146
前端展示实现 . . . . . . . . . . . . . 155
商品介绍 . . . . . . . . . . . . . . 157
前端展示 . . . . . . . . . . . . . . 159
第 10 章 第 10 章 流量复制 AB 测试协程 流量复制 AB 测试协程 . . . . . . . . . . . . . . . . 173 173
流量复制 . . . . . . . . . . . . . . 174
AB 测试 . . . . . . . . . . . . . . 176协程 . . . . . . . . . . . . . . 1791 1
安装 Nginx+Lua 开发环境 安装 Nginx+Lua 开发环境首先我们选择使用 OpenResty,其是由 Nginx 核心加很多第三方模块组成,其最大的亮点是默认集成了 Lua 开
发环境,使得 Nginx 可以作为一个 Web Server 使用。借助于 Nginx 的事件驱动模型和非阻塞 IO,可以实现高
性能的 Web 应用程序。而且 OpenResty 提供了大量组件如 Mysql、Redis、Memcached 等等,使在 Nginx
上开发Web 应用更方便更简单。目前在京东如实时价格、秒杀、动态服务、单品页、列表页等都在使用Nginx+L
ua 架构,其他公司如淘宝、去哪儿网等。
第 1 章 安装 Nginx+Lua 开发环境 | 6安装环境 安装环境
安装步骤可以参考 http:openresty.orgInstallation。
创建目录 usrservers,以后我们把所有软件安装在此目录 创建目录 usrservers,以后我们把所有软件安装在此目录
Java 代码 代码 收藏代码
mkdir -p usrservers
cd usrservers
安装依赖(我的环境是 ubuntu,可以使用如下命令安装,其他的可以参考 openresty 安装 安装依赖(我的环境是 ubuntu,可以使用如下命令安装,其他的可以参考 openresty 安装 步骤) 步骤)
Java 代码 代码 收藏代码
apt-get install libreadline-dev libncurses5-dev libpcre3-dev libssl-dev perl
下载 ngx_openresty-1.7.7.2.tar.gz 并解压 下载 ngx_openresty-1.7.7.2.tar.gz 并解压
Java 代码 代码 收藏代码
wget http:openresty.orgdownloadngx_openresty-1.7.7.2.tar.gz
tar -xzvf ngx_openresty-1.7.7.2.tar.gz
ngx_openresty-1.7.7.2bundle 目录里存放着 nginx 核心和很多第三方模块,比如有我们需要的 Lua 和 LuaJ
IT。
安装 LuaJIT 安装 LuaJIT
Java 代码 代码 收藏代码
cd bundleLuaJIT-2.1-20150120
make clean make make install
ln -sf luajit-2.1.0-alpha usrlocalbinluajit
第 1 章 安装 Nginx+Lua 开发环境 | 7下载 ngx_cache_purge 模块,该模块用于清理 nginx 缓存 下载 ngx_cache_purge 模块,该模块用于清理 nginx 缓存
Java 代码 代码 收藏代码
cd usrserversngx_openresty-1.7.7.2bundle
wget https:github.comFRiCKLEngx_cache_purgearchive2.3.tar.gz
tar -xvf 2.3.tar.gz
下载 nginx_upstream_check_module 模块,该模块用于 ustream 健康检查 下载 nginx_upstream_check_module 模块,该模块用于 ustream 健康检查
Java 代码 代码 收藏代码
cd usrserversngx_openresty-1.7.7.2bundle
wget https:github.comyaoweibinnginx_upstream_check_modulearchivev0.3.0.tar.gz
tar -xvf v0.3.0.tar.gz
安装 ngx_openresty 安装 ngx_openresty
Java 代码 代码 收藏代码
cd usrserversngx_openresty-1.7.7.2
.configure --prefix=usrservers --with-http_realip_module --with-pcre --with-luajit --add-module=.bundlengx_cache_purge-2.3 --add-module=.bundlenginx_upstream_check_module-0.3.0 -j2
make make install--with 安装一些内置集成的模块--with-http_realip_module 取用户真实 ip 模块
-with-pcre Perl 兼容的达式模块--with-luajit 集成 luajit 模块--add-module 添加自定义的第三方模块,如此次的 ngx_che_purge
到 usrservers 目录下 到 usrservers 目录下
Java 代码 代码 收藏代码
cd usrservers
ll
第 1 章 安装 Nginx+Lua 开发环境 | 8会发现多出来了如下目录,说明安装成功
usrserversluajit usrserversluajit :luajit 环境,luajit 类似于 java 的 jit,即即时编译,lua 是一种解释语言,通过 luajit 可以
即时编译 lua 代码到机器代码,得到很好的性能;
usrserverslualib usrserverslualib:要使用的 lua 库,里边提供了一些默认的 lua 库,如 redis,json 库等,也可以把一些自
己开发的或第三方的放在这;
usrserversnginx :安装的 nginx;
通过 usrserversnginxsbinnginx -V 查看 nginx 版本和安装的模块
启动 nginx 启动 nginx
usrserversnginxsbinnginx
接下来该配置 nginx+lua 开发环境了
第 1 章 安装 Nginx+Lua 开发环境 | 9配置环境 配置环境
配置及 Nginx HttpLuaModule 文档在可以查看 http:openresty.orgInstallation。
编辑 nginx.conf 配置文件 编辑 nginx.conf 配置文件
Java 代码 代码 收藏代码
vim usrserversnginxconfnginx.conf
在 http 部分添加如下配置 在 http 部分添加如下配置
Java 代码 代码 收藏代码
\lua模块路径,多个之间”;”分隔,其中”;;”表示默认搜索路径,默认到usrserversnginx下找
lua_package_path usrserverslualib?.lua;;; lua 模块
lua_package_cpath usrserverslualib?.so;;; c模块
为了方便开发我们在 usrserversnginxconf 目录下创建一个 lua.conf 为了方便开发我们在 usrserversnginxconf 目录下创建一个 lua.conf
Java 代码 代码 收藏代码
\lua.conf
server {
listen 80;
server_name _;
}
在 nginx.conf 中的 http 部分添加 include lua.conf 包含此文件片段 在 nginx.conf 中的 http 部分添加 include lua.conf 包含此文件片段
Java 代码 代码 收藏代码
include lua.conf;
第 1 章 安装 Nginx+Lua 开发环境 | 10测试是否正常 测试是否正常
Java 代码 代码 收藏代码
usrserversnginxsbinnginx -t
如果显示如下内容说明配置成功
nginx: the configuration file usrserversnginxconfnginx.conf syntax is ok
nginx: configuration file usrserversnginxconfnginx.conf test is successful
第 1 章 安装 Nginx+Lua 开发环境 | 11HelloWorld HelloWorld
在 lua.conf 中 server 部分添加如下配置 在 lua.conf 中 server 部分添加如下配置
Java 代码 代码 收藏代码
location lua {
default_type 'texthtml';
content_by_lua 'ngx.say(hello world)';
}
测试配置是否正确 测试配置是否正确
Java 代码 代码 收藏代码
usrserversnginxsbinnginx -t
重启 nginx 重启 nginx
Java 代码 代码 收藏代码
usrserversnginxsbinnginx -s reload
访问如 访问如 http:192.168.1.6lua http:192.168.1.6lua (自己的机器根据实际情况换 ip),可以看到如下内容 (自己的机器根据实际情况换 ip),可以看到如下内容
hello world
lua 代码文件 lua 代码文件
我们把 lua 代码放在 nginx 配置中会随着 lua 的代码的增加导致配置文件太长不好维护,因此我们应该把 lua 代
码移到外部文件中存储。
Java 代码 代码 收藏代码
vim usrserversnginxconfluatest.lua
第 1 章 安装 Nginx+Lua 开发环境 | 12Java 代码 代码 收藏代码
\添加如下内容
ngx.say(hello world);
然后 lua.conf 修改为
Java 代码 代码 收藏代码
location lua {
default_type 'texthtml';
content_by_lua_file confluatest.lua; 相对于nginx安装目录
}
此处 confluatest.lua 也可以使用绝对路径 usrserversnginxconfluatest.lua。
lua_code_cache lua_code_cache
默认情况下 lua_code_cache 是开启的,即缓存 lua 代码,即每次 lua 代码变更必须reload nginx 才生效,如
果在开发阶段可以通过 lua_code_cache off;关闭缓存,这样调试时每次修改 lua 代码不需要 reload nginx;但
是正式环境一定记得开启缓存。
Java 代码 代码 收藏代码
location lua {
default_type 'texthtml';
lua_code_cache off;
content_by_lua_file confluatest.lua;
}
开启后 reload nginx 会看到如下报警
nginx: [alert] lua_code_cache is off; this will hurt performance in usrserversnginxconflua.conf:8
错误日志 错误日志
如果运行过程中出现错误,请不要忘记查看错误日志。
Java 代码 代码 收藏代码
tail -f usrserversnginxlogserror.log
第 1 章 安装 Nginx+Lua 开发环境 | 13到此我们的基本环境搭建完毕。
第 1 章 安装 Nginx+Lua 开发环境 | 14nginx+lua 项目构建 nginx+lua 项目构建
以后我们的 nginx lua 开发文件会越来越多,我们应该把其项目化,已方便开发。项目目录结构如下所示:
example
example.conf ---该项目的nginx 配置文件
lua ---我们自己的lua代码
test.lua
lualib ---lua依赖库第三方依赖
.lua
.so
其中我们把 lualib 也放到项目中的好处就是以后部署的时候可以一起部署,防止有的服务器忘记复制依赖而造成
缺少依赖的情况。
我们将项目放到到 usrexample 目录下。
usrserversnginxconfnginx.conf 配置文件如下(此处我们最小化了配置文件)
Java 代码 代码 收藏代码
\user nobody;
worker_processes 2;
error_log logserror.log;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type texthtml;
lua模块路径,其中”;;”表示默认搜索路径,默认到usrserversnginx下找
lua_package_path usrexamplelualib?.lua;;; lua 模块
lua_package_cpath usrexamplelualib?.so;;; c模块
include usrexampleexample.conf;
}
通过绝对路径包含我们的 lua 依赖库和 nginx 项目配置文件。
usrexampleexample.conf 配置文件如下
Java 代码 代码 收藏代码
第 1 章 安装 Nginx+Lua 开发环境 | 15server {
listen 80;
server_name _;
location lua {
default_type 'texthtml';
lua_code_cache off;
content_by_lua_file usrexampleluatest.lua;
}
}
lua 文件我们使用绝对路径 usrexampleluatest.lua。
到此我们就可以把 example 扔 svn 上了。
第 1 章 安装 Nginx+Lua 开发环境 | 162 2
Nginx+Lua 开发入门 Nginx+Lua 开发入门Nginx 入门 Nginx 入门
本文目的是学习 Nginx+Lua 开发,对于 Nginx 基本知识可以参考如下文章:
nginx 启动、关闭、重启
http:www.cnblogs.comderekchenarchive201102171957209.html
agentzh 的 Nginx 教程
http:openresty.orgdownloadagentzh-nginx-tutorials-zhcn.html
Nginx+Lua 入门
http:17173ops.com2013110117173-ngx-lua-manual.shtml
nginx 配置指令的执行顺序
http:zhongfox.github.ioblogserver20130515nginx-exec-order
nginx 与 lua 的执行顺序和步骤说明
http:www.mrhaoting.com?p=157
Nginx 配置文件 nginx.conf 中文详解
http:www.ha97.com5194.html
Tengine 的 Nginx 开发从入门到精通
http:tengine.taobao.orgbook
官方文档
http:wiki.nginx.orgConfiguration
第 2 章 Nginx+Lua 开发入门 | 18第 2 章 Lua 入门 第 2 章 Lua 入门
第 2 章 Lua 入门 | 19本文目的是学习 Nginx+Lua 开发,对于 Lua 基本知识可以参考如下文章:
Lua 简明教程
http:coolshell.cnarticles10739.html
lua 在线 lua 学习教程
http:book.luaer.cn
Lua 5.1 参考手册
http:www.codingnow.com2000downloadlua_manual.html
Lua 5.3 参考手册
http:cloudwu.github.iolua53doc
第 2 章 Lua 入门 | 20Nginx Lua API Nginx Lua API
和一般的 Web Server 类似,我们需要接收请求、处理并输出响应。而对于请求我们需要获取如请求参数、请求
头、Body 体等信息;而对于处理就是调用相应的 Lua 代码即可;输出响应需要进行响应状态码、响应头和响应
内容体的输出。因此我们从如上几个点出发即可。
接收请求 接收请求
example.conf 配置文件 example.conf 配置文件
Java 代码 收藏代码
location ~ lua_request(\d+)(\d+) {
设置nginx变量
set a 1;
set b host;
default_type texthtml;
nginx内容处理
content_by_lua_file usrexampleluatest_request.lua;
内容体处理完成后调用
echo_after_body ngx.var.b b;
}
test_request.lua test_request.lua
Java 代码 收藏代码--nginx变量
local var = ngx.var
ngx.say(ngx.var.a : , var.a,
)
ngx.say(ngx.var.b : , var.b,
)
ngx.say(ngx.var[2] : , var[2],
)
ngx.var.b = 2;
ngx.say(
)--请求头
local headers = ngx.req.get_headers
ngx.say(headers begin,
)
ngx.say(Host : , headers[Host],
)
第 2 章 Lua 入门 | 21ngx.say(user-agent : , headers[user-agent],
)
ngx.say(user-agent : , headers.user_agent,
)
for k,v in pairs(headers) do
if type(v) == table then
ngx.say(k, : , table.concat(v, ,),
)
else
ngx.say(k, : , v,
)
end
end
ngx.say(headers end,
)
ngx.say(
)--get请求uri参数
ngx.say(uri args begin,
)
local uri_args = ngx.req.get_uri_args
for k, v in pairs(uri_args) do
if type(v) == table then
ngx.say(k, : , table.concat(v, , ),
)
else
ngx.say(k, : , v,
)
end
end
ngx.say(uri args end,
)
ngx.say(
)--post请求参数
ngx.req.read_body
ngx.say(post args begin,
)
local post_args = ngx.req.get_post_args
for k, v in pairs(post_args) do
if type(v) == table then
ngx.say(k, : , table.concat(v, , ),
)
else
ngx.say(k, : , v,
)
end
end
ngx.say(post args end,
)
ngx.say(
)--请求的http协议版本
ngx.say(ngx.req.http_version : , ngx.req.http_version,
)--请求方法
ngx.say(ngx.req.get_method : , ngx.req.get_method,
)--原始的请求头内容
ngx.say(ngx.req.raw_header : , ngx.req.raw_header,
)--请求的body内容体
第 2 章 Lua 入门 | 22ngx.say(ngx.req.get_body_data : , ngx.req.get_body_data,
)
ngx.say(
)
ngx.var ngx.var : nginx 变量,如果要赋值如 ngx.var.b = 2,此变量必须提前声明;另外对于 nginx location 中使用
正则捕获的捕获组可以使用 ngx.var [捕获组数字]获取;
ngx.req.get_headers ngx.req.get_headers:获取请求头,默认只获取前100,如果想要获取所以可以调用ngx.req.get_header
s(0);获取带中划线的请求头时请使用如 headers.user_agent 这种方式;如果一个请求头有多个值,则返回的
是 table;
ngx.req.get_uri_args ngx.req.get_uri_args:获取 url 请求参数,其用法和 get_headers 类似;
ngx.req.get_post_args ngx.req.get_post_args:获取 post 请求内容体,其用法和 get_headers 类似,但是必须提前调用 ngx.req.r
ead_body 来读取 body 体(也可以选择在 nginx 配置文件使用lua_need_request_body on;开启读取 bod
y 体,但是官方不推荐);
ngx.req.raw_header ngx.req.raw_header:未解析的请求头字符串;
ngx.req.get_body_data ngx.req.get_body_data:为解析的请求 body 体内容字符串。
如上方法处理一般的请求基本够用了。另外在读取 post 内容体时根据实际情况设置 client_body_buffer_size
和 client_max_body_size 来保证内容在内存而不是在文件中。
使用如下脚本测试
Java 代码 代码 收藏代码
wget --post-data 'a=1b=2' 'http:127.0.0.1lua_request12?a=3b=4' -O -
输出响应 输出响应
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_response_1 {
default_type texthtml;
content_by_lua_file usrexampleluatest_response_1.lua;
}
第 2 章 Lua 入门 | 23test_response_1.lua test_response_1.lua
Java 代码 代码 收藏代码--写响应头
ngx.header.a = 1--多个响应头可以使用table
ngx.header.b = {2, 3}--输出响应
ngx.say(a, b,
)
ngx.print(c, d,
)--200状态码退出
return ngx.exit(200)
ngx.header:输出响应头;
ngx.print:输出响应内容体;
ngx.say:通ngx.print,但是会最后输出一个换行符;
ngx.exit:指定状态码退出。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_response_2 {
default_type texthtml;
content_by_lua_file usrexampleluatest_response_2.lua;
}
test_response_2.lua test_response_2.lua
Java 代码 代码 收藏代码
ngx.redirect(http:jd.com, 302)
ngx.redirect ngx.redirect:重定向;
ngx.status= 状态码,设置响应的状态码;ngx.resp.get_headers 获取设置的响应状态码;ngx.send_head
ers 发送响应状态码,当调用 ngx.sayngx.print 时自动发送响应状态码;可以通过 ngx.headers_sent=true
判断是否发送了响应状态码。
第 2 章 Lua 入门 | 24其他 API 其他 API
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_other {
default_type texthtml;
content_by_lua_file usrexampleluatest_other.lua;
}
test_other.lua test_other.lua
Java 代码 代码 收藏代码--未经解码的请求uri
local request_uri = ngx.var.request_uri;
ngx.say(request_uri : , request_uri,
);--解码
ngx.say(decode request_uri : , ngx.unescape_uri(request_uri),
);--MD5
ngx.say(ngx.md5 : , ngx.md5(123),
)--http time
ngx.say(ngx.http_time : , ngx.http_time(ngx.time),
)
ngx.escape_uringx.unescape_uri : uri 编码解码;
ngx.encode_argsngx.decode_args:参数编码解码;
ngx.encode_base64ngx.decode_base64:BASE64 编码解码;
ngx.re.match:nginx 正则表达式匹配;
更多 Nginx Lua API 请参考 http:wiki.nginx.orgHttpLuaModuleNginx_API_for_Lua。
Nginx 全局内存 Nginx 全局内存
使用过如 Java 的朋友可能知道如 Ehcache 等这种进程内本地缓存,Nginx 是一个 Master 进程多个 Worker
进程的工作方式,因此我们可能需要在多个 Worker 进程中共享数据,那么此时就可以使用 ngx.shared.DICT
来实现全局内存共享。
第 2 章 Lua 入门 | 25首先在 nginx.conf 的 http 部分分配内存大小 首先在 nginx.conf 的 http 部分分配内存大小
Java 代码 代码 收藏代码
\共享全局变量,在所有worker间共享
lua_shared_dict shared_data 1m;
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_shared_dict {
default_type texthtml;
content_by_lua_file usrexampleluatest_lua_shared_dict.lua;
}
test_lua_shared_dict.lua test_lua_shared_dict.lua
Java 代码 代码 收藏代码--1、获取全局共享内存变量
local shared_data = ngx.shared.shared_data--2、获取字典值
local i = shared_data:get(i)
if not i then
i = 1--3、惰性赋值
shared_data:set(i, i)
ngx.say(lazy set i , i,
)
end--递增
i = shared_data:incr(i, 1)
ngx.say(i=, i,
)
更多 API 请参考 http:wiki.nginx.orgHttpLuaModulengx.shared.DICT。
到此基本的 Nginx Lua API 就学完了,对于请求处理和输出响应如上介绍的 API 完全够用了,更多 API 请参考
官方文档。
第 2 章 Lua 入门 | 26Nginx Lua 模块指令 Nginx Lua 模块指令
Nginx 共11个处理阶段,而相应的处理阶段是可以做插入式处理,即可插拔式架构;另外指令可以在 http、serv
er、server if、location、location if 几个范围进行配置:
指令 所处处理
阶段
使用范围 解释
init_by_lua
init_by_lu
a_file
loading-
config
http nginx Master进程加载配置时执行;
通常用于初始化全局配置预加载Lua模块
init_worke
r_by_lua
init_worke
r_by_lua_f
ile
startin
g-worke
r
http 每个Nginx Worker进程启动时调用的计时
器,如果Master进程不允许则只会在init_by_l
ua之后调用;
通常用于定时拉取配置数据,或者后端服务的
健康检查
set_by_lu
a
set_by_lu
a_file
rewrite server,server i
f,location,locati
on if
设置nginx变量,可以实现复杂的赋值逻辑;此
处是阻塞的,Lua代码要做到非常快;
rewrite_b
y_lua
rewrite_b
y_lua_file
rewrite t
ail
http,server,loc
ation,location i
f
rrewrite阶段处理,可以实现复杂的转发重定
向逻辑;
access_b
y_lua
access t
ail
http,server,loc
ation,location i
f
请求访问阶段处理,用于访问控制
第 2 章 Lua 入门 | 27access_b
y_lua_file
content_b
y_lua
content_b
y_lua_file
content location,locat
ion if
内容处理器,接收请求处理并输出响应
header_filt
er_by_lua
header_filt
er_by_lu
a_file
output-
header-
filter
http,server,l
ocation,locati
on if
设置header和cookie
body_filte
r_by_lua
body_filte
r_by_lua_f
ile
output-
body-filt
er
http,server,l
ocation,locati
on if
对响应数据进行过滤,比如截断、替换。
log_by_lu
a
log_by_lu
a_file
log http,server,l
ocation,locati
on if
log阶段处理,比如记录访问量统计平均响应
时间
更详细的解释请参考 http:wiki.nginx.orgHttpLuaModuleDirectives。如上指令很多并不常用,因此我们只
拿其中的一部分做演示。
第 2 章 Lua 入门 | 28init_by_lua init_by_lua
每次 Nginx 重新加载配置时执行,可以用它来完成一些耗时模块的加载,或者初始化一些全局配置;在 Master
进程创建 Worker 进程时,此指令中加载的全局变量会进行 Copy-OnWrite,即会复制到所有全局变量到 Work
er 进程。
nginx.conf 配置文件中的 http 部分添加如下代码 nginx.conf 配置文件中的 http 部分添加如下代码
Java 代码 代码 收藏代码
\共享全局变量,在所有worker间共享
lua_shared_dict shared_data 1m;
init_by_lua_file usrexampleluainit.lua;
init.lua init.lua
Java 代码 代码 收藏代码--初始化耗时的模块
local redis = require 'resty.redis'
local cjson = require 'cjson'--全局变量,不推荐
count = 1--共享全局内存
local shared_data = ngx.shared.shared_data
shared_data:set(count, 1)
test.lua test.lua
Java 代码 代码 收藏代码
count = count + 1
ngx.say(global variable : , count)
local shared_data = ngx.shared.shared_data
ngx.say(, shared memory : , shared_data:get(count))
shared_data:incr(count, 1)
ngx.say(hello world)
第 2 章 Lua 入门 | 29访问如 http:192.168.1.2lua 会发现全局变量一直不变,而共享内存一直递增 访问如 http:192.168.1.2lua 会发现全局变量一直不变,而共享内存一直递增
global variable : 2 , shared memory : 8 hello world
另外注意一定在生产环境开启 lua_code_cache,否则每个请求都会创建 Lua VM 实例。
init_worker_by_lua init_worker_by_lua
用于启动一些定时任务,比如心跳检查,定时拉取服务器配置等等;此处的任务是跟 Worker 进程数量有关系
的,比如有2个 Worker 进程那么就会启动两个完全一样的定时任务。
nginx.conf 配置文件中的 http 部分添加如下代码 nginx.conf 配置文件中的 http 部分添加如下代码
Java 代码 代码 收藏代码
init_worker_by_lua_file usrexampleluainit_worker.lua;
init_worker.lua init_worker.lua
Java 代码 代码 收藏代码
local count = 0
local delayInSeconds = 3
local heartbeatCheck = nil
heartbeatCheck = function(args)
count = count + 1
ngx.log(ngx.ERR, do check , count)
local ok, err = ngx.timer.at(delayInSeconds, heartbeatCheck)
if not ok then
ngx.log(ngx.ERR, failed to startup heartbeart worker..., err)
end
end
heartbeatCheck
第 2 章 Lua 入门 | 30ngx.timer.at ngx.timer.at:延时调用相应的回调方法;ngx.timer.at(秒单位延时,回调函数,回调函数的参数列表);可以将
延时设置为0即得到一个立即执行的任务,任务不会在当前请求中执行不会阻塞当前请求,而是在一个轻量级线程
中执行。
另外根据实际情况设置如下指令
lua_max_pending_timers 1024; 最大等待任务数
lua_max_running_timers 256; 最大同时运行任务数
set_by_lua set_by_lua
设置 nginx 变量,我们用的 set 指令即使配合 if 指令也很难实现负责的赋值逻辑;
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_set_1 {
default_type texthtml;
set_by_lua_file num usrexampleluatest_set_1.lua;
echo num;
}
set_by_lua_file set_by_lua_file:语法 set_by_lua_file var lua_file arg1 arg2...; 在 lua代码中可以实现所有复杂的逻辑,但
是要执行速度很快,不要阻塞;
test_set_1.lua test_set_1.lua
Java 代码 代码 收藏代码
local uri_args = ngx.req.get_uri_args
local i = uri_args[i] or 0
local j = uri_args[j] or 0
return i + j
得到请求参数进行相加然后返回。
访问如 http:192.168.1.2lua_set_1?i=1j=10 进行测试。 如果我们用纯 set 指令是无法实现的。
再举个实际例子,我们实际工作时经常涉及到网站改版,有时候需要新老并存,或者切一部分流量到新版
第 2 章 Lua 入门 | 31首先在 example.conf 中使用 map 指令来映射 host 到指定 nginx 变量,方便我们测试 首先在 example.conf 中使用 map 指令来映射 host 到指定 nginx 变量,方便我们测试
Java 代码 代码 收藏代码
测试时使用的动态请求
map host item_dynamic {
default 0;
item2014.jd.com 1;
}
如绑定 hosts
192.168.1.2 item.jd.com ;
192.168.1.2 item2014.jd.com ;
此时我们想访问 item2014.jd.com 时访问新版,那么我们可以简单的使用如
Java 代码 代码 收藏代码
if (item_dynamic = 1) {
proxy_pass http:new;
}
proxy_pass http:old;
但是我们想把商品编号为 8 位(比如品类为图书的)没有改版完成,需要按照相应规则跳转到老版,但是其他的到
新版;虽然使用 if 指令能实现,但是比较麻烦,基本需要这样
Java 代码 代码 收藏代码
set jump 0;
if(item_dynamic = 1) {
set jump 1;
}
if(uri ~ ^6[0-9]{7}.html) {
set jump {jump}2;
}
\非强制访问新版,且访问指定范围的商品
if (jump == 02) {
proxy_pass http:old;
}
proxy_pass http:new;
第 2 章 Lua 入门 | 32以上规则还是比较简单的,如果涉及到更复杂的多重 ifelse 或嵌套 ifelse 实现起来就更痛苦了,可能需要到后
端去做了;此时我们就可以借助 lua 了:
Java 代码 代码 收藏代码
set_by_lua to_book '
local ngx_match = ngx.re.match
local var = ngx.var
local skuId = var.skuId
local r = var.item_dynamic ~= 1 and ngx.re.match(skuId, ^[0-9]{8})
if r then return 1 else return 0 end;
';
set_by_lua to_mvd '
local ngx_match = ngx.re.match
local var = ngx.var
local skuId = var.skuId
local r = var.item_dynamic ~= 1 and ngx.re.match(skuId, ^[0-9]{9})
if r then return 1 else return 0 end;
';
\自营图书
if (to_book) {
proxy_pass http:127.0.0.1old_bookskuId.html;
}
\自营音像
if (to_mvd) {
proxy_pass http:127.0.0.1old_mvdskuId.html;
}
\默认
proxy_pass http:127.0.0.1proxyskuId.html;
rewrite_by_lua rewrite_by_lua
执行内部 URL 重写或者外部重定向,典型的如伪静态化的 URL 重写。其默认执行在 rewrite 处理阶段的最后。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_rewrite_1 {
default_type texthtml;
rewrite_by_lua_file usrexampleluatest_rewrite_1.lua;
第 2 章 Lua 入门 | 33echo no rewrite;
}
test_rewrite_1.lua test_rewrite_1.lua
Java 代码 代码 收藏代码
if ngx.req.get_uri_args[jump] == 1 then
return ngx.redirect(http:www.jd.com?jump=1, 302)
end
当我们请求 http:192.168.1.2lua_rewrite_1 时发现没有跳转,而请求 http:192.168.1.2lua_rewrite_1?jum
p=1 时发现跳转到京东首页了。 此处需要301302跳转根据自己需求定义。
example.conf 配置文件 example.conf 配置文件
Java 代码 收藏代码
location lua_rewrite_2 {
default_type texthtml;
rewrite_by_lua_file usrexampleluatest_rewrite_2.lua;
echo rewrite2 uri : uri, a : arg_a;
}
test_rewrite_2.lua test_rewrite_2.lua
Java 代码 收藏代码
if ngx.req.get_uri_args[jump] == 1 then
ngx.req.set_uri(lua_rewrite_3, false);
ngx.req.set_uri(lua_rewrite_4, false);
ngx.req.set_uri_args({a = 1, b = 2});
end
ngx.req.set_uri(uri, false) ngx.req.set_uri(uri, false):可以内部重写 uri(可以带参数),等价于 rewrite ^ lua_rewrite_3;通过配合 if
else 可以实现 rewrite ^ lua_rewrite_3 break;这种功能;此处两者都是 location 内部 url 重写,不会重新发
起新的 location 匹配;
ngx.req.set_uri_args ngx.req.set_uri_args:重写请求参数,可以是字符串(a=1b=2)也可以是 table;
访问如 http:192.168.1.2lua_rewrite_2?jump=0 时得到响应 rewrite2 uri : lua_rewrite_2, a :
第 2 章 Lua 入门 | 34访问如 http:192.168.1.2lua_rewrite_2?jump=1 时得到响应 rewrite2 uri : lua_rewrite_4, a : 1
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_rewrite_3 {
default_type texthtml;
rewrite_by_lua_file usrexampleluatest_rewrite_3.lua;
echo rewrite3 uri : uri;
}
c test_rewrite_3.lua c test_rewrite_3.lua
Java 代码 代码 收藏代码
if ngx.req.get_uri_args[jump] == 1 then
ngx.req.set_uri(lua_rewrite_4, true);
ngx.log(ngx.ERR, =========)
ngx.req.set_uri_args({a = 1, b = 2});
end
ngx.req.set_uri(uri, true) ngx.req.set_uri(uri, true):可以内部重写 uri,即会发起新的匹配 location 请求,等价于 rewrite ^ lua_rewrit
e_4 last;此处看 error log 是看不到我们记录的log。
所以请求如 http:192.168.1.2lua_rewrite_3?jump=1 会到新的 location 中得到响应,此处没有 lua_rewrit
e_4,所以匹配到 lua 请求,得到类似如下的响应 global variable : 2 , shared memory : 1 hello world
即
rewrite ^ lua_rewrite_3; 等价于 ngx.req.set_uri(lua_rewrite_3, false);
rewrite ^ lua_rewrite_3 break; 等价于 ngx.req.set_uri(lua_rewrite_3, false); 加 ifelse判断breakreturn
rewrite ^ lua_rewrite_4 last; 等价于 ngx.req.set_uri(lua_rewrite_4, true);
注意,在使用 rewrite_by_lua 时,开启 rewrite_log on;后也看不到相应的 rewrite log。
access_by_lua access_by_lua
用于访问控制,比如我们只允许内网 ip 访问,可以使用如下形式
Java 代码 代码 收藏代码
第 2 章 Lua 入门 | 35allow 127.0.0.1;
allow 10.0.0.08;
allow 192.168.0.016;
allow 172.16.0.012;
deny all;
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_access {
default_type texthtml;
access_by_lua_file usrexampleluatest_access.lua;
echo access;
}
test_access.lua test_access.lua
Java 代码 代码 收藏代码
if ngx.req.get_uri_args[token] ~= 123 then
return ngx.exit(403)
end
即如果访问如 http:192.168.1.2lua_access?token=234 将得到 403 Forbidden 的响应。这样我们可以根据
如 cookie 用户 token 来决定是否有访问权限。
content_by_lua content_by_lua
此指令之前已经用过了,此处就不讲解了。
另外在使用 PCRE 进行正则匹配时需要注意正则的写法,具体规则请参考 http:wiki.nginx.orgHttpLuaModul
e中的Special PCRE Sequences部 分。还有其他的注意事项也请阅读官方文档。
第 2 章 Lua 入门 | 363 3
RedisSSDB+Twemproxy 安装与使用 RedisSSDB+Twemproxy 安装与使用目前对于互联网公司不使用 Redis 的很少,Redis 不仅仅可以作为 key-value 缓存,而且提供了丰富的数据结
果如 set、list、map 等,可以实现很多复杂的功能;但是 Redis 本身主要用作内存缓存,不适合做持久化存
储,因此目前有如 SSDB、ARDB 等,还有如京东的 JIMDB,它们都支持 Redis 协议,可以支持 Redis 客户
端直接访问;而这些持久化存储大多数使用了如LevelDB、RocksDB、LMDB 持久化引擎来实现数据的持久化
存储;京东的 JIMDB 主要分为两个版本:LevelDB 和 LMDB,而我们看到的京东商品详情页就是使用 LMDB
引擎作为存储的,可以实现海量KV存储;当然 SSDB 在京东内部也有些部门在使用;另外调研过得如豆瓣的 be
ansDB 也是很不错的。具体这些持久化引擎之间的区别可以自行查找资料学习。
Twemproxy 是一个 RedisMemcached 代理中间件,可以实现诸如分片逻辑、HashTag、减少连接数等功
能;尤其在有大量应用服务器的场景下 Twemproxy 的角色就凸显了,能有效减少连接数。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 38Redis 安装与使用 Redis 安装与使用
下载 redis 并安装 下载 redis 并安装
Java 代码 代码
cd usrservers
wget https:github.comantirezredisarchive2.8.19.tar.gz
tar -xvf 2.8.19.tar.gz
cd redis-2.8.19
make
通过如上步骤构建完毕。
后台启动 Redis 服务器 后台启动 Redis 服务器
Java 代码 代码
nohup usrserversredis-2.8.19srcredis-server usrserversredis-2.8.19redis.conf
查看是否启动成功 查看是否启动成功
Java 代码 代码
ps -aux | grep redis
进入客户端 进入客户端
Java 代码 代码
usrserversredis-2.8.19srcredis-cli -p 6379
执行如下命令 执行如下命令
Java 代码 代码
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 39127.0.0.1:6379> set i 1
OK
127.0.0.1:6379> get i
1
通过如上命令可以看到我们的 Redis 安装成功。更多细节请参考 http:redis.io。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 40SSDB 安装与使用 SSDB 安装与使用
下载 SSDB 并安装 下载 SSDB 并安装
Java 代码 代码
\首先确保安装了g++,如果没有安装,如ubuntu可以使用如下命令安装
apt-get install g++
cd usrservers
wget https:github.comideawussdbarchive1.8.0.tar.gz
tar -xvf 1.8.0.tar.gz
make
后台启动 SSDB 服务器 后台启动 SSDB 服务器
Java 代码 代码
nohup usrserversssdb-1.8.0ssdb-server usrserversssdb-1.8.0ssdb.conf
查看是否启动成功 查看是否启动成功
Java 代码 代码
ps -aux | grep ssdb
进入客户端 进入客户端
Java 代码 代码
usrserversssdb-1.8.0toolsssdb-cli -p 8888
usrserversredis-2.8.19srcredis-cli -p 888
因为 SSDB 支持 Redis 协议,所以用 Redis 客户端也可以访问
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 41执行如下命令 执行如下命令
Java 代码 代码
127.0.0.1:8888> set i 1
OK
127.0.0.1:8888> get i
1
安装过程中遇到错误请参考 http:ssdb.iodocszh_cninstall.html;对于 SSDB 的配置请参考官方文档 http
s:github.comideawussdbhttp:ssdb.iodocszh_cninstall.html。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 42Twemproxy 安装与使用 Twemproxy 安装与使用
首先需要安装 autoconf、automake、libtool 工具,比如 ubuntu 可以使用如下命令安装
Java 代码 代码
apt-get install autoconf automake
apt-get install libtool
下载 Twemproxy 并安装 下载 Twemproxy 并安装
Java 代码 代码
cd usrservers
wget https:github.comtwittertwemproxyarchivev0.4.0.tar.gz
tar -xvf v0.4.0.tar.gz
cd twemproxy-0.4.0
autoreconf -fvi
.configure make
此处根据要注意,如上安装方式在有些服务器上可能在大量如mset时可能导致 Twemproxy 崩溃,需要使用如
CFLAGS=-O1 .configure make 或 CFLAGS=-O3 -fno-strict-aliasing .configure make 安
装。
配置 配置
Java 代码 代码
vim usrserverstwemproxy-0.4.0confnutcracker.yml
Java 代码 代码
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
servers:
- 127.0.0.1:6379:1
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 43启动 Twemproxy 代理 启动 Twemproxy 代理
Java 代码 代码
usrserverstwemproxy-0.4.0srcnutcracker -d -c usrserverstwemproxy-0.4.0confnutcracker.yml
-d 指定后台启动 -c 指定配置文件;此处我们指定了代理端口为 1111,其他配置的含义后续介绍。
查看是否启动成功 查看是否启动成功
Java 代码 代码
ps -aux | grep nutcracker
进入客户端 进入客户端
Java 代码 代码
usrserversredis-2.8.19srcredis-cli -p 1111
执行如下命令 执行如下命令
Java 代码
127.0.0.1:1111> set i 1
OK
127.0.0.1:1111> get i
1
Twemproxy 文档请参考 https:github.comtwittertwemproxy。
到此基本的安装就完成了。接下来做一些介绍。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 44Redis 设置 Redis 设置
基本设置 基本设置
Java 代码 代码
\端口设置,默认6379
port 6379
\日志文件,默认devnull
logfile
Redis 内存 Redis 内存
Java 代码 代码
内存大小对应关系
\ 1k => 1000 bytes
\ 1kb => 1024 bytes
\ 1m => 1000000 bytes
\ 1mb => 10241024 bytes
\ 1g => 1000000000 bytes
\ 1gb => 102410241024 bytes
\设置Redis占用100mb的大小
maxmemory 100mb
\如果内存满了就需要按照如相应算法进行删除过期的最老的
\volatile-lru 根据LRU算法移除设置了过期的key
\allkeys-lru 根据LRU算法移除任何key(包含那些未设置过期时间的key)
\volatile-randomallkeys->random 使用随机算法而不是LRU进行删除
\volatile-ttl 根据Time-To-Live移除即将过期的key
\noeviction 永不过期,而是报错
maxmemory-policy volatile-lru
\Redis并不是真正的LRUTTL,而是基于采样进行移除的,即如采样10个数据移除其中最老的即将过期的
maxmemory-samples 10
而如 Memcached 是真正的 LRU,此处要根据实际情况设置缓存策略,如缓存用户数据时可能带上了过期时
间,此时采用 volatile-lru 即可;而假设我们的数据未设置过期时间,此时可以考虑使用 allkeys-lruallkeys->r
andom;假设我们的数据不允许从内存删除那就使用noeviction。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 45内存大小尽量在系统内存的 60%~80% 之间,因为如客户端、主从时复制时都需要缓存区的,这些也是耗费系统
内存的。
Redis 本身是单线程的,因此我们可以设置每个实例在 6-8GB 之间,通过启动更多的实例提高吞吐量。如 128
GB 的我们可以开启 8GB 10 个实例,充分利用多核 CPU。
Redis 主从 Redis 主从
实际项目时,为了提高吞吐量,我们使用主从策略,即数据写到主 Redis,读的时候从从 Redis上读,这样可以
通过挂载更多的从来提高吞吐量。而且可以通过主从机制,在叶子节点开启持久化方式防止数据丢失。
Java 代码 代码
\在配置文件中挂载主从,不推荐这种方式,我们实际应用时Redis可能是会宕机的
slaveof masterIP masterPort
\从是否只读,默认yes
slave-read-only yes
\当从失去与主的连接或者复制正在进行时,从是响应客户端(可能返回过期的数据)还是返回“SYNC with master in progress”错误,默认yes响应客户端
slave-serve-stale-data yes
\从库按照默认10s的周期向主库发送PING测试连通性
repl-ping-slave-period 10
\设置复制超时时间(SYNC期间批量IO传输、PING的超时时间),确保此值大于repl-ping-slave-period
\repl-timeout 60
\当从断开与主的连接时的复制缓存区,仅当第一个从断开时创建一个,缓存区越大从断开的时间可以持续越长
\ repl-backlog-size 1mb
\当从与主断开持续多久时清空复制缓存区,此时从就需要全量复制了,如果设置为0将永不清空
\ repl-backlog-ttl 3600
\slave客户端缓存区,如果缓存区超过256mb将直接断开与从的连接,如果持续60秒超过64mb也会断开与从的连接
client-output-buffer-limit slave 256mb 64mb 60
此处需要根据实际情况设置client-output-buffer-limit slave和 repl-backlog-size;比如如果网络环境不好,从与主经常断开,而每次设置的数据都特别大而且速度特别快(大量设置html片段)那么就需要加大repl-backlog-size。
主从示例
Java 代码 代码
cd usrserversredis-2.8.19
cp redis.conf redis_6660.conf
cp redis.conf redis_6661.conf
vim redis_6660.conf
vim redis_6661.conf
将端口分别改为 port 6660 和 port 6661,然后启动
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 46Java 代码 代码
nohup usrserversredis-2.8.19srcredis-server usrserversredis-2.8.19redis_6660.conf
nohup usrserversredis-2.8.19srcredis-server usrserversredis-2.8.19redis_6661.conf
查看是否启动
Java 代码 代码
ps -aux | grep redis
进入从客户端,挂主
Java 代码 代码
usrserversredis-2.8.19srcredis-cli -p 6661
Java 代码 代码
127.0.0.1:6661> slaveof 127.0.0.1 6660
OK
127.0.0.1:6661> info replication
\ Replication
role:slave
master_host:127.0.0.1
master_port:6660
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:57
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
进入主
Java 代码 代码
usrserversredis-2.8.19 usrserversredis-2.8.19srcredis-cli -p 6660
Java 代码 代码
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 47127.0.0.1:6660> info replication
\ Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6661,state=online,offset=85,lag=1
master_repl_offset:85
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:84
127.0.0.1:6660> set i 1
OK
进入从
Java 代码
usrserversredis-2.8.19srcredis-cli -p 6661
Java 代码
127.0.0.1:6661> get i
1
此时可以看到主从挂载成功,可以进行主从复制了。使用 slaveof no one 断开主从。
Redis 持久化 Redis 持久化
Redis 虽然不适合做持久化存储,但是为了防止数据丢失有时需要进行持久化存储,此时可以挂载一个从(叶子
节点)只进行持久化存储工作,这样假设其他服务器挂了,我们可以通过这个节点进行数据恢复。
Redis 持久化有 RDB 快照模式和 AOF 追加模式,根据自己需求进行选择。
RDB 持久化 RDB 持久化
Java 代码 代码
\格式save seconds changes 即N秒变更N次则保存,从如下默认配置可以看到丢失数据的周期很长,通过save “” 配置可以完全禁用此持久化
save 900 1
save 300 10
save 60 10000
\RDB是否进行压缩,压缩耗CPU但是可以减少存储大小
rdbcompression yes
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 48\RDB保存的位置,默认当前位置
dir .
\RDB保存的数据库名称
dbfilename dump.rdb
\不使用AOF模式,即RDB模式
appendonly no
可以通过 set 一个数据,然后很快的 kill 掉 redis 进程然后再启动会发现数据丢失了。
AOF 持久化 AOF 持久化
AOF(append only file)即文件追加模式,即把每一个用户操作的命令保存下来,这样就会存在好多重复的命
令导致恢复时间过长,那么可以通过相应的配置定期进行 AOF 重写来减少重复。
Java 代码
\开启AOF
appendonly yes
\AOF保存的位置,默认当前位置
dir .
\AOF保存的数据库名称
appendfilename appendonly.aof
\持久化策略,默认每秒fsync一次,也可以选择always即每次操作都进行持久化,或者no表示不进行持久化而是借助操作系统的同步将缓存区数据写到磁盘
appendfsync everysec
\AOF重写策略(同时满足如下两个策略进行重写)
\当AOF文件大小占到初始文件大小的多少百分比时进行重写
auto-aof-rewrite-percentage 100
\触发重写的最小文件大小
auto-aof-rewrite-min-size 64mb
\为减少磁盘操作,暂缓重写阶段的磁盘同步
no-appendfsync-on-rewrite no
此处的 appendfsync everysec 可以认为是 RDB 和 AOF 的一个折中方案。
当 bgsave 出错时停止写(MISCONF Redis is configured to save RDB snapshots, but is currently not
able to persist on disk.),遇到该错误可以暂时改为 no,当写成功后再改回 yes stop-writes-on-bgsave-
error yes
更多 Redis 持久化请参考 http:redis.readthedocs.orgenlatesttopicpersistence.html。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 49Redis 动态调整配置 Redis 动态调整配置
获取 maxmemory(10mb)
Java 代码 代码
127.0.0.1:6660> config get maxmemory
1) maxmemory
2) 10485760
设置新的 maxmemory(20mb)
Java 代码 代码
127.0.0.1:6660> config set maxmemory 20971520
OK
但是此时重启 redis 后该配置会丢失,可以执行如下命令重写配置文件
Java 代码 代码
127.0.0.1:6660> config rewrite
OK
注意:此时所以配置包括主从配置都会重写。
Redis 执行 Lua 脚本 Redis 执行 Lua 脚本
Redis 客户端支持解析和处理 lua 脚本,因为 Redis 的单线程机制,我们可以借助 Lua 脚本实现一些原子操
作,如扣减库存红包之类的。此处不建议使用 EVAL 直接发送 lua 脚本到客户端,因为其每次都会进行 Lua 脚
本的解析,而是使用 SCRIPT LOAD+ EVALSHA 进行操作。未来不知道是否会用 luajit 来代替 lua,让redis l
ua 脚本性能更强。
到此基本的 Redis 知识就讲完了。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 50Twemproxy 设置 Twemproxy 设置
一旦涉及到一台物理机无法存储的情况就需要考虑使用分片机制将数据存储到多台服务器,可以说是 Redis 集
群;如果客户端都是如 Java 没什么问题,但是如果有多种类型客户端(如 PHP、C)等也要使用那么需要保证
它们的分片逻辑是一样的;另外随着客户端的增加,连接数也会随之增多,发展到一定地步肯定会出现连接数不
够用的;此时 Twemproxy 就可以上场了。主要作用:分片、减少连接数。另外还提供了 Hash Tag 机制来帮助
我们将相似的数据存储到同一个分片。另外也可以参考豌豆荚的 https:github.comwandoulabscodis。
基本配置 基本配置
其使用 YML 语法,如
Java 代码
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
timeout:1000
redis: true
servers:
- 127.0.0.1:6660:1
- 127.0.0.1:6661:1
server1:是给当前分片配置起的名字,一个配置文件可以有多个分片配置;
listen : 监听的 ip 和端口;
hash:散列算法;
distribution:分片算法,比如一致性 Hash 取模;
timeout:连接后端 Redis 或接收响应的超时时间;
redis:是否是 redis 代理,如果是 false 则是 memcached 代理;
servers:代理的服务器列表,该列表会使用 distribution 配置的分片算法进行分片;
分片算法 分片算法
hash 算法:
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 51one_at_a_time
md5
crc16
crc32 (crc32 implementation compatible with libmemcached)
crc32a (correct crc32 implementation as per the spec)
fnv1_64
fnv1a_64
fnv1_32
fnv1a_32
hsieh
murmur
jenkins
分片算法:
ketama(一致性 Hash 算法)
modula(取模)
random(随机算法)
服务器列表 服务器列表
servers:
- ip:port:weight alias
如
servers:
- 127.0.0.1:6660:1
- 127.0.0.1:6661:1
或者
servers:
- 127.0.0.1:6660:1 server1
- 127.0.0.1:6661:1 server2
推荐使用后一种方式,默认情况下使用 ip:port:weight 进行散列并分片,这样假设服务器宕机换上新的服务
器,那么此时得到的散列值就不一样了,因此建议给每个配置起一个别名来保证映射到自己想要的服务器。即如
果不使用一致性 Hash 算法来作缓存服务器,而是作持久化存储服务器时就更有必要了(即不存在服务器下线的
情况,即使服务器 ip:port 不一样但仍然要得到一样的分片结果)。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 52HashTag HashTag
比如一个商品有:商品基本信息(p:id:)、商品介绍(d:id:)、颜色尺码(c:id:)等,假设我们存储时不采用 HashTag
将会导致这些数据不会存储到一个分片,而是分散到多个分片,这样获取时将需要从多个分片获取数据进行合
并,无法进行 mget;那么如果有了 HashTag,那么可以使用“::”中间的数据做分片逻辑,这样 id 一样的将会
分到一个分片。
nutcracker.yml配置如下 nutcracker.yml配置如下
Java 代码 代码
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
hash_tag: ::
servers:
- 127.0.0.1:6660:1 server1
- 127.0.0.1:6661:1 server2
连接 Twemproxy 连接 Twemproxy
Java 代码
usrserversredis-2.8.19srcredis-cli -p 1111
Java 代码 代码
127.0.0.1:1111> set p:12: 1
OK
127.0.0.1:1111> set d:12: 1
OK
127.0.0.1:1111> set c:12: 1
OK
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 53在我的服务器上可以连接 6660 端口 在我的服务器上可以连接 6660 端口
Java 代码 代码
usrserversredis-2.8.19srcredis-cli -p 6660
127.0.0.1:6660> get p:12:
1
127.0.0.1:6660> get d:12:
1
127.0.0.1:6660> get c:12:
1
一致性 Hash 与服务器宕机 一致性 Hash 与服务器宕机
如果我们把 Redis 服务器作为缓存服务器并使用一致性 Hash 进行分片,当有服务器宕机时需要自动从一致性 H
ash 环上摘掉,或者其上线后自动加上,此时就需要如下配置:
是否在节点故障无法响应时自动摘除该节点,如果作为存储需要设置为为false auto_eject_hosts: true 重试
时间(毫秒),重新连接一个临时摘掉的故障节点的间隔,如果判断节点正常会自动加到一致性Hash环上 serve
r_retry_timeout: 30000 节点故障无法响应多少次从一致性Hash环临时摘掉它,默认是2 server_failure_lim
it: 2
支持的 Redis 命令 支持的 Redis 命令
不是所有 Redis 命令都支持,请参考 [https:github.comtwittertwemproxyblobmasternotesredis.mdhr
ef=https:github.comtwittertwemproxyblobmasternotesredis.md)。
因为我们所有的 Twemproxy 配置文件规则都是一样的,因此我们应该将其移到我们项目中。
Java 代码 代码
cp usrserverstwemproxy-0.4.0confnutcracker.yml usrexample
另外 Twemproxy 提供了启动重启停止脚本方便操作,但是需要修改配置文件位置为 usrexamplenutcrack
er.yml。
Java 代码 代码
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 54chmod +x usrserverstwemproxy-0.4.0scriptsnutcracker.init
vim usrserverstwemproxy-0.4.0scriptsnutcracker.init
将 OPTIONS 改为
OPTIONS=-d -c usrexamplenutcracker.yml
另外注释掉. etcrc.dinit.dfunctions;将 daemon --user {USER} {prog} OPTIONS 改为 {prog} O
PTIONS;将 killproc 改为 killall。
这样就可以使用如下脚本进行启动、重启、停止了。
usrserverstwemproxy-0.4.0scriptsnutcracker.init {start|stop|status|restart|reload|condrestart}
对于扩容最简单的办法是:
1. 创建新的集群;
2. 双写两个集群;
3. 把数据从老集群迁移到新集群(不存在才设置值,防止覆盖新的值);
4. 复制速度要根据实际情况调整,不能影响老集群的性能;
5. 切换到新集群即可,如果使用Twemproxy代理层的话,可以做到迁移对读的应用透明。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 554 4
Lua 模块开发 Lua 模块开发在实际开发中,不可能把所有代码写到一个大而全的 lua 文件中,需要进行分模块开发;而且模块化是高性能 Lu
a 应用的关键。使用 require 第一次导入模块后,所有 Nginx 进程全局共享模块的数据和代码,每个 Worker 进
程需要时会得到此模块的一个副本(Copy-On-Write),即模块可以认为是每 Worker 进程共享而不是每 Ngin
x Server 共享;另外注意之前我们使用 init_by_lua 中初始化的全局变量是每请求复制一个;如果想在多个 Wor
ker 进程间共享数据可以使用 ngx.shared.DICT 或如 Redis 之类的存储。
在 usrexamplelualib 中已经提供了大量第三方开发库如 cjson、redis 客户端、mysql客户端:
cjson.so
resty
aes.lua
core.lua
dns
lock.lua
lrucache
lrucache.lua
md5.lua
memcached.lua
mysql.lua
random.lua
redis.lua……
需要注意在使用前需要将库在 nginx.conf 中导入:
Java 代码 代码
\lua模块路径,其中”;;”表示默认搜索路径,默认到usrserversnginx下找
lua_package_path usrexamplelualib?.lua;;; lua 模块
lua_package_cpath usrexamplelualib?.so;;; c模块
使用方式是在lua中通过如下方式引入
Java 代码 代码
local cjson = require(“cjson”)
local redis = require(“resty.redis”)
接下来我们来开发一个简单的 lua 模块。
Java 代码 代码
vim usrexamplelualibmodule1.lua
Java 代码 代码
第 4 章 Lua 模块开发 | 57local count = 0
local function hello
count = count + 1
ngx.say(count : , count)
end
local _M = {
hello = hello
}
return _M
开发时将所有数据做成局部变量局部函数;通过 _M 导出要暴露的函数,实现模块化封装。
接下来创建 test_module_1.lua
Java 代码 代码
vim usrexampleluatest_module_1.lua
Java 代码 代码
local module1 = require(module1)
module1.hello
使用 local var = require (模块名),该模块会到 lua_package_path 和lua_package_cpath 声明的的位置查
找我们的模块,对于多级目录的使用 require (目录1.目录2.模块名)加载。
example.conf 配置
Java 代码 代码
location lua_module_1 {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_module_1.lua;
}
访问如 http:192.168.1.2lua_module_1 进行测试,会得到类似如下的数据,count 会递增
count : 1
count :2……
count :N
第 4 章 Lua 模块开发 | 58此时可能发现 count 一直递增,假设我们的 worker_processes 2,我们可以通过 kill -9 nginx worker proce
ss 杀死其中一个 Worker 进程得到 count 数据变化。
假设我们创建了 vimusrexamplelualibtestmodule2.lua 模块,可以通过 local module2 = require(test.m
odule2) 加载模块
基本的模块开发就完成了,如果是只读数据可以通过模块中声明 local 变量存储;如果想在每 Worker 进程共
享,请考虑竞争;如果要在多个 Worker 进程间共享请考虑使用 ngx.shared.DICT 或如 Redis 存储。
第 4 章 Lua 模块开发 | 595 5
常用 Lua 开发库 1-redis、mysql、http 客户端 常用 Lua 开发库 1-redis、mysql、http 客户端对于开发来说需要有好的生态开发库来辅助我们快速开发,而 Lua 中也有大多数我们需要的第三方开发库如 Red
is、Memcached、Mysql、Http 客户端、JSON、模板引擎等。 一些常见的 Lua 库可以在 github 上搜索,htt
ps:github.comsearch?utf8=%E2%9C%93q=lua+resty。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 61Redis 客户端 Redis 客户端
lua-resty-redis 是为基于 cosocket API 的 ngx_lua 提供的 Lua redis 客户端,通过它可以完成 Redis 的操
作。默认安装 OpenResty 时已经自带了该模块,使用文档可参考https:github.comopenrestylua-resty-r
edis。
在测试之前请启动 Redis 实例:
nohup usrserversredis-2.8.19srcredis-serverusrserversredis-2.8.19redis_6660.conf
基本操作 基本操作
编辑 test_redis_baisc.lua
Java 代码 代码
local function close_redis(red)
if not red then
return
end
local ok, err = red:close
if not ok then
ngx.say(close redis error : , err)
end
end
local redis = require(resty.redis)--创建实例
local red = redis:new--设置超时(毫秒)
red:set_timeout(1000)--建立连接
local ip = 127.0.0.1
local port = 6660
local ok, err = red:connect(ip, port)
if not ok then
ngx.say(connect to redis error : , err)
return close_redis(red)
end--调用API进行处理
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 62ok, err = red:set(msg, hello world)
if not ok then
ngx.say(set msg error : , err)
return close_redis(red)
end--调用API获取数据
local resp, err = red:get(msg)
if not resp then
ngx.say(get msg error : , err)
return close_reedis(red)
end--得到的数据为空处理
if resp == ngx.null then
resp = '' --比如默认值
end
ngx.say(msg : , resp)
close_redis(red)
基本逻辑很简单,要注意此处判断是否为 nil,需要跟 ngx.null 比较。
example.conf 配置文件
Java 代码 代码
location lua_redis_basic {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_redis_basic.lua;
}
访问如 http:192.168.1.2lua_redis_basic 进行测试,正常情况得到如下信息 msg : hello world
连接池 连接池
建立 TCP 连接需要三次握手而释放 TCP 连接需要四次握手,而这些往返时延仅需要一次,以后应该复用 TCP
连接,此时就可以考虑使用连接池,即连接池可以复用连接。
我们只需要将之前的 close_redis 函数改造为如下即可:
Java 代码 代码
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 63local function close_redis(red)
if not red then
return
end--释放连接(连接池实现)
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say(set keepalive error : , err)
end
end
即设置空闲连接超时时间防止连接一直占用不释放;设置连接池大小来复用连接。
此处假设调用 red:set_keepalive,连接池大小通过 nginx.conf 中 http 部分的如下指令定义:
默认连接池大小,默认 30 lua_socket_pool_size 30; 默认超时时间,默认 60s lua_socket_keepalive_tim
eout 60s;
注意:
1. 连接池是每 Worker 进程的,而不是每 Server 的;
2. 当连接超过最大连接池大小时,会按照 LRU 算法回收空闲连接为新连接使用;
3. 连接池中的空闲连接出现异常时会自动被移除;
4. 连接池是通过 ip 和 port 标识的,即相同的 ip 和 port 会使用同一个连接池(即使是不同类型的客户端如 Re
dis、Memcached);
5. 连接池第一次 set_keepalive 时连接池大小就确定下了,不会再变更;
6. cosocket 的连接池 http:wiki.nginx.orgHttpLuaModuletcpsock:setkeepalive。
pipeline pipeline
pipeline 即管道,可以理解为把多个命令打包然后一起发送;MTU(Maxitum Transmission Unit 最大传输单
元)为二层包大小,一般为 1500 字节;而 MSS(Maximum Segment Size 最大报文分段大小)为四层包大
小,其一般是 1500-20(IP 报头)-20(TCP 报头)=1460 字节;因此假设我们执行的多个 Redis 命令能在
一个报文中传输的话,可以减少网络往返来提高速度。因此可以根据实际情况来选择走 pipeline 模式将多个命令
打包到一个报文发送然后接受响应,而 Redis 协议也能很简单的识别和解决粘包。
修改之前的代码片段
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 64Java 代码 代码
red:init_pipeline
red:set(msg1, hello1)
red:set(msg2, hello2)
red:get(msg1)
red:get(msg2)
local respTable, err = red:commit_pipeline--得到的数据为空处理
if respTable == ngx.null then
respTable = {} --比如默认值
end--结果是按照执行顺序返回的一个table
for i, v in ipairs(respTable) do
ngx.say(msg : , v,
)
end
通过 init_pipeline 初始化,然后通过 commit_pipieline 打包提交 init_pipeline 之后的Redis命令;返回结
果是一个 lua table,可以通过 ipairs 循环获取结果;
配置相应 location,测试得到的结果
msg : OK
msg : OK
msg : hello1
msg : hello2
Redis Lua 脚本
利用 Redis 单线程特性,可以通过在 Redis 中执行 Lua 脚本实现一些原子操作。如之前的 red:get(msg) 可
以通过如下两种方式实现:
1、直接 eval:
Java 代码 代码
local resp, err = red:eval(return redis.call('get', KEYS[1]), 1, msg);
2、script load 然后 evalsha SHA1 校验和,这样可以节省脚本本身的服务器带宽: Java 代码 代码
local sha1, err = red:script(load, return redis.call('get', KEYS[1]));
if not sha1 then
ngx.say(load script error : , err)
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 65return close_redis(red)
end
ngx.say(sha1 : , sha1,
)
local resp, err = red:evalsha(sha1, 1, msg);
首先通过 script load 导入脚本并得到一个 sha1 校验和(仅需第一次导入即可),然后通过evalsha 执行 sha1
校验和即可,这样如果脚本很长通过这种方式可以减少带宽的消耗。
此处仅介绍了最简单的 redis lua 脚本,更复杂的请参考官方文档学习使用。
另外 Redis 集群分片算法该客户端没有提供需要自己实现,当然可以考虑直接使用类似于Twemproxy 这种中间
件实现。
Memcached 客户端使用方式和本文类似,本文就不介绍了。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 66Mysql 客户端 Mysql 客户端
lua-resty-mysql 是为基于 cosocket API 的 ngx_lua 提供的 Lua Mysql 客户端,通过它可以完成 Mysql 的
操作。默认安装 OpenResty 时已经自带了该模块,使用文档可参考https:github.comopenrestylua-rest
y-mysql。
编辑 test_mysql.lua 编辑 test_mysql.lua
Java 代码
local function close_db(db)
if not db then
return
end
db:close
end
local mysql = require(resty.mysql)--创建实例
local db, err = mysql:new
if not db then
ngx.say(new mysql error : , err)
return
end--设置超时时间(毫秒)
db:set_timeout(1000)
local props = {
host = 127.0.0.1,port = 3306,database = mysql,user = root,password = 123456
}
local res, err, errno, sqlstate = db:connect(props)
if not res then
ngx.say(connect to mysql error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 67--删除表
local drop_table_sql = drop table if exists test
res, err, errno, sqlstate = db:query(drop_table_sql)
if not res then
ngx.say(drop table error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end--创建表
local create_table_sql = create table test(id int primary key auto_increment, ch varchar(100))
res, err, errno, sqlstate = db:query(create_table_sql)
if not res then
ngx.say(create table error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end--插入
local insert_sql = insert into test (ch) values('hello')
res, err, errno, sqlstate = db:query(insert_sql)
if not res then
ngx.say(insert error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
res, err, errno, sqlstate = db:query(insert_sql)
ngx.say(insert rows : , res.affected_rows, , id : , res.insert_id,
)--更新
local update_sql = update test set ch = 'hello2' where id = .. res.insert_id
res, err, errno, sqlstate = db:query(update_sql)
if not res then
ngx.say(update error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
ngx.say(update rows : , res.affected_rows,
)--查询
local select_sql = select id, ch from test
res, err, errno, sqlstate = db:query(select_sql)
if not res then
ngx.say(select error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 68for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say(select row , i, : , name, = , value,
)
end
end
ngx.say(
)--防止sql注入
local ch_param = ngx.req.get_uri_args[ch] or ''--使用ngx.quote_sql_str防止sql注入
local query_sql = select id, ch from test where ch = .. ngx.quote_sql_str(ch_param)
res, err, errno, sqlstate = db:query(query_sql)
if not res then
ngx.say(select error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say(select row , i, : , name, = , value,
)
end
end--删除
local delete_sql = delete from test
res, err, errno, sqlstate = db:query(delete_sql)
if not res then
ngx.say(delete error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
ngx.say(delete rows : , res.affected_rows,
)
close_db(db)
对于新增修改删除会返回如下格式的响应:
Java 代码 代码
{
insert_id = 0,server_status = 2,warning_count = 1,第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 69affected_rows = 32,message = nil
}
affected_rows 表示操作影响的行数,insert_id 是在使用自增序列时产生的 id。
对于查询会返回如下格式的响应:
Java 代码 代码
{
{ id= 1, ch= hello},{ id= 2, ch= hello2}
}
null 将返回 ngx.null。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码
location lua_mysql {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_mysql.lua;
}
访问如 访问如 http:192.168.1.2lua_mysql?ch=hello http:192.168.1.2lua_mysql?ch=hello 进行测试,得到如下结果 进行测试,得到如下结果
Java 代码 代码
insert rows : 1 , id : 2
update rows : 1
select row 1 : ch = hello
select row 1 : id = 1
select row 2 : ch = hello2
select row 2 : id = 2
select row 1 : ch = hello
select row 1 : id = 1
delete rows : 2
客户端目前还没有提供预编译 SQL 支持(即占位符替换位置变量),这样在入参时记得使用 ngx.quote_sql_st
r 进行字符串转义,防止 sql 注入;连接池和之前 Redis 客户端完全一样就不介绍了。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 70对于 Mysql 客户端的介绍基本够用了,更多请参考 https:github.comopenrestylua-resty-mysql。
其他如 MongoDB 等数据库的客户端可以从 github 上查找使用。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 71Http 客户端 Http 客户端
OpenResty 默认没有提供 Http 客户端,需要使用第三方提供;当然我们可以通过 ngx.location.capture 去方
式实现,但是有一些限制,后边我们再做介绍。
我们可以从 github 上搜索相应的客户端,比如 https:github.compintsizedlua-resty-http。
lua-resty-http lua-resty-http
下载 lua-resty-http 客户端到 lualib 下载 lua-resty-http 客户端到 lualib
Java 代码 代码
cd usrexamplelualibresty
wget https:raw.githubusercontent.compintsizedlua-resty-httpmasterlibrestyhttp_headers.lua
wget https:raw.githubusercontent.compintsizedlua-resty-httpmasterlibrestyhttp.lua
test_http_1.lua test_http_1.lua
Java 代码 代码
local http = require(resty.http)--创建http客户端实例
local httpc = http.new
local resp, err = httpc:request_uri(http:s.taobao.com, {
method = GET,path = search?q=hello,headers = {
[User-Agent] = Mozilla5.0 (Windows NT 6.1; WOW64) AppleWebKit537.36 (KHTML, like Gecko) Chrome40.0.2214.111 Safari537.36
}
})
if not resp then
ngx.say(request error :, err)
return
end--获取状态码
ngx.status = resp.status
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 72--获取响应头
for k, v in pairs(resp.headers) do
if k ~= Transfer-Encoding and k ~= Connection then
ngx.header[k] = v
end
end--响应体
ngx.say(resp.body)
httpc:close
响应头中的 Transfer-Encoding 和 Connection 可以忽略,因为这个数据是当前 server 输出的。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码
location lua_http_1 {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_http_1.lua;
}
在 nginx.conf 中的 http 部分添加如下指令来做 DNS 解析 在 nginx.conf 中的 http 部分添加如下指令来做 DNS 解析
Java 代码 代码
resolver 8.8.8.8;
记得要配置 DNS 解析器 resolver 8.8.8.8,否则域名是无法解析的。
访问如 访问如 http:192.168.1.2lua_http_1 http:192.168.1.2lua_http_1 会看到淘宝的搜索界面。 会看到淘宝的搜索界面。
使用方式比较简单,如超时和连接池设置和之前 Redis 客户端一样,不再阐述。更多客户端使用规则请参考 http
s:github.compintsizedlua-resty-http。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 73ngx.location.capture ngx.location.capture
ngx.location.capture 也可以用来完成 http 请求,但是它只能请求到相对于当前 nginx 服务器的路径,不能使
用之前的绝对路径进行访问,但是我们可以配合 nginx upstream 实现我们想要的功能。
在 nginx.cong中 的 http 部分添加如下 upstream 配置 在 nginx.cong中 的 http 部分添加如下 upstream 配置
Java 代码 代码
upstream backend {
server s.taobao.com;
keepalive 100;
}
即我们将请求 upstream 到 backend;另外记得一定要添加之前的 DNS 解析器。
在 example.conf 配置如下 location 在 example.conf 配置如下 location
Java 代码 代码
location ~ proxy(.) {
internal;
proxy_pass http:backend1is_argsargs;
}
internal 表示只能内部访问,即外部无法通过 url 访问进来; 并通过 proxy_pass 将请求转发到 upstream。
test_http_2.lua test_http_2.lua
Java 代码 代码
local resp = ngx.location.capture(proxysearch, {
method = ngx.HTTP_GET,args = {q = hello}
})
if not resp then
ngx.say(request error :, err)
return
end
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 74ngx.log(ngx.ERR, tostring(resp.status))--获取状态码
ngx.status = resp.status--获取响应头
for k, v in pairs(resp.header) do
if k ~= Transfer-Encoding and k ~= Connection then
ngx.header[k] = v
end
end--响应体
if resp.body then
ngx.say(resp.body)
end
通过 ngx.location.capture 发送一个子请求,此处因为是子请求,所有请求头继承自当前请求,还有如 ngx.ctx
和ngx.var 是否继承可以参考官方文档 http:wiki.nginx.orgHttpLuaModulengx.location.capture。 另外还
提供了 ngx.location.capture_multi用于并发发出多个请求,这样总的响应时间是最慢的一个,批量调用时有
用。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码
location lua_http_2 {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_http_2.lua;
}
访问如 访问如 http:192.168.1.2lua_http_2 http:192.168.1.2lua_http_2 进行测试可以看到淘宝搜索界面。 进行测试可以看到淘宝搜索界面。
我们通过 upstream+ngx.location.capture 方式虽然麻烦点,但是得到更好的性能和upstream 的连接池、负
载均衡、故障转移、proxy cache 等特性。
不过因为继承在当前请求的请求头,所以可能会存在一些问题,比较常见的就是 gzip 压缩问题,ngx.location.c
apture 不会解压缩后端服务器的 GZIP 内容,解决办法可以参考https:github.comopenrestylua-nginx-m
oduleissues12;因为我们大部分这种http 调用的都是内部服务,因此完全可以在 proxy location 中添加prox
y_pass_request_headers off; 来不传递请求头。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 756 6
JSON 库、编码转换、字符串处理 JSON 库、编码转换、字符串处理JSON 库 JSON 库
在进行数据传输时 JSON 格式目前应用广泛,因此从 Lua 对象与 JSON 字符串之间相互转换是一个非常常见的
功能;目前 Lua 也有几个 JSON 库,本人用过 cjson、dkjson。其中 cjson的语法严格(比如 unicode \u002
0\u7eaf ),要求符合规范否则会解析失败(如 \u002),而 dkjson 相对宽松,当然也可以通过修改 cjson 的
源码来完成一些特殊要求。而在使用dkjson 时也没有遇到性能问题,目前使用的就是 dkjson。使用时要特别注
意的是大部分 JSON库都仅支持 UTF-8 编码;因此如果你的字符编码是如 GBK 则需要先转换为 UTF-8 然后
进行处理。
test_cjson.lua test_cjson.lua
Java 代码 代码
local cjson = require(cjson)--lua对象到字符串
local obj = {
id = 1,name = zhangsan,age = nil,is_male = false,hobby = {film, music, read}
}
local str = cjson.encode(obj)
ngx.say(str,
)--字符串到lua对象
str = '{hobby:[film,music,read],is_male:false,name:zhangsan,id:1,age:null}'
local obj = cjson.decode(str)
ngx.say(obj.age,
)
ngx.say(obj.age == nil,
)
ngx.say(obj.age == cjson.null,
)
ngx.say(obj.hobby[1],
)--循环引用
obj = {
id = 1
第 6 章 JSON 库、编码转换、字符串处理 | 77}
obj.obj = obj-- Cannot serialise, excessive nesting--ngx.say(cjson.encode(obj),
)
local cjson_safe = require(cjson.safe)--nil
ngx.say(cjson_safe.encode(obj),
)
null 将会转换为 cjson.null;循环引用会抛出异常 Cannot serialise, excessive nesting,默认解析嵌套深度是
1000,可以通过 cjson.encode_max_depth 设置深度提高性能;使用 cjson.safe 不会抛出异常而是返回 ni
l。
example.conf 配置文件 example.conf 配置文件
Java 代码
location ~ lua_cjson {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexamplel ......
天下武功,为快不破。Nginx 的看家本领就是速度,Lua 的拿手好戏亦是速度,这两者的结合在速度上无疑有基
因上的优势。最先将 Nginx,Lua 组合到一起的是 OpenResty,它有一个 ngx_lua 模块,将 Lua 嵌入到了 N
ginx 里面。本教程从环境搭建到实战讲解,逐步向读者展示如何使用 Nginx+Lua 框架进行开发。
适用人群 适用人群
开发高速高并发网站的程序员。
学习前提 学习前提
学习本教程前,你需要了解 Java、Lua 等编程语言。
鸣谢:http:jinnianshilongnian.iteye.comcategory333854目录 目录
前言 前言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1
第 1 章 第 1 章 安装 Nginx+Lua 开发环境 安装 Nginx+Lua 开发环境 . . . . . . . . . . . . . . . . 5 5
安装环境 . . . . . . . . . . . . . . 7
配置环境 . . . . . . . . . . . . . . 10
HelloWorld. . . . . . . . . . . . . . 12
nginx+lua 项目构建 . . . . . . . . . . . . 15
第 2 章 第 2 章 Nginx+Lua 开发入门 Nginx+Lua 开发入门 . . . . . . . . . . . . . . . . . . . . 17 17
Nginx 入门 . . . . . . . . . . . . . . 18
第 2 章 第 2 章 Lua 入门 Lua 入门. . . . . . . . . . . . . . . . . . . . . . 19 19
Nginx Lua API . . . . . . . . . . . . . 21
Nginx Lua 模块指令. . . . . . . . . . . . 27
第 3 章 第 3 章 RedisSSDB+Twemproxy 安装与使用 RedisSSDB+Twemproxy 安装与使用. . . . . . . . . . . . . . 37 37
Redis 安装与使用. . . . . . . . . . . . 39
SSDB 安装与使用. . . . . . . . . . . . 41
Twemproxy 安装与使用. . . . . . . . . . . 43
Redis 设置 . . . . . . . . . . . . . 45
Twemproxy 设置. . . . . . . . . . . . 51
第 4 章 第 4 章 Lua 模块开发 Lua 模块开发 . . . . . . . . . . . . . . . . . . . . 56 56
第 5 章 第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 常用 Lua 开发库 1-redis、mysql、http 客户端. . . . . . . . . . . . 60 60
Redis 客户端 . . . . . . . . . . . . . 62
Mysql 客户端 . . . . . . . . . . . . . 67
Http 客户端. . . . . . . . . . . . . . 72
第 6 章 第 6 章 JSON 库、编码转换、字符串处理 JSON 库、编码转换、字符串处理 . . . . . . . . . . . . . . 76 76JSON 库. . . . . . . . . . . . . . 77
第 7 章 第 7 章 常用 Lua 开发库 3-模板渲染 常用 Lua 开发库 3-模板渲染 . . . . . . . . . . . . . . . . 90 90
模板位置 . . . . . . . . . . . . . . 92
使用示例 . . . . . . . . . . . . . . 96
第 8 章 第 8 章 HTTP 服务 HTTP 服务. . . . . . . . . . . . . . . . . . . . . . 99 99
架构 . . . . . . . . . . . . . . 101
实现 . . . . . . . . . . . . . . 105
后台逻辑 . . . . . . . . . . . . . . 106
前台逻辑 . . . . . . . . . . . . . . 107
项目搭建 . . . . . . . . . . . . . . 108
Redis+Twemproxy 配置. . . . . . . . . . . 109
Mysql+Atlas 配置 . . . . . . . . . . . . 111
Java+Tomcat 安装 . . . . . . . . . . . . 115
Java+Tomcat 逻辑开发. . . . . . . . . . . 117
Nginx+Lua 逻辑开发 . . . . . . . . . . . . 123
第 9 章 第 9 章 Web 开发实战2——商品详情页 Web 开发实战2——商品详情页 . . . . . . . . . . . . . . . . 127 127
技术选型 . . . . . . . . . . . . . . 130
核心流程 . . . . . . . . . . . . . . 131
项目搭建 . . . . . . . . . . . . . . 108
数据存储实现 . . . . . . . . . . . . . 133
动态服务实现 . . . . . . . . . . . . . 146
前端展示实现 . . . . . . . . . . . . . 155
商品介绍 . . . . . . . . . . . . . . 157
前端展示 . . . . . . . . . . . . . . 159
第 10 章 第 10 章 流量复制 AB 测试协程 流量复制 AB 测试协程 . . . . . . . . . . . . . . . . 173 173
流量复制 . . . . . . . . . . . . . . 174
AB 测试 . . . . . . . . . . . . . . 176协程 . . . . . . . . . . . . . . 1791 1
安装 Nginx+Lua 开发环境 安装 Nginx+Lua 开发环境首先我们选择使用 OpenResty,其是由 Nginx 核心加很多第三方模块组成,其最大的亮点是默认集成了 Lua 开
发环境,使得 Nginx 可以作为一个 Web Server 使用。借助于 Nginx 的事件驱动模型和非阻塞 IO,可以实现高
性能的 Web 应用程序。而且 OpenResty 提供了大量组件如 Mysql、Redis、Memcached 等等,使在 Nginx
上开发Web 应用更方便更简单。目前在京东如实时价格、秒杀、动态服务、单品页、列表页等都在使用Nginx+L
ua 架构,其他公司如淘宝、去哪儿网等。
第 1 章 安装 Nginx+Lua 开发环境 | 6安装环境 安装环境
安装步骤可以参考 http:openresty.orgInstallation。
创建目录 usrservers,以后我们把所有软件安装在此目录 创建目录 usrservers,以后我们把所有软件安装在此目录
Java 代码 代码 收藏代码
mkdir -p usrservers
cd usrservers
安装依赖(我的环境是 ubuntu,可以使用如下命令安装,其他的可以参考 openresty 安装 安装依赖(我的环境是 ubuntu,可以使用如下命令安装,其他的可以参考 openresty 安装 步骤) 步骤)
Java 代码 代码 收藏代码
apt-get install libreadline-dev libncurses5-dev libpcre3-dev libssl-dev perl
下载 ngx_openresty-1.7.7.2.tar.gz 并解压 下载 ngx_openresty-1.7.7.2.tar.gz 并解压
Java 代码 代码 收藏代码
wget http:openresty.orgdownloadngx_openresty-1.7.7.2.tar.gz
tar -xzvf ngx_openresty-1.7.7.2.tar.gz
ngx_openresty-1.7.7.2bundle 目录里存放着 nginx 核心和很多第三方模块,比如有我们需要的 Lua 和 LuaJ
IT。
安装 LuaJIT 安装 LuaJIT
Java 代码 代码 收藏代码
cd bundleLuaJIT-2.1-20150120
make clean make make install
ln -sf luajit-2.1.0-alpha usrlocalbinluajit
第 1 章 安装 Nginx+Lua 开发环境 | 7下载 ngx_cache_purge 模块,该模块用于清理 nginx 缓存 下载 ngx_cache_purge 模块,该模块用于清理 nginx 缓存
Java 代码 代码 收藏代码
cd usrserversngx_openresty-1.7.7.2bundle
wget https:github.comFRiCKLEngx_cache_purgearchive2.3.tar.gz
tar -xvf 2.3.tar.gz
下载 nginx_upstream_check_module 模块,该模块用于 ustream 健康检查 下载 nginx_upstream_check_module 模块,该模块用于 ustream 健康检查
Java 代码 代码 收藏代码
cd usrserversngx_openresty-1.7.7.2bundle
wget https:github.comyaoweibinnginx_upstream_check_modulearchivev0.3.0.tar.gz
tar -xvf v0.3.0.tar.gz
安装 ngx_openresty 安装 ngx_openresty
Java 代码 代码 收藏代码
cd usrserversngx_openresty-1.7.7.2
.configure --prefix=usrservers --with-http_realip_module --with-pcre --with-luajit --add-module=.bundlengx_cache_purge-2.3 --add-module=.bundlenginx_upstream_check_module-0.3.0 -j2
make make install--with 安装一些内置集成的模块--with-http_realip_module 取用户真实 ip 模块
-with-pcre Perl 兼容的达式模块--with-luajit 集成 luajit 模块--add-module 添加自定义的第三方模块,如此次的 ngx_che_purge
到 usrservers 目录下 到 usrservers 目录下
Java 代码 代码 收藏代码
cd usrservers
ll
第 1 章 安装 Nginx+Lua 开发环境 | 8会发现多出来了如下目录,说明安装成功
usrserversluajit usrserversluajit :luajit 环境,luajit 类似于 java 的 jit,即即时编译,lua 是一种解释语言,通过 luajit 可以
即时编译 lua 代码到机器代码,得到很好的性能;
usrserverslualib usrserverslualib:要使用的 lua 库,里边提供了一些默认的 lua 库,如 redis,json 库等,也可以把一些自
己开发的或第三方的放在这;
usrserversnginx :安装的 nginx;
通过 usrserversnginxsbinnginx -V 查看 nginx 版本和安装的模块
启动 nginx 启动 nginx
usrserversnginxsbinnginx
接下来该配置 nginx+lua 开发环境了
第 1 章 安装 Nginx+Lua 开发环境 | 9配置环境 配置环境
配置及 Nginx HttpLuaModule 文档在可以查看 http:openresty.orgInstallation。
编辑 nginx.conf 配置文件 编辑 nginx.conf 配置文件
Java 代码 代码 收藏代码
vim usrserversnginxconfnginx.conf
在 http 部分添加如下配置 在 http 部分添加如下配置
Java 代码 代码 收藏代码
\lua模块路径,多个之间”;”分隔,其中”;;”表示默认搜索路径,默认到usrserversnginx下找
lua_package_path usrserverslualib?.lua;;; lua 模块
lua_package_cpath usrserverslualib?.so;;; c模块
为了方便开发我们在 usrserversnginxconf 目录下创建一个 lua.conf 为了方便开发我们在 usrserversnginxconf 目录下创建一个 lua.conf
Java 代码 代码 收藏代码
\lua.conf
server {
listen 80;
server_name _;
}
在 nginx.conf 中的 http 部分添加 include lua.conf 包含此文件片段 在 nginx.conf 中的 http 部分添加 include lua.conf 包含此文件片段
Java 代码 代码 收藏代码
include lua.conf;
第 1 章 安装 Nginx+Lua 开发环境 | 10测试是否正常 测试是否正常
Java 代码 代码 收藏代码
usrserversnginxsbinnginx -t
如果显示如下内容说明配置成功
nginx: the configuration file usrserversnginxconfnginx.conf syntax is ok
nginx: configuration file usrserversnginxconfnginx.conf test is successful
第 1 章 安装 Nginx+Lua 开发环境 | 11HelloWorld HelloWorld
在 lua.conf 中 server 部分添加如下配置 在 lua.conf 中 server 部分添加如下配置
Java 代码 代码 收藏代码
location lua {
default_type 'texthtml';
content_by_lua 'ngx.say(hello world)';
}
测试配置是否正确 测试配置是否正确
Java 代码 代码 收藏代码
usrserversnginxsbinnginx -t
重启 nginx 重启 nginx
Java 代码 代码 收藏代码
usrserversnginxsbinnginx -s reload
访问如 访问如 http:192.168.1.6lua http:192.168.1.6lua (自己的机器根据实际情况换 ip),可以看到如下内容 (自己的机器根据实际情况换 ip),可以看到如下内容
hello world
lua 代码文件 lua 代码文件
我们把 lua 代码放在 nginx 配置中会随着 lua 的代码的增加导致配置文件太长不好维护,因此我们应该把 lua 代
码移到外部文件中存储。
Java 代码 代码 收藏代码
vim usrserversnginxconfluatest.lua
第 1 章 安装 Nginx+Lua 开发环境 | 12Java 代码 代码 收藏代码
\添加如下内容
ngx.say(hello world);
然后 lua.conf 修改为
Java 代码 代码 收藏代码
location lua {
default_type 'texthtml';
content_by_lua_file confluatest.lua; 相对于nginx安装目录
}
此处 confluatest.lua 也可以使用绝对路径 usrserversnginxconfluatest.lua。
lua_code_cache lua_code_cache
默认情况下 lua_code_cache 是开启的,即缓存 lua 代码,即每次 lua 代码变更必须reload nginx 才生效,如
果在开发阶段可以通过 lua_code_cache off;关闭缓存,这样调试时每次修改 lua 代码不需要 reload nginx;但
是正式环境一定记得开启缓存。
Java 代码 代码 收藏代码
location lua {
default_type 'texthtml';
lua_code_cache off;
content_by_lua_file confluatest.lua;
}
开启后 reload nginx 会看到如下报警
nginx: [alert] lua_code_cache is off; this will hurt performance in usrserversnginxconflua.conf:8
错误日志 错误日志
如果运行过程中出现错误,请不要忘记查看错误日志。
Java 代码 代码 收藏代码
tail -f usrserversnginxlogserror.log
第 1 章 安装 Nginx+Lua 开发环境 | 13到此我们的基本环境搭建完毕。
第 1 章 安装 Nginx+Lua 开发环境 | 14nginx+lua 项目构建 nginx+lua 项目构建
以后我们的 nginx lua 开发文件会越来越多,我们应该把其项目化,已方便开发。项目目录结构如下所示:
example
example.conf ---该项目的nginx 配置文件
lua ---我们自己的lua代码
test.lua
lualib ---lua依赖库第三方依赖
.lua
.so
其中我们把 lualib 也放到项目中的好处就是以后部署的时候可以一起部署,防止有的服务器忘记复制依赖而造成
缺少依赖的情况。
我们将项目放到到 usrexample 目录下。
usrserversnginxconfnginx.conf 配置文件如下(此处我们最小化了配置文件)
Java 代码 代码 收藏代码
\user nobody;
worker_processes 2;
error_log logserror.log;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type texthtml;
lua模块路径,其中”;;”表示默认搜索路径,默认到usrserversnginx下找
lua_package_path usrexamplelualib?.lua;;; lua 模块
lua_package_cpath usrexamplelualib?.so;;; c模块
include usrexampleexample.conf;
}
通过绝对路径包含我们的 lua 依赖库和 nginx 项目配置文件。
usrexampleexample.conf 配置文件如下
Java 代码 代码 收藏代码
第 1 章 安装 Nginx+Lua 开发环境 | 15server {
listen 80;
server_name _;
location lua {
default_type 'texthtml';
lua_code_cache off;
content_by_lua_file usrexampleluatest.lua;
}
}
lua 文件我们使用绝对路径 usrexampleluatest.lua。
到此我们就可以把 example 扔 svn 上了。
第 1 章 安装 Nginx+Lua 开发环境 | 162 2
Nginx+Lua 开发入门 Nginx+Lua 开发入门Nginx 入门 Nginx 入门
本文目的是学习 Nginx+Lua 开发,对于 Nginx 基本知识可以参考如下文章:
nginx 启动、关闭、重启
http:www.cnblogs.comderekchenarchive201102171957209.html
agentzh 的 Nginx 教程
http:openresty.orgdownloadagentzh-nginx-tutorials-zhcn.html
Nginx+Lua 入门
http:17173ops.com2013110117173-ngx-lua-manual.shtml
nginx 配置指令的执行顺序
http:zhongfox.github.ioblogserver20130515nginx-exec-order
nginx 与 lua 的执行顺序和步骤说明
http:www.mrhaoting.com?p=157
Nginx 配置文件 nginx.conf 中文详解
http:www.ha97.com5194.html
Tengine 的 Nginx 开发从入门到精通
http:tengine.taobao.orgbook
官方文档
http:wiki.nginx.orgConfiguration
第 2 章 Nginx+Lua 开发入门 | 18第 2 章 Lua 入门 第 2 章 Lua 入门
第 2 章 Lua 入门 | 19本文目的是学习 Nginx+Lua 开发,对于 Lua 基本知识可以参考如下文章:
Lua 简明教程
http:coolshell.cnarticles10739.html
lua 在线 lua 学习教程
http:book.luaer.cn
Lua 5.1 参考手册
http:www.codingnow.com2000downloadlua_manual.html
Lua 5.3 参考手册
http:cloudwu.github.iolua53doc
第 2 章 Lua 入门 | 20Nginx Lua API Nginx Lua API
和一般的 Web Server 类似,我们需要接收请求、处理并输出响应。而对于请求我们需要获取如请求参数、请求
头、Body 体等信息;而对于处理就是调用相应的 Lua 代码即可;输出响应需要进行响应状态码、响应头和响应
内容体的输出。因此我们从如上几个点出发即可。
接收请求 接收请求
example.conf 配置文件 example.conf 配置文件
Java 代码 收藏代码
location ~ lua_request(\d+)(\d+) {
设置nginx变量
set a 1;
set b host;
default_type texthtml;
nginx内容处理
content_by_lua_file usrexampleluatest_request.lua;
内容体处理完成后调用
echo_after_body ngx.var.b b;
}
test_request.lua test_request.lua
Java 代码 收藏代码--nginx变量
local var = ngx.var
ngx.say(ngx.var.a : , var.a,
)
ngx.say(ngx.var.b : , var.b,
)
ngx.say(ngx.var[2] : , var[2],
)
ngx.var.b = 2;
ngx.say(
)--请求头
local headers = ngx.req.get_headers
ngx.say(headers begin,
)
ngx.say(Host : , headers[Host],
)
第 2 章 Lua 入门 | 21ngx.say(user-agent : , headers[user-agent],
)
ngx.say(user-agent : , headers.user_agent,
)
for k,v in pairs(headers) do
if type(v) == table then
ngx.say(k, : , table.concat(v, ,),
)
else
ngx.say(k, : , v,
)
end
end
ngx.say(headers end,
)
ngx.say(
)--get请求uri参数
ngx.say(uri args begin,
)
local uri_args = ngx.req.get_uri_args
for k, v in pairs(uri_args) do
if type(v) == table then
ngx.say(k, : , table.concat(v, , ),
)
else
ngx.say(k, : , v,
)
end
end
ngx.say(uri args end,
)
ngx.say(
)--post请求参数
ngx.req.read_body
ngx.say(post args begin,
)
local post_args = ngx.req.get_post_args
for k, v in pairs(post_args) do
if type(v) == table then
ngx.say(k, : , table.concat(v, , ),
)
else
ngx.say(k, : , v,
)
end
end
ngx.say(post args end,
)
ngx.say(
)--请求的http协议版本
ngx.say(ngx.req.http_version : , ngx.req.http_version,
)--请求方法
ngx.say(ngx.req.get_method : , ngx.req.get_method,
)--原始的请求头内容
ngx.say(ngx.req.raw_header : , ngx.req.raw_header,
)--请求的body内容体
第 2 章 Lua 入门 | 22ngx.say(ngx.req.get_body_data : , ngx.req.get_body_data,
)
ngx.say(
)
ngx.var ngx.var : nginx 变量,如果要赋值如 ngx.var.b = 2,此变量必须提前声明;另外对于 nginx location 中使用
正则捕获的捕获组可以使用 ngx.var [捕获组数字]获取;
ngx.req.get_headers ngx.req.get_headers:获取请求头,默认只获取前100,如果想要获取所以可以调用ngx.req.get_header
s(0);获取带中划线的请求头时请使用如 headers.user_agent 这种方式;如果一个请求头有多个值,则返回的
是 table;
ngx.req.get_uri_args ngx.req.get_uri_args:获取 url 请求参数,其用法和 get_headers 类似;
ngx.req.get_post_args ngx.req.get_post_args:获取 post 请求内容体,其用法和 get_headers 类似,但是必须提前调用 ngx.req.r
ead_body 来读取 body 体(也可以选择在 nginx 配置文件使用lua_need_request_body on;开启读取 bod
y 体,但是官方不推荐);
ngx.req.raw_header ngx.req.raw_header:未解析的请求头字符串;
ngx.req.get_body_data ngx.req.get_body_data:为解析的请求 body 体内容字符串。
如上方法处理一般的请求基本够用了。另外在读取 post 内容体时根据实际情况设置 client_body_buffer_size
和 client_max_body_size 来保证内容在内存而不是在文件中。
使用如下脚本测试
Java 代码 代码 收藏代码
wget --post-data 'a=1b=2' 'http:127.0.0.1lua_request12?a=3b=4' -O -
输出响应 输出响应
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_response_1 {
default_type texthtml;
content_by_lua_file usrexampleluatest_response_1.lua;
}
第 2 章 Lua 入门 | 23test_response_1.lua test_response_1.lua
Java 代码 代码 收藏代码--写响应头
ngx.header.a = 1--多个响应头可以使用table
ngx.header.b = {2, 3}--输出响应
ngx.say(a, b,
)
ngx.print(c, d,
)--200状态码退出
return ngx.exit(200)
ngx.header:输出响应头;
ngx.print:输出响应内容体;
ngx.say:通ngx.print,但是会最后输出一个换行符;
ngx.exit:指定状态码退出。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_response_2 {
default_type texthtml;
content_by_lua_file usrexampleluatest_response_2.lua;
}
test_response_2.lua test_response_2.lua
Java 代码 代码 收藏代码
ngx.redirect(http:jd.com, 302)
ngx.redirect ngx.redirect:重定向;
ngx.status= 状态码,设置响应的状态码;ngx.resp.get_headers 获取设置的响应状态码;ngx.send_head
ers 发送响应状态码,当调用 ngx.sayngx.print 时自动发送响应状态码;可以通过 ngx.headers_sent=true
判断是否发送了响应状态码。
第 2 章 Lua 入门 | 24其他 API 其他 API
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_other {
default_type texthtml;
content_by_lua_file usrexampleluatest_other.lua;
}
test_other.lua test_other.lua
Java 代码 代码 收藏代码--未经解码的请求uri
local request_uri = ngx.var.request_uri;
ngx.say(request_uri : , request_uri,
);--解码
ngx.say(decode request_uri : , ngx.unescape_uri(request_uri),
);--MD5
ngx.say(ngx.md5 : , ngx.md5(123),
)--http time
ngx.say(ngx.http_time : , ngx.http_time(ngx.time),
)
ngx.escape_uringx.unescape_uri : uri 编码解码;
ngx.encode_argsngx.decode_args:参数编码解码;
ngx.encode_base64ngx.decode_base64:BASE64 编码解码;
ngx.re.match:nginx 正则表达式匹配;
更多 Nginx Lua API 请参考 http:wiki.nginx.orgHttpLuaModuleNginx_API_for_Lua。
Nginx 全局内存 Nginx 全局内存
使用过如 Java 的朋友可能知道如 Ehcache 等这种进程内本地缓存,Nginx 是一个 Master 进程多个 Worker
进程的工作方式,因此我们可能需要在多个 Worker 进程中共享数据,那么此时就可以使用 ngx.shared.DICT
来实现全局内存共享。
第 2 章 Lua 入门 | 25首先在 nginx.conf 的 http 部分分配内存大小 首先在 nginx.conf 的 http 部分分配内存大小
Java 代码 代码 收藏代码
\共享全局变量,在所有worker间共享
lua_shared_dict shared_data 1m;
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_shared_dict {
default_type texthtml;
content_by_lua_file usrexampleluatest_lua_shared_dict.lua;
}
test_lua_shared_dict.lua test_lua_shared_dict.lua
Java 代码 代码 收藏代码--1、获取全局共享内存变量
local shared_data = ngx.shared.shared_data--2、获取字典值
local i = shared_data:get(i)
if not i then
i = 1--3、惰性赋值
shared_data:set(i, i)
ngx.say(lazy set i , i,
)
end--递增
i = shared_data:incr(i, 1)
ngx.say(i=, i,
)
更多 API 请参考 http:wiki.nginx.orgHttpLuaModulengx.shared.DICT。
到此基本的 Nginx Lua API 就学完了,对于请求处理和输出响应如上介绍的 API 完全够用了,更多 API 请参考
官方文档。
第 2 章 Lua 入门 | 26Nginx Lua 模块指令 Nginx Lua 模块指令
Nginx 共11个处理阶段,而相应的处理阶段是可以做插入式处理,即可插拔式架构;另外指令可以在 http、serv
er、server if、location、location if 几个范围进行配置:
指令 所处处理
阶段
使用范围 解释
init_by_lua
init_by_lu
a_file
loading-
config
http nginx Master进程加载配置时执行;
通常用于初始化全局配置预加载Lua模块
init_worke
r_by_lua
init_worke
r_by_lua_f
ile
startin
g-worke
r
http 每个Nginx Worker进程启动时调用的计时
器,如果Master进程不允许则只会在init_by_l
ua之后调用;
通常用于定时拉取配置数据,或者后端服务的
健康检查
set_by_lu
a
set_by_lu
a_file
rewrite server,server i
f,location,locati
on if
设置nginx变量,可以实现复杂的赋值逻辑;此
处是阻塞的,Lua代码要做到非常快;
rewrite_b
y_lua
rewrite_b
y_lua_file
rewrite t
ail
http,server,loc
ation,location i
f
rrewrite阶段处理,可以实现复杂的转发重定
向逻辑;
access_b
y_lua
access t
ail
http,server,loc
ation,location i
f
请求访问阶段处理,用于访问控制
第 2 章 Lua 入门 | 27access_b
y_lua_file
content_b
y_lua
content_b
y_lua_file
content location,locat
ion if
内容处理器,接收请求处理并输出响应
header_filt
er_by_lua
header_filt
er_by_lu
a_file
output-
header-
filter
http,server,l
ocation,locati
on if
设置header和cookie
body_filte
r_by_lua
body_filte
r_by_lua_f
ile
output-
body-filt
er
http,server,l
ocation,locati
on if
对响应数据进行过滤,比如截断、替换。
log_by_lu
a
log_by_lu
a_file
log http,server,l
ocation,locati
on if
log阶段处理,比如记录访问量统计平均响应
时间
更详细的解释请参考 http:wiki.nginx.orgHttpLuaModuleDirectives。如上指令很多并不常用,因此我们只
拿其中的一部分做演示。
第 2 章 Lua 入门 | 28init_by_lua init_by_lua
每次 Nginx 重新加载配置时执行,可以用它来完成一些耗时模块的加载,或者初始化一些全局配置;在 Master
进程创建 Worker 进程时,此指令中加载的全局变量会进行 Copy-OnWrite,即会复制到所有全局变量到 Work
er 进程。
nginx.conf 配置文件中的 http 部分添加如下代码 nginx.conf 配置文件中的 http 部分添加如下代码
Java 代码 代码 收藏代码
\共享全局变量,在所有worker间共享
lua_shared_dict shared_data 1m;
init_by_lua_file usrexampleluainit.lua;
init.lua init.lua
Java 代码 代码 收藏代码--初始化耗时的模块
local redis = require 'resty.redis'
local cjson = require 'cjson'--全局变量,不推荐
count = 1--共享全局内存
local shared_data = ngx.shared.shared_data
shared_data:set(count, 1)
test.lua test.lua
Java 代码 代码 收藏代码
count = count + 1
ngx.say(global variable : , count)
local shared_data = ngx.shared.shared_data
ngx.say(, shared memory : , shared_data:get(count))
shared_data:incr(count, 1)
ngx.say(hello world)
第 2 章 Lua 入门 | 29访问如 http:192.168.1.2lua 会发现全局变量一直不变,而共享内存一直递增 访问如 http:192.168.1.2lua 会发现全局变量一直不变,而共享内存一直递增
global variable : 2 , shared memory : 8 hello world
另外注意一定在生产环境开启 lua_code_cache,否则每个请求都会创建 Lua VM 实例。
init_worker_by_lua init_worker_by_lua
用于启动一些定时任务,比如心跳检查,定时拉取服务器配置等等;此处的任务是跟 Worker 进程数量有关系
的,比如有2个 Worker 进程那么就会启动两个完全一样的定时任务。
nginx.conf 配置文件中的 http 部分添加如下代码 nginx.conf 配置文件中的 http 部分添加如下代码
Java 代码 代码 收藏代码
init_worker_by_lua_file usrexampleluainit_worker.lua;
init_worker.lua init_worker.lua
Java 代码 代码 收藏代码
local count = 0
local delayInSeconds = 3
local heartbeatCheck = nil
heartbeatCheck = function(args)
count = count + 1
ngx.log(ngx.ERR, do check , count)
local ok, err = ngx.timer.at(delayInSeconds, heartbeatCheck)
if not ok then
ngx.log(ngx.ERR, failed to startup heartbeart worker..., err)
end
end
heartbeatCheck
第 2 章 Lua 入门 | 30ngx.timer.at ngx.timer.at:延时调用相应的回调方法;ngx.timer.at(秒单位延时,回调函数,回调函数的参数列表);可以将
延时设置为0即得到一个立即执行的任务,任务不会在当前请求中执行不会阻塞当前请求,而是在一个轻量级线程
中执行。
另外根据实际情况设置如下指令
lua_max_pending_timers 1024; 最大等待任务数
lua_max_running_timers 256; 最大同时运行任务数
set_by_lua set_by_lua
设置 nginx 变量,我们用的 set 指令即使配合 if 指令也很难实现负责的赋值逻辑;
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_set_1 {
default_type texthtml;
set_by_lua_file num usrexampleluatest_set_1.lua;
echo num;
}
set_by_lua_file set_by_lua_file:语法 set_by_lua_file var lua_file arg1 arg2...; 在 lua代码中可以实现所有复杂的逻辑,但
是要执行速度很快,不要阻塞;
test_set_1.lua test_set_1.lua
Java 代码 代码 收藏代码
local uri_args = ngx.req.get_uri_args
local i = uri_args[i] or 0
local j = uri_args[j] or 0
return i + j
得到请求参数进行相加然后返回。
访问如 http:192.168.1.2lua_set_1?i=1j=10 进行测试。 如果我们用纯 set 指令是无法实现的。
再举个实际例子,我们实际工作时经常涉及到网站改版,有时候需要新老并存,或者切一部分流量到新版
第 2 章 Lua 入门 | 31首先在 example.conf 中使用 map 指令来映射 host 到指定 nginx 变量,方便我们测试 首先在 example.conf 中使用 map 指令来映射 host 到指定 nginx 变量,方便我们测试
Java 代码 代码 收藏代码
测试时使用的动态请求
map host item_dynamic {
default 0;
item2014.jd.com 1;
}
如绑定 hosts
192.168.1.2 item.jd.com ;
192.168.1.2 item2014.jd.com ;
此时我们想访问 item2014.jd.com 时访问新版,那么我们可以简单的使用如
Java 代码 代码 收藏代码
if (item_dynamic = 1) {
proxy_pass http:new;
}
proxy_pass http:old;
但是我们想把商品编号为 8 位(比如品类为图书的)没有改版完成,需要按照相应规则跳转到老版,但是其他的到
新版;虽然使用 if 指令能实现,但是比较麻烦,基本需要这样
Java 代码 代码 收藏代码
set jump 0;
if(item_dynamic = 1) {
set jump 1;
}
if(uri ~ ^6[0-9]{7}.html) {
set jump {jump}2;
}
\非强制访问新版,且访问指定范围的商品
if (jump == 02) {
proxy_pass http:old;
}
proxy_pass http:new;
第 2 章 Lua 入门 | 32以上规则还是比较简单的,如果涉及到更复杂的多重 ifelse 或嵌套 ifelse 实现起来就更痛苦了,可能需要到后
端去做了;此时我们就可以借助 lua 了:
Java 代码 代码 收藏代码
set_by_lua to_book '
local ngx_match = ngx.re.match
local var = ngx.var
local skuId = var.skuId
local r = var.item_dynamic ~= 1 and ngx.re.match(skuId, ^[0-9]{8})
if r then return 1 else return 0 end;
';
set_by_lua to_mvd '
local ngx_match = ngx.re.match
local var = ngx.var
local skuId = var.skuId
local r = var.item_dynamic ~= 1 and ngx.re.match(skuId, ^[0-9]{9})
if r then return 1 else return 0 end;
';
\自营图书
if (to_book) {
proxy_pass http:127.0.0.1old_bookskuId.html;
}
\自营音像
if (to_mvd) {
proxy_pass http:127.0.0.1old_mvdskuId.html;
}
\默认
proxy_pass http:127.0.0.1proxyskuId.html;
rewrite_by_lua rewrite_by_lua
执行内部 URL 重写或者外部重定向,典型的如伪静态化的 URL 重写。其默认执行在 rewrite 处理阶段的最后。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_rewrite_1 {
default_type texthtml;
rewrite_by_lua_file usrexampleluatest_rewrite_1.lua;
第 2 章 Lua 入门 | 33echo no rewrite;
}
test_rewrite_1.lua test_rewrite_1.lua
Java 代码 代码 收藏代码
if ngx.req.get_uri_args[jump] == 1 then
return ngx.redirect(http:www.jd.com?jump=1, 302)
end
当我们请求 http:192.168.1.2lua_rewrite_1 时发现没有跳转,而请求 http:192.168.1.2lua_rewrite_1?jum
p=1 时发现跳转到京东首页了。 此处需要301302跳转根据自己需求定义。
example.conf 配置文件 example.conf 配置文件
Java 代码 收藏代码
location lua_rewrite_2 {
default_type texthtml;
rewrite_by_lua_file usrexampleluatest_rewrite_2.lua;
echo rewrite2 uri : uri, a : arg_a;
}
test_rewrite_2.lua test_rewrite_2.lua
Java 代码 收藏代码
if ngx.req.get_uri_args[jump] == 1 then
ngx.req.set_uri(lua_rewrite_3, false);
ngx.req.set_uri(lua_rewrite_4, false);
ngx.req.set_uri_args({a = 1, b = 2});
end
ngx.req.set_uri(uri, false) ngx.req.set_uri(uri, false):可以内部重写 uri(可以带参数),等价于 rewrite ^ lua_rewrite_3;通过配合 if
else 可以实现 rewrite ^ lua_rewrite_3 break;这种功能;此处两者都是 location 内部 url 重写,不会重新发
起新的 location 匹配;
ngx.req.set_uri_args ngx.req.set_uri_args:重写请求参数,可以是字符串(a=1b=2)也可以是 table;
访问如 http:192.168.1.2lua_rewrite_2?jump=0 时得到响应 rewrite2 uri : lua_rewrite_2, a :
第 2 章 Lua 入门 | 34访问如 http:192.168.1.2lua_rewrite_2?jump=1 时得到响应 rewrite2 uri : lua_rewrite_4, a : 1
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_rewrite_3 {
default_type texthtml;
rewrite_by_lua_file usrexampleluatest_rewrite_3.lua;
echo rewrite3 uri : uri;
}
c test_rewrite_3.lua c test_rewrite_3.lua
Java 代码 代码 收藏代码
if ngx.req.get_uri_args[jump] == 1 then
ngx.req.set_uri(lua_rewrite_4, true);
ngx.log(ngx.ERR, =========)
ngx.req.set_uri_args({a = 1, b = 2});
end
ngx.req.set_uri(uri, true) ngx.req.set_uri(uri, true):可以内部重写 uri,即会发起新的匹配 location 请求,等价于 rewrite ^ lua_rewrit
e_4 last;此处看 error log 是看不到我们记录的log。
所以请求如 http:192.168.1.2lua_rewrite_3?jump=1 会到新的 location 中得到响应,此处没有 lua_rewrit
e_4,所以匹配到 lua 请求,得到类似如下的响应 global variable : 2 , shared memory : 1 hello world
即
rewrite ^ lua_rewrite_3; 等价于 ngx.req.set_uri(lua_rewrite_3, false);
rewrite ^ lua_rewrite_3 break; 等价于 ngx.req.set_uri(lua_rewrite_3, false); 加 ifelse判断breakreturn
rewrite ^ lua_rewrite_4 last; 等价于 ngx.req.set_uri(lua_rewrite_4, true);
注意,在使用 rewrite_by_lua 时,开启 rewrite_log on;后也看不到相应的 rewrite log。
access_by_lua access_by_lua
用于访问控制,比如我们只允许内网 ip 访问,可以使用如下形式
Java 代码 代码 收藏代码
第 2 章 Lua 入门 | 35allow 127.0.0.1;
allow 10.0.0.08;
allow 192.168.0.016;
allow 172.16.0.012;
deny all;
example.conf 配置文件 example.conf 配置文件
Java 代码 代码 收藏代码
location lua_access {
default_type texthtml;
access_by_lua_file usrexampleluatest_access.lua;
echo access;
}
test_access.lua test_access.lua
Java 代码 代码 收藏代码
if ngx.req.get_uri_args[token] ~= 123 then
return ngx.exit(403)
end
即如果访问如 http:192.168.1.2lua_access?token=234 将得到 403 Forbidden 的响应。这样我们可以根据
如 cookie 用户 token 来决定是否有访问权限。
content_by_lua content_by_lua
此指令之前已经用过了,此处就不讲解了。
另外在使用 PCRE 进行正则匹配时需要注意正则的写法,具体规则请参考 http:wiki.nginx.orgHttpLuaModul
e中的Special PCRE Sequences部 分。还有其他的注意事项也请阅读官方文档。
第 2 章 Lua 入门 | 363 3
RedisSSDB+Twemproxy 安装与使用 RedisSSDB+Twemproxy 安装与使用目前对于互联网公司不使用 Redis 的很少,Redis 不仅仅可以作为 key-value 缓存,而且提供了丰富的数据结
果如 set、list、map 等,可以实现很多复杂的功能;但是 Redis 本身主要用作内存缓存,不适合做持久化存
储,因此目前有如 SSDB、ARDB 等,还有如京东的 JIMDB,它们都支持 Redis 协议,可以支持 Redis 客户
端直接访问;而这些持久化存储大多数使用了如LevelDB、RocksDB、LMDB 持久化引擎来实现数据的持久化
存储;京东的 JIMDB 主要分为两个版本:LevelDB 和 LMDB,而我们看到的京东商品详情页就是使用 LMDB
引擎作为存储的,可以实现海量KV存储;当然 SSDB 在京东内部也有些部门在使用;另外调研过得如豆瓣的 be
ansDB 也是很不错的。具体这些持久化引擎之间的区别可以自行查找资料学习。
Twemproxy 是一个 RedisMemcached 代理中间件,可以实现诸如分片逻辑、HashTag、减少连接数等功
能;尤其在有大量应用服务器的场景下 Twemproxy 的角色就凸显了,能有效减少连接数。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 38Redis 安装与使用 Redis 安装与使用
下载 redis 并安装 下载 redis 并安装
Java 代码 代码
cd usrservers
wget https:github.comantirezredisarchive2.8.19.tar.gz
tar -xvf 2.8.19.tar.gz
cd redis-2.8.19
make
通过如上步骤构建完毕。
后台启动 Redis 服务器 后台启动 Redis 服务器
Java 代码 代码
nohup usrserversredis-2.8.19srcredis-server usrserversredis-2.8.19redis.conf
查看是否启动成功 查看是否启动成功
Java 代码 代码
ps -aux | grep redis
进入客户端 进入客户端
Java 代码 代码
usrserversredis-2.8.19srcredis-cli -p 6379
执行如下命令 执行如下命令
Java 代码 代码
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 39127.0.0.1:6379> set i 1
OK
127.0.0.1:6379> get i
1
通过如上命令可以看到我们的 Redis 安装成功。更多细节请参考 http:redis.io。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 40SSDB 安装与使用 SSDB 安装与使用
下载 SSDB 并安装 下载 SSDB 并安装
Java 代码 代码
\首先确保安装了g++,如果没有安装,如ubuntu可以使用如下命令安装
apt-get install g++
cd usrservers
wget https:github.comideawussdbarchive1.8.0.tar.gz
tar -xvf 1.8.0.tar.gz
make
后台启动 SSDB 服务器 后台启动 SSDB 服务器
Java 代码 代码
nohup usrserversssdb-1.8.0ssdb-server usrserversssdb-1.8.0ssdb.conf
查看是否启动成功 查看是否启动成功
Java 代码 代码
ps -aux | grep ssdb
进入客户端 进入客户端
Java 代码 代码
usrserversssdb-1.8.0toolsssdb-cli -p 8888
usrserversredis-2.8.19srcredis-cli -p 888
因为 SSDB 支持 Redis 协议,所以用 Redis 客户端也可以访问
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 41执行如下命令 执行如下命令
Java 代码 代码
127.0.0.1:8888> set i 1
OK
127.0.0.1:8888> get i
1
安装过程中遇到错误请参考 http:ssdb.iodocszh_cninstall.html;对于 SSDB 的配置请参考官方文档 http
s:github.comideawussdbhttp:ssdb.iodocszh_cninstall.html。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 42Twemproxy 安装与使用 Twemproxy 安装与使用
首先需要安装 autoconf、automake、libtool 工具,比如 ubuntu 可以使用如下命令安装
Java 代码 代码
apt-get install autoconf automake
apt-get install libtool
下载 Twemproxy 并安装 下载 Twemproxy 并安装
Java 代码 代码
cd usrservers
wget https:github.comtwittertwemproxyarchivev0.4.0.tar.gz
tar -xvf v0.4.0.tar.gz
cd twemproxy-0.4.0
autoreconf -fvi
.configure make
此处根据要注意,如上安装方式在有些服务器上可能在大量如mset时可能导致 Twemproxy 崩溃,需要使用如
CFLAGS=-O1 .configure make 或 CFLAGS=-O3 -fno-strict-aliasing .configure make 安
装。
配置 配置
Java 代码 代码
vim usrserverstwemproxy-0.4.0confnutcracker.yml
Java 代码 代码
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
servers:
- 127.0.0.1:6379:1
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 43启动 Twemproxy 代理 启动 Twemproxy 代理
Java 代码 代码
usrserverstwemproxy-0.4.0srcnutcracker -d -c usrserverstwemproxy-0.4.0confnutcracker.yml
-d 指定后台启动 -c 指定配置文件;此处我们指定了代理端口为 1111,其他配置的含义后续介绍。
查看是否启动成功 查看是否启动成功
Java 代码 代码
ps -aux | grep nutcracker
进入客户端 进入客户端
Java 代码 代码
usrserversredis-2.8.19srcredis-cli -p 1111
执行如下命令 执行如下命令
Java 代码
127.0.0.1:1111> set i 1
OK
127.0.0.1:1111> get i
1
Twemproxy 文档请参考 https:github.comtwittertwemproxy。
到此基本的安装就完成了。接下来做一些介绍。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 44Redis 设置 Redis 设置
基本设置 基本设置
Java 代码 代码
\端口设置,默认6379
port 6379
\日志文件,默认devnull
logfile
Redis 内存 Redis 内存
Java 代码 代码
内存大小对应关系
\ 1k => 1000 bytes
\ 1kb => 1024 bytes
\ 1m => 1000000 bytes
\ 1mb => 10241024 bytes
\ 1g => 1000000000 bytes
\ 1gb => 102410241024 bytes
\设置Redis占用100mb的大小
maxmemory 100mb
\如果内存满了就需要按照如相应算法进行删除过期的最老的
\volatile-lru 根据LRU算法移除设置了过期的key
\allkeys-lru 根据LRU算法移除任何key(包含那些未设置过期时间的key)
\volatile-randomallkeys->random 使用随机算法而不是LRU进行删除
\volatile-ttl 根据Time-To-Live移除即将过期的key
\noeviction 永不过期,而是报错
maxmemory-policy volatile-lru
\Redis并不是真正的LRUTTL,而是基于采样进行移除的,即如采样10个数据移除其中最老的即将过期的
maxmemory-samples 10
而如 Memcached 是真正的 LRU,此处要根据实际情况设置缓存策略,如缓存用户数据时可能带上了过期时
间,此时采用 volatile-lru 即可;而假设我们的数据未设置过期时间,此时可以考虑使用 allkeys-lruallkeys->r
andom;假设我们的数据不允许从内存删除那就使用noeviction。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 45内存大小尽量在系统内存的 60%~80% 之间,因为如客户端、主从时复制时都需要缓存区的,这些也是耗费系统
内存的。
Redis 本身是单线程的,因此我们可以设置每个实例在 6-8GB 之间,通过启动更多的实例提高吞吐量。如 128
GB 的我们可以开启 8GB 10 个实例,充分利用多核 CPU。
Redis 主从 Redis 主从
实际项目时,为了提高吞吐量,我们使用主从策略,即数据写到主 Redis,读的时候从从 Redis上读,这样可以
通过挂载更多的从来提高吞吐量。而且可以通过主从机制,在叶子节点开启持久化方式防止数据丢失。
Java 代码 代码
\在配置文件中挂载主从,不推荐这种方式,我们实际应用时Redis可能是会宕机的
slaveof masterIP masterPort
\从是否只读,默认yes
slave-read-only yes
\当从失去与主的连接或者复制正在进行时,从是响应客户端(可能返回过期的数据)还是返回“SYNC with master in progress”错误,默认yes响应客户端
slave-serve-stale-data yes
\从库按照默认10s的周期向主库发送PING测试连通性
repl-ping-slave-period 10
\设置复制超时时间(SYNC期间批量IO传输、PING的超时时间),确保此值大于repl-ping-slave-period
\repl-timeout 60
\当从断开与主的连接时的复制缓存区,仅当第一个从断开时创建一个,缓存区越大从断开的时间可以持续越长
\ repl-backlog-size 1mb
\当从与主断开持续多久时清空复制缓存区,此时从就需要全量复制了,如果设置为0将永不清空
\ repl-backlog-ttl 3600
\slave客户端缓存区,如果缓存区超过256mb将直接断开与从的连接,如果持续60秒超过64mb也会断开与从的连接
client-output-buffer-limit slave 256mb 64mb 60
此处需要根据实际情况设置client-output-buffer-limit slave和 repl-backlog-size;比如如果网络环境不好,从与主经常断开,而每次设置的数据都特别大而且速度特别快(大量设置html片段)那么就需要加大repl-backlog-size。
主从示例
Java 代码 代码
cd usrserversredis-2.8.19
cp redis.conf redis_6660.conf
cp redis.conf redis_6661.conf
vim redis_6660.conf
vim redis_6661.conf
将端口分别改为 port 6660 和 port 6661,然后启动
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 46Java 代码 代码
nohup usrserversredis-2.8.19srcredis-server usrserversredis-2.8.19redis_6660.conf
nohup usrserversredis-2.8.19srcredis-server usrserversredis-2.8.19redis_6661.conf
查看是否启动
Java 代码 代码
ps -aux | grep redis
进入从客户端,挂主
Java 代码 代码
usrserversredis-2.8.19srcredis-cli -p 6661
Java 代码 代码
127.0.0.1:6661> slaveof 127.0.0.1 6660
OK
127.0.0.1:6661> info replication
\ Replication
role:slave
master_host:127.0.0.1
master_port:6660
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:57
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
进入主
Java 代码 代码
usrserversredis-2.8.19 usrserversredis-2.8.19srcredis-cli -p 6660
Java 代码 代码
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 47127.0.0.1:6660> info replication
\ Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6661,state=online,offset=85,lag=1
master_repl_offset:85
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:84
127.0.0.1:6660> set i 1
OK
进入从
Java 代码
usrserversredis-2.8.19srcredis-cli -p 6661
Java 代码
127.0.0.1:6661> get i
1
此时可以看到主从挂载成功,可以进行主从复制了。使用 slaveof no one 断开主从。
Redis 持久化 Redis 持久化
Redis 虽然不适合做持久化存储,但是为了防止数据丢失有时需要进行持久化存储,此时可以挂载一个从(叶子
节点)只进行持久化存储工作,这样假设其他服务器挂了,我们可以通过这个节点进行数据恢复。
Redis 持久化有 RDB 快照模式和 AOF 追加模式,根据自己需求进行选择。
RDB 持久化 RDB 持久化
Java 代码 代码
\格式save seconds changes 即N秒变更N次则保存,从如下默认配置可以看到丢失数据的周期很长,通过save “” 配置可以完全禁用此持久化
save 900 1
save 300 10
save 60 10000
\RDB是否进行压缩,压缩耗CPU但是可以减少存储大小
rdbcompression yes
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 48\RDB保存的位置,默认当前位置
dir .
\RDB保存的数据库名称
dbfilename dump.rdb
\不使用AOF模式,即RDB模式
appendonly no
可以通过 set 一个数据,然后很快的 kill 掉 redis 进程然后再启动会发现数据丢失了。
AOF 持久化 AOF 持久化
AOF(append only file)即文件追加模式,即把每一个用户操作的命令保存下来,这样就会存在好多重复的命
令导致恢复时间过长,那么可以通过相应的配置定期进行 AOF 重写来减少重复。
Java 代码
\开启AOF
appendonly yes
\AOF保存的位置,默认当前位置
dir .
\AOF保存的数据库名称
appendfilename appendonly.aof
\持久化策略,默认每秒fsync一次,也可以选择always即每次操作都进行持久化,或者no表示不进行持久化而是借助操作系统的同步将缓存区数据写到磁盘
appendfsync everysec
\AOF重写策略(同时满足如下两个策略进行重写)
\当AOF文件大小占到初始文件大小的多少百分比时进行重写
auto-aof-rewrite-percentage 100
\触发重写的最小文件大小
auto-aof-rewrite-min-size 64mb
\为减少磁盘操作,暂缓重写阶段的磁盘同步
no-appendfsync-on-rewrite no
此处的 appendfsync everysec 可以认为是 RDB 和 AOF 的一个折中方案。
当 bgsave 出错时停止写(MISCONF Redis is configured to save RDB snapshots, but is currently not
able to persist on disk.),遇到该错误可以暂时改为 no,当写成功后再改回 yes stop-writes-on-bgsave-
error yes
更多 Redis 持久化请参考 http:redis.readthedocs.orgenlatesttopicpersistence.html。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 49Redis 动态调整配置 Redis 动态调整配置
获取 maxmemory(10mb)
Java 代码 代码
127.0.0.1:6660> config get maxmemory
1) maxmemory
2) 10485760
设置新的 maxmemory(20mb)
Java 代码 代码
127.0.0.1:6660> config set maxmemory 20971520
OK
但是此时重启 redis 后该配置会丢失,可以执行如下命令重写配置文件
Java 代码 代码
127.0.0.1:6660> config rewrite
OK
注意:此时所以配置包括主从配置都会重写。
Redis 执行 Lua 脚本 Redis 执行 Lua 脚本
Redis 客户端支持解析和处理 lua 脚本,因为 Redis 的单线程机制,我们可以借助 Lua 脚本实现一些原子操
作,如扣减库存红包之类的。此处不建议使用 EVAL 直接发送 lua 脚本到客户端,因为其每次都会进行 Lua 脚
本的解析,而是使用 SCRIPT LOAD+ EVALSHA 进行操作。未来不知道是否会用 luajit 来代替 lua,让redis l
ua 脚本性能更强。
到此基本的 Redis 知识就讲完了。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 50Twemproxy 设置 Twemproxy 设置
一旦涉及到一台物理机无法存储的情况就需要考虑使用分片机制将数据存储到多台服务器,可以说是 Redis 集
群;如果客户端都是如 Java 没什么问题,但是如果有多种类型客户端(如 PHP、C)等也要使用那么需要保证
它们的分片逻辑是一样的;另外随着客户端的增加,连接数也会随之增多,发展到一定地步肯定会出现连接数不
够用的;此时 Twemproxy 就可以上场了。主要作用:分片、减少连接数。另外还提供了 Hash Tag 机制来帮助
我们将相似的数据存储到同一个分片。另外也可以参考豌豆荚的 https:github.comwandoulabscodis。
基本配置 基本配置
其使用 YML 语法,如
Java 代码
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
timeout:1000
redis: true
servers:
- 127.0.0.1:6660:1
- 127.0.0.1:6661:1
server1:是给当前分片配置起的名字,一个配置文件可以有多个分片配置;
listen : 监听的 ip 和端口;
hash:散列算法;
distribution:分片算法,比如一致性 Hash 取模;
timeout:连接后端 Redis 或接收响应的超时时间;
redis:是否是 redis 代理,如果是 false 则是 memcached 代理;
servers:代理的服务器列表,该列表会使用 distribution 配置的分片算法进行分片;
分片算法 分片算法
hash 算法:
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 51one_at_a_time
md5
crc16
crc32 (crc32 implementation compatible with libmemcached)
crc32a (correct crc32 implementation as per the spec)
fnv1_64
fnv1a_64
fnv1_32
fnv1a_32
hsieh
murmur
jenkins
分片算法:
ketama(一致性 Hash 算法)
modula(取模)
random(随机算法)
服务器列表 服务器列表
servers:
- ip:port:weight alias
如
servers:
- 127.0.0.1:6660:1
- 127.0.0.1:6661:1
或者
servers:
- 127.0.0.1:6660:1 server1
- 127.0.0.1:6661:1 server2
推荐使用后一种方式,默认情况下使用 ip:port:weight 进行散列并分片,这样假设服务器宕机换上新的服务
器,那么此时得到的散列值就不一样了,因此建议给每个配置起一个别名来保证映射到自己想要的服务器。即如
果不使用一致性 Hash 算法来作缓存服务器,而是作持久化存储服务器时就更有必要了(即不存在服务器下线的
情况,即使服务器 ip:port 不一样但仍然要得到一样的分片结果)。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 52HashTag HashTag
比如一个商品有:商品基本信息(p:id:)、商品介绍(d:id:)、颜色尺码(c:id:)等,假设我们存储时不采用 HashTag
将会导致这些数据不会存储到一个分片,而是分散到多个分片,这样获取时将需要从多个分片获取数据进行合
并,无法进行 mget;那么如果有了 HashTag,那么可以使用“::”中间的数据做分片逻辑,这样 id 一样的将会
分到一个分片。
nutcracker.yml配置如下 nutcracker.yml配置如下
Java 代码 代码
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
hash_tag: ::
servers:
- 127.0.0.1:6660:1 server1
- 127.0.0.1:6661:1 server2
连接 Twemproxy 连接 Twemproxy
Java 代码
usrserversredis-2.8.19srcredis-cli -p 1111
Java 代码 代码
127.0.0.1:1111> set p:12: 1
OK
127.0.0.1:1111> set d:12: 1
OK
127.0.0.1:1111> set c:12: 1
OK
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 53在我的服务器上可以连接 6660 端口 在我的服务器上可以连接 6660 端口
Java 代码 代码
usrserversredis-2.8.19srcredis-cli -p 6660
127.0.0.1:6660> get p:12:
1
127.0.0.1:6660> get d:12:
1
127.0.0.1:6660> get c:12:
1
一致性 Hash 与服务器宕机 一致性 Hash 与服务器宕机
如果我们把 Redis 服务器作为缓存服务器并使用一致性 Hash 进行分片,当有服务器宕机时需要自动从一致性 H
ash 环上摘掉,或者其上线后自动加上,此时就需要如下配置:
是否在节点故障无法响应时自动摘除该节点,如果作为存储需要设置为为false auto_eject_hosts: true 重试
时间(毫秒),重新连接一个临时摘掉的故障节点的间隔,如果判断节点正常会自动加到一致性Hash环上 serve
r_retry_timeout: 30000 节点故障无法响应多少次从一致性Hash环临时摘掉它,默认是2 server_failure_lim
it: 2
支持的 Redis 命令 支持的 Redis 命令
不是所有 Redis 命令都支持,请参考 [https:github.comtwittertwemproxyblobmasternotesredis.mdhr
ef=https:github.comtwittertwemproxyblobmasternotesredis.md)。
因为我们所有的 Twemproxy 配置文件规则都是一样的,因此我们应该将其移到我们项目中。
Java 代码 代码
cp usrserverstwemproxy-0.4.0confnutcracker.yml usrexample
另外 Twemproxy 提供了启动重启停止脚本方便操作,但是需要修改配置文件位置为 usrexamplenutcrack
er.yml。
Java 代码 代码
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 54chmod +x usrserverstwemproxy-0.4.0scriptsnutcracker.init
vim usrserverstwemproxy-0.4.0scriptsnutcracker.init
将 OPTIONS 改为
OPTIONS=-d -c usrexamplenutcracker.yml
另外注释掉. etcrc.dinit.dfunctions;将 daemon --user {USER} {prog} OPTIONS 改为 {prog} O
PTIONS;将 killproc 改为 killall。
这样就可以使用如下脚本进行启动、重启、停止了。
usrserverstwemproxy-0.4.0scriptsnutcracker.init {start|stop|status|restart|reload|condrestart}
对于扩容最简单的办法是:
1. 创建新的集群;
2. 双写两个集群;
3. 把数据从老集群迁移到新集群(不存在才设置值,防止覆盖新的值);
4. 复制速度要根据实际情况调整,不能影响老集群的性能;
5. 切换到新集群即可,如果使用Twemproxy代理层的话,可以做到迁移对读的应用透明。
第 3 章 RedisSSDB+Twemproxy 安装与使用 | 554 4
Lua 模块开发 Lua 模块开发在实际开发中,不可能把所有代码写到一个大而全的 lua 文件中,需要进行分模块开发;而且模块化是高性能 Lu
a 应用的关键。使用 require 第一次导入模块后,所有 Nginx 进程全局共享模块的数据和代码,每个 Worker 进
程需要时会得到此模块的一个副本(Copy-On-Write),即模块可以认为是每 Worker 进程共享而不是每 Ngin
x Server 共享;另外注意之前我们使用 init_by_lua 中初始化的全局变量是每请求复制一个;如果想在多个 Wor
ker 进程间共享数据可以使用 ngx.shared.DICT 或如 Redis 之类的存储。
在 usrexamplelualib 中已经提供了大量第三方开发库如 cjson、redis 客户端、mysql客户端:
cjson.so
resty
aes.lua
core.lua
dns
lock.lua
lrucache
lrucache.lua
md5.lua
memcached.lua
mysql.lua
random.lua
redis.lua……
需要注意在使用前需要将库在 nginx.conf 中导入:
Java 代码 代码
\lua模块路径,其中”;;”表示默认搜索路径,默认到usrserversnginx下找
lua_package_path usrexamplelualib?.lua;;; lua 模块
lua_package_cpath usrexamplelualib?.so;;; c模块
使用方式是在lua中通过如下方式引入
Java 代码 代码
local cjson = require(“cjson”)
local redis = require(“resty.redis”)
接下来我们来开发一个简单的 lua 模块。
Java 代码 代码
vim usrexamplelualibmodule1.lua
Java 代码 代码
第 4 章 Lua 模块开发 | 57local count = 0
local function hello
count = count + 1
ngx.say(count : , count)
end
local _M = {
hello = hello
}
return _M
开发时将所有数据做成局部变量局部函数;通过 _M 导出要暴露的函数,实现模块化封装。
接下来创建 test_module_1.lua
Java 代码 代码
vim usrexampleluatest_module_1.lua
Java 代码 代码
local module1 = require(module1)
module1.hello
使用 local var = require (模块名),该模块会到 lua_package_path 和lua_package_cpath 声明的的位置查
找我们的模块,对于多级目录的使用 require (目录1.目录2.模块名)加载。
example.conf 配置
Java 代码 代码
location lua_module_1 {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_module_1.lua;
}
访问如 http:192.168.1.2lua_module_1 进行测试,会得到类似如下的数据,count 会递增
count : 1
count :2……
count :N
第 4 章 Lua 模块开发 | 58此时可能发现 count 一直递增,假设我们的 worker_processes 2,我们可以通过 kill -9 nginx worker proce
ss 杀死其中一个 Worker 进程得到 count 数据变化。
假设我们创建了 vimusrexamplelualibtestmodule2.lua 模块,可以通过 local module2 = require(test.m
odule2) 加载模块
基本的模块开发就完成了,如果是只读数据可以通过模块中声明 local 变量存储;如果想在每 Worker 进程共
享,请考虑竞争;如果要在多个 Worker 进程间共享请考虑使用 ngx.shared.DICT 或如 Redis 存储。
第 4 章 Lua 模块开发 | 595 5
常用 Lua 开发库 1-redis、mysql、http 客户端 常用 Lua 开发库 1-redis、mysql、http 客户端对于开发来说需要有好的生态开发库来辅助我们快速开发,而 Lua 中也有大多数我们需要的第三方开发库如 Red
is、Memcached、Mysql、Http 客户端、JSON、模板引擎等。 一些常见的 Lua 库可以在 github 上搜索,htt
ps:github.comsearch?utf8=%E2%9C%93q=lua+resty。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 61Redis 客户端 Redis 客户端
lua-resty-redis 是为基于 cosocket API 的 ngx_lua 提供的 Lua redis 客户端,通过它可以完成 Redis 的操
作。默认安装 OpenResty 时已经自带了该模块,使用文档可参考https:github.comopenrestylua-resty-r
edis。
在测试之前请启动 Redis 实例:
nohup usrserversredis-2.8.19srcredis-serverusrserversredis-2.8.19redis_6660.conf
基本操作 基本操作
编辑 test_redis_baisc.lua
Java 代码 代码
local function close_redis(red)
if not red then
return
end
local ok, err = red:close
if not ok then
ngx.say(close redis error : , err)
end
end
local redis = require(resty.redis)--创建实例
local red = redis:new--设置超时(毫秒)
red:set_timeout(1000)--建立连接
local ip = 127.0.0.1
local port = 6660
local ok, err = red:connect(ip, port)
if not ok then
ngx.say(connect to redis error : , err)
return close_redis(red)
end--调用API进行处理
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 62ok, err = red:set(msg, hello world)
if not ok then
ngx.say(set msg error : , err)
return close_redis(red)
end--调用API获取数据
local resp, err = red:get(msg)
if not resp then
ngx.say(get msg error : , err)
return close_reedis(red)
end--得到的数据为空处理
if resp == ngx.null then
resp = '' --比如默认值
end
ngx.say(msg : , resp)
close_redis(red)
基本逻辑很简单,要注意此处判断是否为 nil,需要跟 ngx.null 比较。
example.conf 配置文件
Java 代码 代码
location lua_redis_basic {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_redis_basic.lua;
}
访问如 http:192.168.1.2lua_redis_basic 进行测试,正常情况得到如下信息 msg : hello world
连接池 连接池
建立 TCP 连接需要三次握手而释放 TCP 连接需要四次握手,而这些往返时延仅需要一次,以后应该复用 TCP
连接,此时就可以考虑使用连接池,即连接池可以复用连接。
我们只需要将之前的 close_redis 函数改造为如下即可:
Java 代码 代码
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 63local function close_redis(red)
if not red then
return
end--释放连接(连接池实现)
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say(set keepalive error : , err)
end
end
即设置空闲连接超时时间防止连接一直占用不释放;设置连接池大小来复用连接。
此处假设调用 red:set_keepalive,连接池大小通过 nginx.conf 中 http 部分的如下指令定义:
默认连接池大小,默认 30 lua_socket_pool_size 30; 默认超时时间,默认 60s lua_socket_keepalive_tim
eout 60s;
注意:
1. 连接池是每 Worker 进程的,而不是每 Server 的;
2. 当连接超过最大连接池大小时,会按照 LRU 算法回收空闲连接为新连接使用;
3. 连接池中的空闲连接出现异常时会自动被移除;
4. 连接池是通过 ip 和 port 标识的,即相同的 ip 和 port 会使用同一个连接池(即使是不同类型的客户端如 Re
dis、Memcached);
5. 连接池第一次 set_keepalive 时连接池大小就确定下了,不会再变更;
6. cosocket 的连接池 http:wiki.nginx.orgHttpLuaModuletcpsock:setkeepalive。
pipeline pipeline
pipeline 即管道,可以理解为把多个命令打包然后一起发送;MTU(Maxitum Transmission Unit 最大传输单
元)为二层包大小,一般为 1500 字节;而 MSS(Maximum Segment Size 最大报文分段大小)为四层包大
小,其一般是 1500-20(IP 报头)-20(TCP 报头)=1460 字节;因此假设我们执行的多个 Redis 命令能在
一个报文中传输的话,可以减少网络往返来提高速度。因此可以根据实际情况来选择走 pipeline 模式将多个命令
打包到一个报文发送然后接受响应,而 Redis 协议也能很简单的识别和解决粘包。
修改之前的代码片段
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 64Java 代码 代码
red:init_pipeline
red:set(msg1, hello1)
red:set(msg2, hello2)
red:get(msg1)
red:get(msg2)
local respTable, err = red:commit_pipeline--得到的数据为空处理
if respTable == ngx.null then
respTable = {} --比如默认值
end--结果是按照执行顺序返回的一个table
for i, v in ipairs(respTable) do
ngx.say(msg : , v,
)
end
通过 init_pipeline 初始化,然后通过 commit_pipieline 打包提交 init_pipeline 之后的Redis命令;返回结
果是一个 lua table,可以通过 ipairs 循环获取结果;
配置相应 location,测试得到的结果
msg : OK
msg : OK
msg : hello1
msg : hello2
Redis Lua 脚本
利用 Redis 单线程特性,可以通过在 Redis 中执行 Lua 脚本实现一些原子操作。如之前的 red:get(msg) 可
以通过如下两种方式实现:
1、直接 eval:
Java 代码 代码
local resp, err = red:eval(return redis.call('get', KEYS[1]), 1, msg);
2、script load 然后 evalsha SHA1 校验和,这样可以节省脚本本身的服务器带宽: Java 代码 代码
local sha1, err = red:script(load, return redis.call('get', KEYS[1]));
if not sha1 then
ngx.say(load script error : , err)
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 65return close_redis(red)
end
ngx.say(sha1 : , sha1,
)
local resp, err = red:evalsha(sha1, 1, msg);
首先通过 script load 导入脚本并得到一个 sha1 校验和(仅需第一次导入即可),然后通过evalsha 执行 sha1
校验和即可,这样如果脚本很长通过这种方式可以减少带宽的消耗。
此处仅介绍了最简单的 redis lua 脚本,更复杂的请参考官方文档学习使用。
另外 Redis 集群分片算法该客户端没有提供需要自己实现,当然可以考虑直接使用类似于Twemproxy 这种中间
件实现。
Memcached 客户端使用方式和本文类似,本文就不介绍了。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 66Mysql 客户端 Mysql 客户端
lua-resty-mysql 是为基于 cosocket API 的 ngx_lua 提供的 Lua Mysql 客户端,通过它可以完成 Mysql 的
操作。默认安装 OpenResty 时已经自带了该模块,使用文档可参考https:github.comopenrestylua-rest
y-mysql。
编辑 test_mysql.lua 编辑 test_mysql.lua
Java 代码
local function close_db(db)
if not db then
return
end
db:close
end
local mysql = require(resty.mysql)--创建实例
local db, err = mysql:new
if not db then
ngx.say(new mysql error : , err)
return
end--设置超时时间(毫秒)
db:set_timeout(1000)
local props = {
host = 127.0.0.1,port = 3306,database = mysql,user = root,password = 123456
}
local res, err, errno, sqlstate = db:connect(props)
if not res then
ngx.say(connect to mysql error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 67--删除表
local drop_table_sql = drop table if exists test
res, err, errno, sqlstate = db:query(drop_table_sql)
if not res then
ngx.say(drop table error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end--创建表
local create_table_sql = create table test(id int primary key auto_increment, ch varchar(100))
res, err, errno, sqlstate = db:query(create_table_sql)
if not res then
ngx.say(create table error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end--插入
local insert_sql = insert into test (ch) values('hello')
res, err, errno, sqlstate = db:query(insert_sql)
if not res then
ngx.say(insert error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
res, err, errno, sqlstate = db:query(insert_sql)
ngx.say(insert rows : , res.affected_rows, , id : , res.insert_id,
)--更新
local update_sql = update test set ch = 'hello2' where id = .. res.insert_id
res, err, errno, sqlstate = db:query(update_sql)
if not res then
ngx.say(update error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
ngx.say(update rows : , res.affected_rows,
)--查询
local select_sql = select id, ch from test
res, err, errno, sqlstate = db:query(select_sql)
if not res then
ngx.say(select error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 68for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say(select row , i, : , name, = , value,
)
end
end
ngx.say(
)--防止sql注入
local ch_param = ngx.req.get_uri_args[ch] or ''--使用ngx.quote_sql_str防止sql注入
local query_sql = select id, ch from test where ch = .. ngx.quote_sql_str(ch_param)
res, err, errno, sqlstate = db:query(query_sql)
if not res then
ngx.say(select error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say(select row , i, : , name, = , value,
)
end
end--删除
local delete_sql = delete from test
res, err, errno, sqlstate = db:query(delete_sql)
if not res then
ngx.say(delete error : , err, , errno : , errno, , sqlstate : , sqlstate)
return close_db(db)
end
ngx.say(delete rows : , res.affected_rows,
)
close_db(db)
对于新增修改删除会返回如下格式的响应:
Java 代码 代码
{
insert_id = 0,server_status = 2,warning_count = 1,第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 69affected_rows = 32,message = nil
}
affected_rows 表示操作影响的行数,insert_id 是在使用自增序列时产生的 id。
对于查询会返回如下格式的响应:
Java 代码 代码
{
{ id= 1, ch= hello},{ id= 2, ch= hello2}
}
null 将返回 ngx.null。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码
location lua_mysql {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_mysql.lua;
}
访问如 访问如 http:192.168.1.2lua_mysql?ch=hello http:192.168.1.2lua_mysql?ch=hello 进行测试,得到如下结果 进行测试,得到如下结果
Java 代码 代码
insert rows : 1 , id : 2
update rows : 1
select row 1 : ch = hello
select row 1 : id = 1
select row 2 : ch = hello2
select row 2 : id = 2
select row 1 : ch = hello
select row 1 : id = 1
delete rows : 2
客户端目前还没有提供预编译 SQL 支持(即占位符替换位置变量),这样在入参时记得使用 ngx.quote_sql_st
r 进行字符串转义,防止 sql 注入;连接池和之前 Redis 客户端完全一样就不介绍了。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 70对于 Mysql 客户端的介绍基本够用了,更多请参考 https:github.comopenrestylua-resty-mysql。
其他如 MongoDB 等数据库的客户端可以从 github 上查找使用。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 71Http 客户端 Http 客户端
OpenResty 默认没有提供 Http 客户端,需要使用第三方提供;当然我们可以通过 ngx.location.capture 去方
式实现,但是有一些限制,后边我们再做介绍。
我们可以从 github 上搜索相应的客户端,比如 https:github.compintsizedlua-resty-http。
lua-resty-http lua-resty-http
下载 lua-resty-http 客户端到 lualib 下载 lua-resty-http 客户端到 lualib
Java 代码 代码
cd usrexamplelualibresty
wget https:raw.githubusercontent.compintsizedlua-resty-httpmasterlibrestyhttp_headers.lua
wget https:raw.githubusercontent.compintsizedlua-resty-httpmasterlibrestyhttp.lua
test_http_1.lua test_http_1.lua
Java 代码 代码
local http = require(resty.http)--创建http客户端实例
local httpc = http.new
local resp, err = httpc:request_uri(http:s.taobao.com, {
method = GET,path = search?q=hello,headers = {
[User-Agent] = Mozilla5.0 (Windows NT 6.1; WOW64) AppleWebKit537.36 (KHTML, like Gecko) Chrome40.0.2214.111 Safari537.36
}
})
if not resp then
ngx.say(request error :, err)
return
end--获取状态码
ngx.status = resp.status
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 72--获取响应头
for k, v in pairs(resp.headers) do
if k ~= Transfer-Encoding and k ~= Connection then
ngx.header[k] = v
end
end--响应体
ngx.say(resp.body)
httpc:close
响应头中的 Transfer-Encoding 和 Connection 可以忽略,因为这个数据是当前 server 输出的。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码
location lua_http_1 {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_http_1.lua;
}
在 nginx.conf 中的 http 部分添加如下指令来做 DNS 解析 在 nginx.conf 中的 http 部分添加如下指令来做 DNS 解析
Java 代码 代码
resolver 8.8.8.8;
记得要配置 DNS 解析器 resolver 8.8.8.8,否则域名是无法解析的。
访问如 访问如 http:192.168.1.2lua_http_1 http:192.168.1.2lua_http_1 会看到淘宝的搜索界面。 会看到淘宝的搜索界面。
使用方式比较简单,如超时和连接池设置和之前 Redis 客户端一样,不再阐述。更多客户端使用规则请参考 http
s:github.compintsizedlua-resty-http。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 73ngx.location.capture ngx.location.capture
ngx.location.capture 也可以用来完成 http 请求,但是它只能请求到相对于当前 nginx 服务器的路径,不能使
用之前的绝对路径进行访问,但是我们可以配合 nginx upstream 实现我们想要的功能。
在 nginx.cong中 的 http 部分添加如下 upstream 配置 在 nginx.cong中 的 http 部分添加如下 upstream 配置
Java 代码 代码
upstream backend {
server s.taobao.com;
keepalive 100;
}
即我们将请求 upstream 到 backend;另外记得一定要添加之前的 DNS 解析器。
在 example.conf 配置如下 location 在 example.conf 配置如下 location
Java 代码 代码
location ~ proxy(.) {
internal;
proxy_pass http:backend1is_argsargs;
}
internal 表示只能内部访问,即外部无法通过 url 访问进来; 并通过 proxy_pass 将请求转发到 upstream。
test_http_2.lua test_http_2.lua
Java 代码 代码
local resp = ngx.location.capture(proxysearch, {
method = ngx.HTTP_GET,args = {q = hello}
})
if not resp then
ngx.say(request error :, err)
return
end
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 74ngx.log(ngx.ERR, tostring(resp.status))--获取状态码
ngx.status = resp.status--获取响应头
for k, v in pairs(resp.header) do
if k ~= Transfer-Encoding and k ~= Connection then
ngx.header[k] = v
end
end--响应体
if resp.body then
ngx.say(resp.body)
end
通过 ngx.location.capture 发送一个子请求,此处因为是子请求,所有请求头继承自当前请求,还有如 ngx.ctx
和ngx.var 是否继承可以参考官方文档 http:wiki.nginx.orgHttpLuaModulengx.location.capture。 另外还
提供了 ngx.location.capture_multi用于并发发出多个请求,这样总的响应时间是最慢的一个,批量调用时有
用。
example.conf 配置文件 example.conf 配置文件
Java 代码 代码
location lua_http_2 {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexampleluatest_http_2.lua;
}
访问如 访问如 http:192.168.1.2lua_http_2 http:192.168.1.2lua_http_2 进行测试可以看到淘宝搜索界面。 进行测试可以看到淘宝搜索界面。
我们通过 upstream+ngx.location.capture 方式虽然麻烦点,但是得到更好的性能和upstream 的连接池、负
载均衡、故障转移、proxy cache 等特性。
不过因为继承在当前请求的请求头,所以可能会存在一些问题,比较常见的就是 gzip 压缩问题,ngx.location.c
apture 不会解压缩后端服务器的 GZIP 内容,解决办法可以参考https:github.comopenrestylua-nginx-m
oduleissues12;因为我们大部分这种http 调用的都是内部服务,因此完全可以在 proxy location 中添加prox
y_pass_request_headers off; 来不传递请求头。
第 5 章 常用 Lua 开发库 1-redis、mysql、http 客户端 | 756 6
JSON 库、编码转换、字符串处理 JSON 库、编码转换、字符串处理JSON 库 JSON 库
在进行数据传输时 JSON 格式目前应用广泛,因此从 Lua 对象与 JSON 字符串之间相互转换是一个非常常见的
功能;目前 Lua 也有几个 JSON 库,本人用过 cjson、dkjson。其中 cjson的语法严格(比如 unicode \u002
0\u7eaf ),要求符合规范否则会解析失败(如 \u002),而 dkjson 相对宽松,当然也可以通过修改 cjson 的
源码来完成一些特殊要求。而在使用dkjson 时也没有遇到性能问题,目前使用的就是 dkjson。使用时要特别注
意的是大部分 JSON库都仅支持 UTF-8 编码;因此如果你的字符编码是如 GBK 则需要先转换为 UTF-8 然后
进行处理。
test_cjson.lua test_cjson.lua
Java 代码 代码
local cjson = require(cjson)--lua对象到字符串
local obj = {
id = 1,name = zhangsan,age = nil,is_male = false,hobby = {film, music, read}
}
local str = cjson.encode(obj)
ngx.say(str,
)--字符串到lua对象
str = '{hobby:[film,music,read],is_male:false,name:zhangsan,id:1,age:null}'
local obj = cjson.decode(str)
ngx.say(obj.age,
)
ngx.say(obj.age == nil,
)
ngx.say(obj.age == cjson.null,
)
ngx.say(obj.hobby[1],
)--循环引用
obj = {
id = 1
第 6 章 JSON 库、编码转换、字符串处理 | 77}
obj.obj = obj-- Cannot serialise, excessive nesting--ngx.say(cjson.encode(obj),
)
local cjson_safe = require(cjson.safe)--nil
ngx.say(cjson_safe.encode(obj),
)
null 将会转换为 cjson.null;循环引用会抛出异常 Cannot serialise, excessive nesting,默认解析嵌套深度是
1000,可以通过 cjson.encode_max_depth 设置深度提高性能;使用 cjson.safe 不会抛出异常而是返回 ni
l。
example.conf 配置文件 example.conf 配置文件
Java 代码
location ~ lua_cjson {
default_type 'texthtml';
lua_code_cache on;
content_by_lua_file usrexamplel ......
您现在查看是摘要介绍页, 详见PDF附件(2384KB,183页)。




