利用openresty+redis+lua实现灰度发布
为什么需要?
Nginx去开发一个官方自带了非常多的核心模块再加上第三方的模块能够满足我们大部分的业务需要,但是业务的需求、业务的场景变化需要添加些额外的功能,如果自己nginx模块相对来说比较笨重,我们可以使用lua脚本直接内嵌到nginx当中实现一些业务逻辑,完成一些特殊的功能需求。
什么是lua?
Lua并发,比起回调机制的并发来说代码更容易编写和理解,排查问题也会容易是一种轻量级、可嵌入式的脚本语言,这样可以非常容易的嵌入到其他语言中使用。另外Lua提供了协程并发,即以同步调用的方式进行异步执行,从而实现
什么是ngx_lua?
ngx_lua个Web容器;这样开发人员就可以使用是Nginx的一个模块,将Lua嵌入到LuaNginx语言开发高性能中,从而可以使用Web应用了。Lua来编写脚本,这样就可以使用\ Lua编写应用脚本,部署到Nginx中运行,即Nginx变成了一
场景
理论上可以使用到几千行。目前见到的一些应用场景:ngx_lua开发各种复杂的web应用,不过Lua是一种脚本/动态语言,不适合业务逻辑比较重的场景,适合小巧的应用场景,代码行数保持在几十行
web京东的列表页应用:会进行一些业务逻辑处理,甚至进行耗/商品详情页; CPU的模板渲染,一般流程:mysql/redis/http获取数据、业务处理、产生JSON/XML/模板渲染内容,比如
接入网关:实现如数据校验前置、缓存前置、数据过滤、正在开发的无线网关、单品页统一服务、实时价格、动态服务;API请求聚合、AB测试、灰度发布、降级、监控等功能,比如京东的交易大Nginx节点、无线部门
Web防火墙:可以进行IP/URL/UserAgent/Referer黑名单、限流等功能;
缓存服务器:可以对响应内容进行缓存,减少到后端的请求,从而提升性能;
一、安装Lua模块
1、安装lua
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz tar -zxvf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5 make && make install PREFIX=/usr/local/LuaJIT
2、/etc/profile 文件中加入环境变量
装export LUAJIT_LIB=/usr/local/LuaJIT/libnginx的lua模块时会报错很早不到luajit 库 source /etc/profile^ export LUAJIT_INC=export LUAJIT_LIB=/usr/local/LuaJIT/lib立刻生效,如果ngixn安装的时候不生效需要重启服务器^ #路径是上面luajit实际安装路径,路径错误安
3、下载ngx_devel_kit模块
wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
NDK减轻第三方模块开发的代码量。(nginx development kit)模块是一个拓展nginx服务器核心功能的模块,第三方模块开发可以基于它来快速实现。 NDK提供函数和宏处理一些基本任务,
4、下载lua-nginx-module模块
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.9rc7.tar.gz
lua-nginx-module 模块使nginx中能直接运行lua
5、再次编译nginx
增加这两个模块
--add-module=/root/download/lua-nginx-module-0.10.9rc7--add-module=/root/download/ngx_devel_kit-0.3.
并且在编译的时候指定动态链接库位置/usr/local/LuaJIT/lib 为你安装lua脚本的位置
./configure --with-ld-opt="-Wl,-rpath,/usr/local/LuaJIT/lib" --with-http_gzip_static_module --with-file-aio --add-module=/root/course/nginx-upsync-module-2.1.0 --with-threads --add-module=/root/course/ngx_cache_purge-2.3 --add-
module=/root/course/lua-nginx-module-0.10.9rc7 --add-module=/root/course/ngx_devel_kit-0.3.
二、ngx_lua入门
所有阶段都会运行的;另外指令可以在ngx_lua^ 属于 nginx的一部分,它的执行指令都包含在http、server、server ifnginx的^11 、个步骤之中了,相应的处理阶段可以做插入式处理,即可插拔式架构,不过location、location if几个范围进行配置: ngx_lua并不是
指令指令 所处处理阶段所处处理阶段 使用范围使用范围 解释解释
init_by_luainit_by_lua_file loading-config http nginx Master 块 进程加载配置时执行;通常用于初始化全局配置/预加载Lua模
init_worker_by_luainit_worker_by_lua_file starting-worker http 每个在init_by_luaNginx Worker之后调用;进程启动时调用的计时器,如果 通常用于定时拉取配置/数据,或者后端服务的健Master进程不允许则只会
康检查
set_by_luaset_by_lua_file rewrite server,serverif,location,location if 设置到非常快;nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做
rewrite_by_luarewrite_by_lua_file rewritetail http,server,location,locationif rewrite阶段处理,可以实现复杂的转发/重定向逻辑;
access_by_luaaccess_by_lua_file accesstail http,server,location,locationif 请求访问阶段处理,用于访问控制
content_by_luacontent_by_lua_file content location,location if 内容处理器,接收请求处理并输出响应
header_filter_by_luaheader_filter_by_lua_file output-header-
filter
httplocation if,server,location, 设置header和cookie
body_filter_by_luabody_filter_by_lua_file output-body-
filter
httplocation if,server,location, 对响应数据进行过滤,比如截断、替换。
log log阶段处理,比如记录访问量/统计平均响应时间
log_by_lualog_by_lua_file httplocation if,server,location,
nginx-lua 部分api说明
参数参数 说明说明
ngx.arg 指令参数,如跟在content_by_lua_file后面的参数
ngx.var 变量,ngx.var.VARIABLE引用某个变量
ngx.ctx 请求的lua上下文
ngx.header 响应头,ngx.header.HEADER引用某个头
ngx.status 响应码
API 说明
ngx.log 输出到error.log
print 等价于 ngx.log(ngx.NOTICE, ...)
ngx.send_headers 发送响应头
ngx.headers_sent 响应头是否已发送
ngx.resp.get_headers 获取响应头
ngx.timer.at 注册定时器事件
ngx.is_subrequest 当前请求是否是子请求
ngx.location.capture 发布一个子请求
ngx.location.capture_multi 发布多个子请求
ngx.print 输出响应
ngx.say 输出响应,自动添加'\n'
ngx.flush 刷新响应
ngx.exit 结束请求
ngx.sleep 无阻塞的休眠(使用定时器实现)
ngx.on_abort 注册client断开请求时的回调函数
ngx.req.start_time 请求的开始时间
ngx.req.http_version 请求的HTTP版本号
ngx.req.raw_header 请求头(包括请求行)
ngx.req.get_method 请求方法
ngx.req.set_method 请求方法重载
ngx.req.set_uri 请求URL重写
ngx.req.get_uri_args 获取请求参数
ngx.req.get_post_args 获取请求表单
ngx.req.get_headers 获取请求头
ngx.escape_uri 字符串的url编码
ngx.unescape_uri 字符串url解码
ngx.encode_args 将table编码为一个参数字符串
ngx.decode_args 将参数字符串编码为一个table
ngx.encode_base64 字符串的base64编码
ngx.decode_base64 字符串的base64解码
ngx.crc32_short 字符串的crs32_short哈希
ngx.crc32_long 字符串的crs32_long哈希
ngx.hmac_sha1 字符串的hmac_sha1哈希
ngx.md5 返回 16 进制MD
ngx.md5_bin 返回 2 进制MD
ngx.sha1_bin 返回 2 进制sha1哈希值
ngx.quote_sql_str SQL语句转义
ngx.today 返回当前日期
ngx.time 返回UNIX时间戳
ngx.now 返回当前时间
ngx.update_time 刷新时间后再返回
ngx.cookie_time 返回的时间可用于cookie值
ngx.http_time 返回的时间可用于HTTP头
ngx.parse_http_time 解析HTTP头的时间
相应的api在这里可以找到
https://github.com/openresty/lua-nginx-module#nginx-api-for-lua
openresty+redis+lua实现灰度发布
grays.lua
-- 引入redis的客户端
local redis=require "resty.redis"
-- 获取客户端的ip地址
--ngx.req.get_headers()
local_ip = ngx.var.remote_addr
--记录日志
if local_ip == nil then
ngx.log(ngx.ERR,'IP-----',local_ip)
end
ngx.log(ngx.ERR,'IP-----',local_ip)
--连接redis,查询当前ip,是否再redis当中
local cache=redis.new()
local ok,err = cache.connect(cache,'127.0.0.1',6379)
if not ok then
--ngx.log(ngx.ERR,'连接失败',local_ip)
return
end
local allow_ip=cache:get(local_ip)
if allow_ip == local_ip then
ngx.exec("@client2")
return
end
ngx.exec("@client1")
cache:close()
--ngx.header.content_type="text/plain"
--ngx.say(local_ip);
--[[
]]
nginx.conf
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
lua_code_cache off; #关闭代码缓存
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
upstream client1 {
server 127.0.0.1:9501; #模拟生产服务器
}
upstream client2 {
server 127.0.0.1:9502; #模拟预发布服务器
}
#加载lua包
lua_package_path "/usr/local/openresty/lualib/project/common/?.lua;;";
server {
listen 80;
#server_name localhost;
#charset koi8-r;
set $test "hello world";
#access_log logs/host.access.log main;
location /test {
#root /www;
#content_by_lua '
# ngx.header.content_type="text/plain";
# ngx.say (ngx.var.test);
#';
content_by_lua_file /usr/local/openresty/lualib/project/grays/gray.lua;
#index index.html index.htm;
}
location @client1 {
proxy_pass http://client1;
}
location @client2 {
proxy_pass http://client2;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}