openresty学习笔记,用于方便搭建能够处理


openresty 简介

openresty 是一个基于 nginx 与 lua 的高性能 web 平台,其内部 集成了大量精良的 lua 库、第三方模块以及大数的依赖项。用于 方便搭建能够处理超高并发、扩展性极高的动态 web 应用、 web 服务和动态网关。

openresty 通过汇聚各种设计精良的 nginx 模块,从而将 nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人 员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单 机并发连接的高性能 Web 应用系统。

openresty 的目标是让你的 Web 服务直接跑在 Nginx 服务内部, 充分利用 Nginx 的非阻塞 I/O 模型(多reactor 模型),不仅仅 对 HTTP 客户端请求(stream),甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis etcd kafka grpc 等都进行一致的高性能响应(upstream)。

openresty 安装

官网: http://openresty.org/cn/

下载页面:http://openresty.org/cn/download.html 

# 安装依赖
apt-get install libpcre3-dev \
   libssl-dev perl make build-essential curl
# 解压源码
tar -xzvf openresty-VERSION.tar.gz
# 配置:默认, --prefix=/usr/local/openresty 程序会被
安装到/usr/local/openresty目录。
./configure
make -j2
sudo make install
cd ~
export PATH=/usr/local/openresty/bin:$PATH

启动、关闭、重启 openresty

# 指定配置启动 openresty
openresty -p . -c conf/nginx.conf
# 优雅退出
openresty -p . -s quit
# 重启 openresty
openresty -p . -s reload

openresty 应用场景

奇虎360的所有服务端团队都在使用,京东、百度、魅族、知 乎、优酷、新浪这些互联网公司都在使用。有用来写 WAF (web application firewall)、有做 CDN 调度、有做广告系统、消息推送系统,API server 的。还有用在非常关键的业务上,比如高可用架构分享的京东商品详情页。

1 在请求真正到达上游服务之前,Lua 可以随心所欲的做复杂的访问控制和安全检测

2 随心所欲的操控响应头里面的信息

3 从外部存储服务(比如 Redis,Memcached,MySQL, Postgres)中获取后端信息,并用这些信息来实时选择哪一个后端来完成业务访问

4 在内容 handler 中随意编写复杂的 Web 应用,使用同步但依然非阻塞的方式,访问后端数据库和其他存储

5 在 rewrite 阶段,通过 Lua 完成非常复杂的 URL dispatch

6 用 Lua 可以为 nginx 子请求和任意 location,实现高级缓存机制

lua-nginx-module

nginx 采用模块化设计,使得每一个 http 模块可以仅专注于完 成一个独立的、简单的功能,而一个请求的完整处理过程可以 由无数个 http 模块共同合作完成。为了灵活有效地指定下一个 http 处理模块是哪一个;http 框架依据常见的的处理流程将处 理阶段划分为 11 个阶段,其中每一个阶段都可以由任意多个 http 模块流水式地处理请求。

openresty 将 lua 脚本嵌入到 nginx 阶段处理的末尾模块下;这样以来并不会影响 nginx 原有的功能,而是在 nginx 基础上丰富它的功能;

嵌入 lua 的优点是:使用 openresty 开发,不需要重新编译, 直接修改 lua 脚本,重新启动即可;

lua 模块指令顺序

问题:访问某个页面,先验证是否用户权限是否合法,否则跳到用户验证界面;

问题:黑白名单在哪个阶段实现?

init_by_lua

在 nginx 重新加载配置文件时,运行里面 lua 脚本,常用于 全局变量的申请。例如 lua_shared_dict 共享内存的申请,只 有当 nginx 重启后,共享内存数据才清空,这常用于统计。

set_by_lua

设置一个变量,常用与计算一个逻辑,然后返回结果,该阶 段不能运行Output API、Control API、Subrequest API、 Cosocket API

rewrite_by_lua

在 access 阶段前运行,主要用于 rewrite url;

access_by_lua

主要用于访问控制,这条指令运行于 nginx access 阶段的末 尾,因此总是在 allow 和 deny 这样的指令之后运行,它们 同属 access 阶段。可用来判断请求是否具备访问权限;

content_by_lua

阶段是所有请求处理阶段中最为重要的一个,运行在这个阶 段的配置指令一般都肩负着生成内容(content)并输出 HTTP 响应。

header_filter_by_lua

一般只用于设置 Cookie 和 Headers 等。

body_filter_by_lua

一般会在一次请求中被调用多次,因为这是实现基于 HTTP 1.1 chunked 编码的所谓“流式输出”的。

log_by_lua

