后端技术_高性能web网关Openresty
最近更新:2024-11-15
|
字数总计:1.9k
|
阅读估时:7分钟
|
阅读量: 次
什么是Openresty nginx架构回顾 责任链模式 nginx复合责任链 责任链配置举例 cosocket 黑名单功能实现 Openresty总结
什么是Openresty
nginx + lua
Openresty扩展了nginx的功能,并没有修改nginx的功能,所以nginx替换成openresty没有任何的副作用
Lua的作用:
openresty在nginx的不同处理阶段嵌入lua脚本,会在不同阶段触发写的lua函数。
(cosocket功能,点睛之笔)利用lua的协程将nginx的异步方法转化成同步的,这样方便我们写代码;而且以往我们只用nginx作为反向代理,有了openresty我们就可以在nginx这一层写我们的代码,做业务开发。
直接在nginx层做业务开发
nginx架构回顾
nginx
一开始nginx只有一个master线程
然后fork出许多worker线程,每个worker线程对应可能有多个server(即虚拟主机,监听端口)
多个worker线程通过accept锁(共享内存,原子变量)来进行监听的接收争抢
lua
一开始master线程中就初始化了lua虚拟机(fork之前会触发init_by_lua,根据写时复制,这期间初始化的lua变量,worker里面都有相同的变量)
在worker线程中(fork之后触发init_worker_by_lua),一个server对应一个lua虚拟机,而且lua虚拟机之间的数据是不互通的
责任链模式
解耦合:请求者和处理者之间的耦合关系,也就是说请求的人不需要关心请求如何被处理,而处理的人不需要关心是怎样的请求,它都会按照既定的规则去处理;
一个请求沿着处理链条进行处理,依次进行传递,直到某个处理过程终止
可以打断链条
nginx复合责任链
nginx 的 http 11个阶段
11个阶段横向链接,构成了一条责任链
不同的阶段下面链接了多个方法(C函数),说明不同的阶段可以用不同的方法处理;这些方法又构成了一条责任链
两次打断:打断某个阶段的函数处理过程,或打断11个阶段中的某个阶段
lua嵌入:
嵌入master进行fork之前
worker建立之后
http的11个阶段:每个阶段的处理函数的责任链的最末尾(即openresty会丰富nginx的功能,但不会改变nginx的功能)
责任链配置举例
nginx.conf1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 # mkdir logs # openresty -p . -c conf/nginx.conf # -p 指定执行目录 # -c 指定配置文件 # openresty -t . -c conf/nginx.conf # -t test 检查配置文件 # openresty -p . -s reload # -s 发送一个signal,只修改了conf,不需要重新编译nginx,直接重启就行 worker_processes 9; # worker线程数 events { worker_connections 10240; # 每个worker的epoll处理的最大连接数 } http { # http请求 error_log ./logs/error.log info; # 设置log目录 server { # 虚拟主机 listen 8989; location / { rewrite_by_lua_block { -- 在rewrite阶段最后面挂一个lua代码 -- XXXXXX?jump=1时跳转到百度 -- 假如在这里进行了执行跳转,那么一个http请求的责任链就被打断了,所以content阶段就不会执行到了 local args = ngx.req.get_uri_args() if args["jump"] == "1" then return ngx.redirect("http://baidu.com") elseif args["jump"] == "2" then return ngx.redirect("/jump_other") end } content_by_lua_block { -- 在content阶段挂一个lua代码,就是在内容生成返回给客户端阶段执行脚本 ngx.say("hello", "\t", ngx.var.remote_addr) } } location /jump_other { content_by_lua_block { -- 这里属于跳转后的另一个http请求的责任链阶段 ngx.say("jump other", "\t", ngx.var.remote_addr) } body_filter_by_lua_block{ -- 在content生成之后,对body内容进行修改阶段 local chunk = ngx.arg[1] -- 将body的other修改为lin ngx.arg[1] = string.gsub(chunk, "other", "lin") } log_by_lua_block { -- 在最后一个阶段挂在统计信息,并输出日志 local request_method = ngx.var.request_method local request_uri = ngx.var.request_uri local status = ngx.var.status local response_time = ngx.var.request_time local msg = string.format("[%s] %s -Status:%d, response time=%.2fms", os.date("%Y-%m-%d %H:%M:%S"), request_uri, status, response_time) ngx.log(ngx.INFO, msg) } } } } stream { # 裸tcp请求 }
可嵌入位置图示
cosocket
通过cosocket机制,实现业务层的同步非阻塞方式;
举例1 2 3 4 5 6 7 8 9 10 11 12 13 14 http { server{ listen 8989; location / { content_by_lua_block { -- 访问redis local redis = require "resty.redis" rdb = redis.new() local teacher = rdb:hget("teacher") -- 注意这里,并没有传递回调function,这是一个同步接口,所以会阻塞在这里,必须要拿到这个teacher才会执行后面的代码。但是说是非阻塞,是因为nginx是单线程的,我们不会阻塞nginx,而是通过阻塞lua协程的方式实现的,所以是同步非阻塞的方式,通过协程的方式将异步的过程转化化成了同步的过程。 } } } }
黑名单功能实现
black.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 # mkdir logs # openresty -p . -c conf/black.conf # -p 指定执行目录 # -c 指定配置文件 # openresty -p . -t -c conf/black.conf # -t test 检查配置文件 # openresty -p . -s reload # -s 发送一个signal,只修改了conf,不需要重新编译nginx,直接重启就行 # openresty -p . -s stop worker_processes 9; # worker线程数 events { worker_connections 10240; # 每个worker的epoll处理的最大连接数 } http { # http请求 error_log ./logs/black.log info; # 设置log目录 lua_shared_dict blk_list 1m; # 设置1m的共享内存空间,用于black_v2过程 # 共享内存什么时候初始化呢? # 1. init_by_lua X (虽然说这样做,只需要初始化一次,worker就能共享操作共享内存的权限,但是master这个进程是还没有epoll的,但是我们想要建立redis连接,需要epoll) # 2. init_worker_by_lua √ init_worker_by_lua_file ./app/init_worker.lua; server { # 虚拟主机 listen 8989; location / { access_by_lua_block { -- 硬编码方式,线上不能用 local black_list = { ["120.241.117.56"] = true } if black_list[ngx.var.remote_addr] then return ngx.exit(403) end } content_by_lua_block { ngx.say("hello", "\t", ngx.var.remote_addr) } } location /black_v1 { # 通过文件方式引入lua脚本 # 实际生产中也不会使用这个版本,因为每次都去建立redis连接 代价太大了 access_by_lua_file ./app/black_v1.lua; content_by_lua_block { ngx.say("black_v1", "\t", ngx.var.remote_addr) } } location /black_v2 { # 基于共享内存,也就是虚拟机外的,更是worker间共享的 access_by_lua_file ./app/black_v2.lua; content_by_lua_block { ngx.say("black_v2", "\t", ngx.var.remote_addr) } } } }
redis
1 2 3 4 5 6 7 8 cd redis_dataredis-server ./redis.conf redis-cli -h 127.0.0.1 > auth 123456 > sadd black_list 127.2.2.4 > sadd black_list 127.2.2.3 > smembers black_list > srem black_list 127.2.2.4
black_v1.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 local redis = require "resty.redis" local rdb = redis:new()local ok, err = rdb:connect("127.0.0.1" , 6379 )if not ok then return ngx.exit (301 ) end local res,err = rdb:auth("123456" )if not res then ngx.log (ngx.ERR, err) return ngx.exit (301 ) end local ip = ngx.var.remote_addrlocal exists, err = rdb:sismember("black_list" , ip)if exists == 1 then return ngx.exit (403 ) else ngx.log (ngx.ERR, err) end
init_worker.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 if ngx.worker.id() ~= 0 then return end local redis = require "resty.redis" local bklist = ngx.shared.blk_listlocal function update_blacklist () local red = redis:new() local ok, err = red:connect("127.0.0.1" , 6379 ) if not ok then return end local res,err = red:auth("123456" ) if not res then ngx.log (ngx.ERR, err) return ngx.exit (301 ) end local black_list, err = red:smembers("black_list" ) bklist:flush_all() for _, v in pairs (black_list) do bklist:set(v, true ) end ngx.timer.at(5 , update_blacklist) end ngx.timer.at(5 , update_blacklist)
black_v2.lua
1 2 3 4 5 6 7 local bklist = ngx.shared.blk_listlocal ip = ngx.var.remote_addrif bklist:get(ip) then return ngx.exit (403 ) end
Openresty总结
背靠nginx,嵌入到各个阶段的lua函数
cosocket可同步非阻塞在多个阶段访问第三方服务,在nginx上实现业务成为可能
ngx.shared.dict共享内存可在多个worker进程共享数据,数据实时生效
2024-11-14
该篇文章被 Cleofwine
归为分类:
服务端