OpenResty集成了Nginx,在支持Nginx所有的功能前提下,整合了Lua、Mysql、 Redis、Memcached等插件,使Nginx功能更强大。做7层负载均衡,做web开发,缓存,流控、waf、网关。
推荐张开涛的《亿级流量网站架构核心技术》。里边有很多解决高并发问题的思路和方案,其中包含OpenResty的使用。
LUA第三方库存放在openresty\lualib\resty目录下,即可在lua中调用
百度->OpenResty最佳实践
https://moonbingbing.gitbooks.io/openresty-best-practices/content/
https://www.showapi.com/book/view/2123/65
查看错误日志 openresty-1.15.8.2-win64\logs\error.log,上边会有lua的异常。包含时间、lua文件、报错行数、错误信息。
2020/03/10 11:00:29 [error] 5496#8124: *1 lua entry thread aborted: runtime error: ./lua/api.lua:1: attempt to index global 'headers' (a nil value)
stack traceback:
coroutine 0:
./lua/api.lua: in main chunk, client: 127.0.0.1, server: localhost, request: "POST /luacache?key=123 HTTP/1.1", subrequest: "/api", host: "localhost"
https://space.bilibili.com/431715942/video?tid=0&page=1&keyword=&order=pubdate
https://www.runoob.com/lua/lua-tutorial.html
https://blog.csdn.net/imilli/article/details/83621325
案例:https://moonbingbing.gitbooks.io/openresty-best-practices/content/ngx_lua/lua-variable-scope.html
局部变量:https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/local.html
local局部变量线程不共享,非局部变量线程共享
g_var = 1 -- global var
local l_var = 2 -- local var
可以通过LUA接收请求后控制请求跳转。这样LUA代码就可以包裹整个请求的响应前->响应后过程。可以实现:限流、灰度发布、缓存命中与存储等等。
ngx.exec("@url");
--跳转到nginx中配置的 location @url {}
--子请求,body 必须是字符串形式
--"/api"转发会nginx,寻找匹配location
local resp = ngx.location.capture("/api", {method=ngx.HTTP_POST,
body = "hello",
args = {a = "aa", b = "bb"}})
ngx.say(resp.body)
生成、解析复杂Json字符串:{"age":"23","testArray":{"array":[8,9,11,14,25]},"Himi":"himigame.com"}
local cjson = require "cjson"
--生成json字符串
local _jsonArray={}
_jsonArray[1]=8
_jsonArray[2]=9
_jsonArray[3]=11
_jsonArray[4]=14
_jsonArray[5]=25
local _arrayFlagKey={}
_arrayFlagKey["array"]=_jsonArray
local tab = {}
tab["Himi"]="himigame.com"
tab["testArray"]=_arrayFlagKey
tab["age"]="23"
--tab转json字符串
local jsonData = cjson.encode(tab)
print(jsonData)
-- 打印结果: {"age":"23","testArray":{"array":[8,9,11,14,25]},"Himi":"himigame.com"}
--解析json
local data = cjson.decode(jsonData)
local a = data.age
local b = data.testArray.array[2]
local c = data.Himi
--返回响应内容体;(内容体结束后没有换行符;)
ngx.print("aaaaaaaaaaaaaa")
--返回响应内容体;(内容体结束后,输出一个换行符;)
ngx.say("aaaaaaaaaaaaaaa")
https://blog.csdn.net/xiejunna/article/details/71647281
https://segmentfault.com/a/1190000007923803
local ip=ngx.req.get_headers()["X-REAL-IP"]
https://blog.csdn.net/qq362228416/article/details/54746353
https://www.iteye.com/blog/jinnianshilongnian-2187328
redis长/短链接测试:长连接(用连接池)性能远远好过短连接
https://www.cnblogs.com/tinywan/p/6838630.ht
防止sql注入:
https://moonbingbing.gitbooks.io/openresty-best-practices/content/openresty/safe_sql.html
OpenResty有两种缓存方式,分别是shared_dict(worker之间共享,所以必须保证操作的原子性)和lua-resty-lrucache(worker不共享性能更好)。使用jmeter压测500线程3轮,有/无lua缓存性能差别很明显。
https://moonbingbing.gitbooks.io/openresty-best-practices/content/ngx_lua/cache.html
shared_dict—api
shared_dict提供了丰富的原子性api。可以实现LRU、TTL、putIfAbsent、setnx、incr等类似功能。
http://www.hangdaowangluo.com/archives/2762
利用双重检查锁解决缓存击穿
https://moonbingbing.gitbooks.io/openresty-best-practices/content/lock/cache-miss-storm.html
https://blog.csdn.net/echineselearning/article/details/63792905
local resty_lock = require "resty.lock";
local lock = resty_lock:new(“lockkey”);
local elapsed, err = lock:lock(key); --试图获取锁,如果锁以被占用则当前请求被阻塞
if not elapsed then --取锁失败
if err == “timeout” then --锁超时,被自动释放,根据自己的业务情况选择后续动作
do something
return;
end
do something
return;
end
--取锁成功
...
lock:unlock();
使用说明:https://www.kutu66.com//GitHub/article_107620
案例视频:https://www.bilibili.com/video/av61296135?p=18
首先通过后端程序生成静态html页面,保存到OpenResty的html目录下。静态html页面中为经常变化的值预留lua渲染占位符{},例如:库存,价格等信息。当外部请求此页面时,通过lua请求后端mysql\redis,渲染占位符部分{}信息后返回。
适用场景:1 与用户权限无关的页面。比如:门户首页、商品展示页、公共页等等
及时返回客户端请求,不影响用户体验,返回后继续执行lua代码完成后续处理,比如缓存、断连、日志等....
注意此时虽然已经响应用户,但是连接并没有断开。
ngx.eof() 下面的代码是响应后继续执行的
https://moonbingbing.gitbooks.io/openresty-best-practices/content/ngx_lua/continue_after_eof.html
功能
支持IP白名单和黑名单功能,直接将黑名单的IP访问拒绝。
支持URL白名单,将不需要过滤的URL进行定义。
支持User-Agent的过滤,匹配自定义规则中的条目,然后进行处理(返回403)。
支持CC流量攻击防护,单个URL指定时间的访问次数,超过设定值,直接返回403。
支持Cookie过滤,匹配自定义规则中的条目,然后进行处理(返回403)。
支持URL过滤,匹配自定义规则中的条目,如果用户请求的URL包含这些,返回403。
支持URL参数过滤,原理同上。
支持日志记录,将所有拒绝的操作,记录到日志中去
视频:video/av73199123
文档:
https://blog.csdn.net/m0_37886429/article/details/73178889
https://blog.csdn.net/chuanxincui/article/details/86089763
\openresty-1.15.8.2-win64\conf\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 5000; #设置单个worker_processes最大连接数
use epoll; #linux使用epoll事件驱动,因为epoll的性能相比其他事件驱动要好很多
}
http {
include mime.types;
default_type text/html;
#添加;;标识默认路径下的lualib
lua_package_path "$prefix/lualib/?.lua;;";
lua_package_cpath "$prefix/lualib/?.so;;";
#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; (秒) 1d=1天 1h=1小时 1m=1分钟
keepalive_timeout 30;
gzip on; #文件压缩 (优化带宽和响应速度)
gzip_buffers 32 4K; #缓冲
gzip_comp_level 6; #压缩级别(级别越高,压的越小,越浪费CPU计算资源)
gzip_min_length 10000; #小于10000字节不压缩
gzip_types application/javascript text/css text/xml; #图片/mp3这样的二进制文件,不必压缩。因为压缩率比较小, 比如100->80字节,而且压缩也是耗费CPU资源的.
gzip_disable "MSIE [1-6]\."; #配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_vary on; # 是否传输gzip压缩标志
proxy_buffer_size 128k;
#代理请求缓存区_这个缓存区间会保存用户的头信息以供Nginx进行规则处理_一般只要能保存下头信息即可
proxy_buffers 4 128k;
#同上 告诉Nginx保存单个用的几个Buffer最大用多大空间
proxy_busy_buffers_size 256k;
#如果系统很忙的时候可以申请更大的proxy_buffers 官方推荐*2
proxy_temp_file_write_size 128k;
#proxy缓存临时文件的大小
proxy_temp_path E:/nginx/temp;
#用于指定本地目录来缓冲较大的代理请求(绝对路径)
proxy_cache_path E:/nginx/cache levels=1:2 keys_zone=cache_one:200m inactive=1d max_size=20g;
#设置web缓存区名为cache_one,可以缓存任意格式响应数据。levels=E:/nginx/cache中文件目录级别,
#内存缓存空间大小为200M,自动清除超过1天没有被访问过的缓存数据,硬盘缓存空间大小20g
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=20r/s;
# 限流异常:503 Service Temporarily Unavailable
# 1)limit_req_zone定义在http块中,$binary_remote_addr表示保存客户端IP地址的二进制形式作为key。
# 2)Zone定义IP状态及URL访问频率的共享内存区域。zone=keyword标识区域的名字,以及冒号后面跟区域大小。16000个IP地址的状态信息约1MB,所以示例中区域可以存储160000个IP地址。
# 3)Rate定义最大请求速率。示例中速率不能超过每秒10个请求。
# 4)burst排队大小,瞬时大流量会放入队列。nodelay表示队列中请求并行执行。默认为串行执行
# lua的本地缓存空间。自定义名:my_cache
lua_shared_dict my_cache 128m;
upstream mysvr {
server 127.0.0.1:18083 weight=1 max_fails=2 fail_timeout=10s;
}
server {
#监听端口
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
#图片缓存时间设置
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{ #浏览器缓存10天
expires 10d;
}
#JS和CSS缓存时间设置
location ~ .*\.(js|css)?$
{ #浏览器缓存1小时
expires 1h;
}
#所有静态文件由nginx直接读取,不经过tomcat或resin
# location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$
# { expires 15d; }
# location ~ .*.(js|css)?$
# { expires 1h; }
#设定查看Nginx状态的地址
location /NginxStatus {
stub_status on;
access_log on;
auth_basic "NginxStatus";
auth_basic_user_file conf/htpasswd;
#htpasswd文件的内容可以用apache提供的htpasswd工具来产生。
}
location /a {
proxy_pass http://192.168.1.55:8788;
#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
}
location /b {
proxy_pass http://192.168.1.55:8787;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
}
location /luacache {
#IP限流
limit_req zone=mylimit burst=10 nodelay;
# lua本地缓存实现。测试路径http://localhost/luacache?key=123
# 在不限流情况下压测对比 /luacache 和 /api。有缓存的/luacache性能提升10%以上,如果/api是java服务器则性能提升更巨大。
content_by_lua_file lua/luacache.lua;
}
location /api {
#lua模拟后端rest_api返回json。
content_by_lua_file lua/api.lua;
}
location /redis {
#限流
limit_req zone=mylimit burst=10 nodelay;
#为每个请求执行redis.lua脚本(相对路径)
content_by_lua_file lua/redis.lua;
}
#只有跳转过来的请求,才会享受location中的配置,比如缓存、超时等
location @url1 {
proxy_pass http://mysvr;
proxy_cache cache_one; #此处的cache_one必须于上一步配置的缓存区域名称相同
proxy_cache_methods GET; #缓存GET请求的响应数据(html、json...)
proxy_cache_valid 200 304 2m; #1d=1天 1h=1小时 1m=1分钟
proxy_cache_valid 301 302 1d;
proxy_cache_valid any 1m;
#不同的请求设置不同的缓存时效
proxy_cache_key $host$uri$is_args$args;
#缓存的key
proxy_connect_timeout 10;#跟后端服务器连接的超时时间_发起握手等候响应超时时间 (秒)
proxy_read_timeout 10;#连接成功后_等候后端服务器响应的时间_其实已经进入后端的排队之中等候处理(秒)
proxy_send_timeout 500;#后端服务器数据回传时间_就是在规定时间内后端服务器必须传完所有数据(秒)
add_header X-Cache-Status $upstream_cache_status; #在响应头查看缓存命中状态 X-Cache-Status
# ·MISS 未命中,请求被传送到后端
# ·HIT 缓存命中
# ·EXPIRED 缓存已经过期请求被传送到后端
# ·UPDATING 正在更新缓存,将使用旧的应答
# ·STALE 后端将得到过期的应答
#向后端服务转发内容
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
}
#需要安装ngx_cache_purge插件
#location ~ /purge(/.*) {
# #删除指定缓存区域cache_one的特定缓存文件$1$is_args$args
# proxy_cache_purge cache_one $host$1$is_args$args;
# #运行本机和10.0.217.0网段的机器访问,拒绝其它所有
# allow 127.0.0.1;
# allow 10.0.217.0/24;
# deny all;
#}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
\openresty-1.15.8.2-win64\lua\redis.lua
--获取客户端ip
local function get_client_ip()
local headers=ngx.req.get_headers() --请求头
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
return ip
end
--连接池注意:复用连接,减少创建TCP链接的消耗。
--测试:不使用连接池,直接close。一定概率响应慢或超时现象,应是创建连接导致。
--1、连接池是每Worker进程的,而不是每Server的;
--2、当连接超过最大连接池大小时,会按照LRU算法回收空闲连接为新连接使用;
--3、连接池中的空闲连接出现异常时会自动被移除;
--4、连接池是通过ip和port标识的,即相同的ip和port会使用同一个连接池(即使是不同类型的客户端如Redis、Memcached);
--5、第一次set_keepalive时连接池大小就确定下了,不会再变更;
local function close_redis(red)
if not red then
return
end
--local ok, err = red:close()
--释放连接回连接池(设置 空闲链接存活毫秒 + 连接池大小)
local ok, err = red:set_keepalive(10000, 20)--设置空闲连接超时时间防止连接一直占用不释放
if not ok then
ngx.say("set keepalive error : ", err)
end
end
local clientIP = get_client_ip();
--local cjson = require("cjson") -- 引入json模块
--require引用openresty\lualib目录下的lua库
local redis=require "resty.redis";
local red=redis:new();
red:set_timeout(500);
--redis连接
local ok,err=red:connect("39.106.1.1", 6379);
if not ok then
ngx.say("failed to connect redis ",err);
--ngx.log(ngx.WARN, "failed to connect redis");
close_redis(red);
return ngx.exec("@url1");--链接redis失败,nginx跳转到 location @url1
end
ok,err = red:auth('xxxxxxx');
if not ok then
ngx.say("failed to authenticate: ", err)
return close_redis(red);
end
red:select('0');
--redis中获取缓存(red:后对应redis指令)
local message, err=red:get(clientIP);
if not message or message == ngx.null then --message ~= ngx.null
ok, err = red:set(clientIP, "context");
ok, err = red:expire(clientIP, 10);
if not ok then
ngx.say("failed to set cache: ", err);
--ngx.log(ngx.WARN, "failed to set cache");
return close_redis(red);
end
--nginx跳转到 location @url1
ngx.exec("@url1");
else
--注意default_type设置类型,部分类型会使say响应变为文件下载
--命中缓存响应
ngx.say("msg: ",message);
end
--ngx.redirect("http://www.elong.com") --302
ok,err=close_redis(red);
\openresty-1.15.8.2-win64\lua\api.lua
local cjson = require "cjson"
local headers=ngx.req.get_headers() --请求头
local info=headers["info"]
--取得URL中参数
local urlarg = ngx.req.get_uri_args()
--获取url请求参数key的值,等效arg["key"]
local key =urlarg.key
--若是body=nil ,则前边加上ngx.req.read_body(),当前body可读
ngx.req.read_body()
local body = ngx.req.get_body_data()
if nil == body then
--解决大body(16k)读不到的问题
local body_file = ngx.req.get_body_file()
if body_file then
body = read_from_file(body_file)
end
end
local res={}
res['code']=200
res['msg']='ok'
res['req_body']=body
--模拟后端restapi返回json
ngx.say(cjson.encode(res))
\openresty-1.15.8.2-win64\lua\luacache.lua
--【nginx.conf 里面配置 lua_shared_dict my_cache 128m;】
--API说明: http://www.hangdaowangluo.com/archives/2762
--shared_dict—api:都保证原子性
--head获取客户端ip
local function get_client_ip()
local headers=ngx.req.get_headers() --请求头
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
return ip
end
function get_from_cache(key)
local cache_ngx = ngx.shared.my_cache
local value = cache_ngx:get(key)
return value
end
function set_to_cache(key, value, exptime)
if not exptime then
exptime = 0
end
local cache_ngx = ngx.shared.my_cache
local succ, err, forcible = cache_ngx:set(key, value, exptime)
return succ
end
function add_to_cache(key, value, exptime)
--exptime过期时间s
if not exptime then
exptime = 0
end
local cache_ngx = ngx.shared.my_cache
local success, err, forcible = cache_ngx:add(key, value, exptime)
return success
--success, err, forcible = ngx.shared.DICT:set(key, value, exptime?, flags?)
--与【set】方法区别在于不会插入重复的键,如果待插入的key已经存在,将会返回nil和和err=”exists”
--API保证原子性
--参数
--参数value:可设置 booleans, numbers, strings, 或 nil;
--可选参数exptime:表明key的有效期时间,单位是秒(s),默认值为0,表明永远不会过期;
--可选参数flags:是一个用户标志值,会在调用get方法时同时获取得到。
--返回值
--success:成功插入为true,插入失败为false
--err:操作失败时的错误信息,可能类似add"no memory"
--forcible:true表明需要通过强制删除(LRU算法)共享内存上其他字典项来实现插入,false表明没有删除
--共享内存上的字典项来实现插入。
end
local cjson = require("cjson") -- 引入json模块
--local request_uri = ngx.var.request_uri --带参数uri
--不带参数uri
local uri = ngx.var.uri
--取得URL中参数
local urlarg = ngx.req.get_uri_args()
--获取url请求参数key的值,urlarg.key等效urlarg["key"]
local key =urlarg.key
--ngx.req.read_body()=设置当前body可读
ngx.req.read_body()
local body = ngx.req.get_body_data()
if nil == body then
--解决大body(16k)读不到的问题
local body_file = ngx.req.get_body_file()
if body_file then
body = read_from_file(body_file)
end
end
--查lua缓存
local value =get_from_cache(key)
local success = false
if not value or value == ngx.null then
--子请求跳转nginx location /api。默认是GET,默认会转发head。
local resp = ngx.location.capture("/api", {method=ngx.HTTP_POST,body = body,args=urlarg})
ngx.say(resp.body,"miss-cache")
--先响应用户,再缓存
ngx.eof()
if resp.status==200 then
--不存在则放入缓存 ,相当于redis setnx
success = add_to_cache(key,resp.body,10)
end
else
--命中缓存
ngx.say(value,"hit")
end