该阶段总是运行在请求结束的时候,用于请求的后续操作, 如在共享内存中进行统计数据,如果要高精确的数据统计, 应该使用 body_filter_by_lua

嵌入原理

openresty 是在nginx处理的阶段末尾加上我们的方法,补充功能,原来实现的功能不受影响。

责任链模式

ngx.exit(status) 

如果status == 0 只打断当前责任链,如果status>=200 则打断整个责任链,直接退出。

ngx.redirect()

cosocket

openresty 为 nginx 添加的最核心的功能就是 cosocket;自 cosocket 加入,可以在 http 请求处理中访问第三方服务; cosocket 主要依据 nginx 中的事件机制和 lua 的协程结合后实现了非阻塞网络 io;在业务逻辑使用层面上可以通过同步非阻塞的方式来写代码;

引入 cosocket 后,nginx 中相当于有了多条并行同步逻辑线 (lua 协程),nginx 中单线程负责唤醒或让出其中 lua 协程; 唤醒或让出依据来源于协程运行的条件是否得到满足;

问题:比较 openresty 、skynet、zvnet 的 lua 虚拟机抽象和 lua 协程抽象?

黑名单用户 nginx.conf

worker_processes 8;

events {
    worker_connections 10240;
}

http {
    lua_shared_dict bklist 1m;
    init_worker_by_lua_file ./app/init_worker.lua;
    server {
        listen 9000;

        location / {
            access_by_lua_block {
                local black_list = {
                    ["192.168.44.1"] = 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 {
            access_by_lua_file ./app/black_v1.lua;

            content_by_lua_block {
                ngx.say("hello" , "\t" , ngx.var.remote_addr)
            }
        }

        location /black_v2 {
            access_by_lua_file ./app/black_v2.lua;

            content_by_lua_block {
                ngx.say("hello" , "\t" , ngx.var.remote_addr)
            }
        }
    }
}

black_v1.lua  (将黑名单ip放入redis)

local redis = require "resty.redis"

local red = redis:new()

local ok , err = red:connect("127.0.0.1" , 6379)

if not ok then 
    return ngx.exit(301)
end

local ip = ngx.var.remote_addr

local exists , err = red:sismember("black_list" , ip)

if exists == 1 then
    return ngx.exit(403)
end

black_v2.lua   

local bklist = ngx.shared.bklist

local ip = ngx.var.remote_addr

if bklist:get(ip) then 
    return ngx.exit(403)
end

init_worker.lua   一个worker定时往共享内存中刷数据,(黑名单ip)

if ngx.worker.id() ~= 0 then
    return
end

local redis = require "resty.redis"
local bklist = ngx.shared.bklist


local function update_blacklist()
    local red = redis:new()
    local ok , err = red:connect("127.0.0.1" , 6379)
    if not ok then 
        return
    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)

反向代理

worker_processes 8;

events {
    worker_connections 10240;
}

# http
http {
    server {
        listen 8989;
        location / {
            rewrite_by_lua_block {
                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_here")
                end
            }
            content_by_lua_block {
                ngx.say("hello", "\t", ngx.var.remote_addr)
            }
        }
        location /jump_here {
            content_by_lua_block {
                ngx.say("jump_here hello", "\t", ngx.var.remote_addr)
            }
            body_filter_by_lua_block {
                local chunk = ngx.arg[1]
                ngx.arg[1] = chunk:gsub("hello", "mark")
            }
        }
    }
}

stream {
    upstream ups {
        server 127.0.0.1:8888;
    }
    server {
        listen 9999;
        proxy_pass ups;
        proxy_protocol on;
    }

    server {
        listen 9000;
        content_by_lua_file ./app/proxy.lua;
    }
}

cosocket   proxy.lua

local sock, err = ngx.req.socket()

if err then
    ngx.log(ngx.INFO, err)
end

local upsock, ok

upsock = ngx.socket.tcp()

ok, err = upsock:connect("127.0.0.1", 8989)
if not ok then
    ngx.log(ngx.INFO, "connect error:"..err)
end

upsock:send(ngx.var.remote_addr .. '\n')

local function handle_upstream()
    local data
    for i=1, 1000 do
        local reader = upsock:receiveuntil("\n", {inclusive = true})
        data, err, _ = reader()
        if err then
            sock:close()
            upsock:close()
            return
        end
        sock:send(data)
    end
end

ngx.thread.spawn(handle_upstream)

local data

while true do
    local reader = sock:receiveuntil("\n", {inclusive = true})
    data, err, _ = reader()
    if err then
        sock:close()
        upsock:close()
        return
    end
    upsock:send(data)
end

相关内容