高性能web网关Openresty实践

news2024/11/15 21:31:04

高性能web网关之openresty

  • 一、openresty 简介
  • 二、openresty 安装
  • 三、openresty开发实践 —— content_by_lua 阶段
  • 四、openresty开发实践 —— rewrite_by_lua 阶段
  • 五、openresty开发实践 —— body_filter_by_lua 阶段
  • 六、openresty开发实践 —— 黑名单
    • 6.1、基础版
    • 6.2、进阶版
    • 6.3、高阶版
  • 七、openresty开发实践 —— 反向代理
  • 总结
  • 后言

一、openresty 简介

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

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

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

二、openresty 安装

(1)下载源压缩包:

wget https://openresty.org/download/openresty-1.21.4.1.tar.gz

(2)安装依赖:

sudo apt-get install libpcre3-dev libssl-dev perl make build-essential curl

(3)解压源码:

tar -xzvf openresty-1.21.4.1.tar.gz

(4)配置:默认, --prefix=/usr/local/openresty 程序会被安装到/usr/local/openresty目录。

cd openresty-1.21.4.1
./configure

(5)编译和安装:

make -j2
sudo make install

(6)设置环境:

cd ~
export PATH=/usr/local/openresty/bin:$PATH

(7)测试:

~$ openresty -v
nginx version: openresty/1.21.4.1

启动、关闭、重启 openresty:

# 指定配置启动 openresty
# 需要指定工作目录,示例中的 . 表示当前目录为工作目录。
openresty -p . -c conf/nginx.conf
# 优雅退出
openresty -p . -s quit
# 重启 openresty
openresty -p . -s reload

三、openresty开发实践 —— content_by_lua 阶段

(1)新建一个项目文件夹,项目文件夹新建三个子文件夹,分别是app、conf、logs,分别用来存放编写的应用程序、配置文件、日志文件。

mkdir my_openresty
cd my_openresty
mkdir app
mkdir conf
mkdir logs

(2)在conf下创建nginx.conf文件,输入以下内容:

worker_processes 2;
events {
    worker_connections 10240;
}
#######################
# 以下为配置块
#######################

# http 协议
http {
    #虚拟主机
    server {
        listen 8989;
        # 处理http请求
        # 捕获和处理
        location / {
            # 内容处理,在配置中写代码
            content_by_lua_block {
                ngx.say("hello","\t",ngx.var.remote_addr)
            }
        }
    }
}

#
# tcp 使用stream
#
# stream{
#    
#}

(3)openresty启动nginx:

openresty -p . -c conf/nginx.conf

(4)查看nginx启动状态:

ps aux | grep nginx

执行结果:

fly       15341  0.0  0.0  33264  1272 ?        Ss   17:23   0:00 nginx: master process openresty -p . -c conf/nginx.conf
fly       15342  0.0  0.3  37516  7276 ?        S    17:23   0:00 nginx: worker process
fly       15343  0.0  0.3  37516  7276 ?        S    17:23   0:00 nginx: worker process
fly       15345  0.0  0.0  15984   968 pts/2    S+   17:23   0:00 grep --color=auto nginx

(5)在浏览器输入服务器IP和端口,可以看到如下的结果:
openresty_test_result

四、openresty开发实践 —— rewrite_by_lua 阶段

nginx.conf文件,输入以下内容:

worker_processes 2;
events {
    worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################

# http 协议
http {
    #虚拟主机
    server {
        listen 8989;
        # 处理http请求
        # 捕获和处理
        location / {
            # 初始化数据,可以在此阶段加载耗时模块、设置全局变量
            # init_by_lua_block {
            #     gloabl_a=100
            # }

            # 用于执行内部url重写或外部重定向
            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)
            }
        }
        #  rewrite_by_lua不止能跳转到外部,也可以内部跳转
        location /jump_here {
            # 内容处理,在配置中写代码
            content_by_lua_block {
                ngx.say("hello, jump_here","\t",ngx.var.remote_addr)
            }
        }
    }
}

#
# tcp 使用stream
#
# stream{
#    
#}

没有启动openresty,则输入如下命令启动:

openresty -p . -c conf/nginx.conf

如果是之前已经启动了,只需要reload即可:

openresty -p . -s reload

五、openresty开发实践 —— body_filter_by_lua 阶段

nginx.conf文件,输入以下内容:

worker_processes 2;
events {
    worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################

# http 协议
http {
    #虚拟主机
    server {
        listen 8989;
        # 处理http请求
        # 捕获和处理
        location / {
            # 用于执行内部url重写或外部重定向
            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)
            }
        }
        #  rewrite_by_lua不止能跳转到外部,也可以内部跳转
        location /jump_here {
            # 内容处理,在配置中写代码
            content_by_lua_block {
                ngx.say("hello, jump_here","\t",ngx.var.remote_addr)
            }
            # 用于修改应答body的内容
            body_filter_by_lua_block {
                local chunk=ngx.arg[1]
                ngx.arg[1]=chunk:gsub("hello","FLY.")
            }
        }
    }
}

#
# tcp 使用stream
#
# stream{
#    
#}

执行效果:
body_filter

六、openresty开发实践 —— 黑名单

黑名单功能一般在access_by_lua阶段实现,实现访问控制。

6.1、基础版

新建nginx_new.conf文件,输入以下内容:

worker_processes 2;
events {
    worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################

# http 协议
http {
    #虚拟主机
    server {
        listen 8989;
        location / {
            access_by_lua_block {
                local block_list={
                    ["192.168.0.105"]=true
                }
                if block_list[ngx.var.remote_addr] then
                    return ngx.exit(403)
                end
            }
            
             # 内容处理,在配置中写代码
            content_by_lua_block {
                ngx.say("hello","\t",ngx.var.remote_addr)
            }
        }
    }
}

没有启动openresty,则输入如下命令启动:

openresty -p . -c conf/nginx_new.conf

如果是之前已经启动了,只需要reload即可:

openresty -p . -s reload

执行效果:
forbidden

注意,示例中的IP是写死在代码中的,在实际使用中不会这样来,一般存储在其他地方,比如redis。

6.2、进阶版

修改nginx_new.conf文件内容:

worker_processes 2;
events {
    worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################

# http 协议
http {
    #虚拟主机
    server {
        listen 8989;
        location / {
            # 用于访问控制
            access_by_lua_block {
                local block_list={
                    ["192.168.0.105"]=true
                }
                if block_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)
            }
        }
    }
}

black_v1.lua文件内容为:

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

只需要reload即可:

openresty -p . -s reload

注意,要记得先运行redis,同时添加IP地址到KEY中。

127.0.0.1:6379> SADD black_list 192.168.0.105
(integer) 1
127.0.0.1:6379> keys *
1) "black_list"
127.0.0.1:6379> SMEMBERS black_list
1) "192.168.0.105"
127.0.0.1:6379> 

执行效果:
redis_black_list
如果不知道有哪些接口可以使用,可以通过如下命令查询:

restydoc resty.redis

虽然把IP存储在了redis中,没有写死在代码里,但是每一次请求都要访问redis,这会导致整个系统的吞吐量降低;可以将这些数据写到共享内存中。

6.3、高阶版

redis+共享内存方式。而且为了保证数据有效性,需要定期将redis中的数据拉取到共享内存中;那么就需要在init_worker_by_lua阶段添加定时器。

修改nginx_new.conf文件内容:

worker_processes 2;
events {
    worker_connections 10240;
}
#######################
# 以下为nginx配置块
#######################

# http 协议
http {
    # 创建共享内存
    lua_shared_dict bklist 1m;
    # 初始化数据,定时器
    init_worker_by_lua_file ./app/init_worker.lua;
    
    #虚拟主机
    server {
        listen 8989;
        location / {
            # 用于访问控制
            access_by_lua_block {
                local block_list={
                    ["192.168.0.105"]=true
                }
                if block_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_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文件内容为:

-- 只需要一个进程拉取数据即可。
if ngx.worker.id() ~=0 then
    return
end

-- 获取共享内存
local bklist =ngx.shared.bklist

local redis=require "resty.redis"


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)

只需要reload即可:

openresty -p . -s reload

注意,要记得先运行redis,同时添加IP地址到KEY中。

127.0.0.1:6379> SADD black_list 192.168.0.105
(integer) 1
127.0.0.1:6379> keys *
1) "black_list"
127.0.0.1:6379> SMEMBERS black_list
1) "192.168.0.105"
127.0.0.1:6379> 

执行效果:
black_list_v2

七、openresty开发实践 —— 反向代理

nginx.conf文件内容:


worker_processes 4;

events {
    worker_connections 10240;
}

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;
    }
}

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

没有启动openresty,则输入如下命令启动:

openresty -p . -c conf/nginx.conf

如果是之前已经启动了,只需要reload即可:

openresty -p . -s reload

总结

  1. nginx基础上嵌入lua产生openresty,在openresty基础上封装一个框架产生kong。直接在nginx上开发是比较复杂的,需要熟悉nginx的配置、nginx数据结构、nginx模块构建等,很难进行业务开发;nginx基础上嵌入lua产生的openresty可以进行业务的开发;openresty基础上封装一个框架并添加一些分布式特性就产生kong,即分布式api网关。

  2. openresty中引用了luajit,性能上接近c语言。

  3. nginx是多进程架构,master进程会fork出n个(CPU核心数)worker进程;worker进程间通过共享内存通信;worker进程负责处理请求,通过锁解决惊群问题和控制新的连接接入(负载均衡);请求沿着责任链进行处理。

  4. openresty中的lua是嵌入在责任链的最后一层,即openresty是对nginx功能的补充,不会影响原来在nginx上实现的功能。

  5. 责任链的打断:ngx.exit()或者ngx.redirect()可以打断责任链。restydoc -s func() 可以查看命令查看接口描述。

  6. nginx中直接反向代理可能造成后端服务器(upstream)无法获取到客户端的IP(客户端IP丢失),这是比较严重的问题,我们需要再转发的时候也要把IP地址转发到后端服务器(使用 proxy protocol v1 或 proxy protocol v2)。

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

后言

本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux系统提升感兴趣的读者,可以点击链接查看详细的服务:C/C++服务器开发 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/129432.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【面试题】面试官:为什么Promise中的错误不能被try/catch?

大厂面试题分享 面试题库 前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 前言 之前我写过一篇文章&#xff0c;讨论了为什么async await中的错误可以被try catch&#xff0c;而setTimeout等api不能&#xff0c…

【pandas】教程:7-调整表格数据的布局

Pandas 调整表格的布局 本节使用的数据为 data/titanic.csv&#xff0c;链接为 pandas案例和教程所使用的数据-机器学习文档类资源-CSDN文库 导入数据 import pandas as pd titanic pd.read_csv("data/titanic.csv") titanic.head()PassengerId Survived Pclas…

传感器工作原理以及传感器种类详解

随着物联网时代的到来&#xff0c;现代信息技术快速发展&#xff0c;其中包含了计算机技术、通信技术和传感器技术等&#xff0c;计算机相当于人类的大脑&#xff0c;通信技术类似人体的神经&#xff0c;而传感器就等同于人的感觉器官。从广义上说&#xff0c;传感器就是一种能…

存储控制器

存储控制器是按照一定的时序规则对存储器的访问进行必要控制的设备&#xff0c;包括地址信号、数据信号以及各种命令信号的控制&#xff0c;使主设备(访问存储器的设备)能够根据自己的要求使用存储器上的存储资源。 存储控制器的作用主要就是进行接口的转换&#xff0c;将主设…

通信原理 | 一些常用的概念记录

这篇文章只是记录平时了解到的一些概念,并没有针对性,比较杂乱,纯粹就是当做笔记本用的,各位看官请在茶余饭后的休闲时间阅读最为合适了解到新的概念的话,会随时更新世界四大导航系统 世界上有四大卫星导航系统,它们分别是美国的GPS、俄罗斯的格洛纳斯卫星导航系统、欧盟…

尚医通-SpringBoot整合MongoDB(十七)

目录&#xff1a; &#xff08;1&#xff09;MongDB-SpringBoot整合-MongoTemplate操作 &#xff08;2&#xff09;MongoTemplate操作2 &#xff08;3&#xff09;MongoTemplate操作3 &#xff08;4&#xff09;MongoDB-SpringBoot整合-MongoRepository操作 &#xff08;1&a…

FIIL、南卡、漫步者蓝牙耳机怎么选?国产半入耳蓝牙耳机推荐

随着 TWS耳机市场的发展&#xff0c;越来越多的手机厂商&#xff0c;新晋的品牌&#xff0c;甚至是老牌的音频品牌都加入到了 TWS耳机的行列中&#xff0c;让消费者的选择范围变得更大。当前热销的南卡小音舱、漫步者Lolli3、FIIL CC2蓝牙耳机都是目前受消费者欢迎的&#xff0…

pyqt 显示图片的若干方法

date: 2022-11-30 14:23 status: public title: ‘pyqt 显示图片的若干方法’ 单张图片 使用lable 显示图片 特点是最简单&#xff0c;但功能也最少。 #!/usr/bin/env python # -*- coding: utf-8 -*- import sysfrom PyQt5.QtGui import QPixmap from PyQt5.QtWidgets impor…

JAVAGUI编程初识之AWT

文章目录前言一 GUI编程简介二 AWT简介2.1 组件(Component)和容器(Container)2.2 Frame2.2.1 演示1-创建一个窗口2.2.2 演示2-多个窗口的创建2.3 Panel2.3.1 演示-Panel使用三 布局管理3.1 布局管理器之FlowLayout3.1.1 FlowLayout简介3.1.2 演示-FlowLayout使用3.2 布局管理器…

电子签章结构以及规范讲解

前言&#xff1a; 安全电子签章是通过采用PKI公钥密码技术&#xff0c;将数字图像处理技术与电子签名技术进行结合&#xff0c;以电子形式对加盖印章图像数据的电子文档进行数字签名&#xff0c;以确保文档来源的真实性以及文档的完整性&#xff0c;防止对文档未经授权的篡改&…

算法训练 —— 链表(1)

目录 1. LeetCode203.移除链表元素 2. LeetCode21.合并两个有序链表 3. LeetCode206.翻转链表 4. LeetCode707.设计链表 1. LeetCode203.移除链表元素 移除链表元素 题解&#xff1a;通过两个指针来控制&#xff0c;cur和prev&#xff1b;cur指针去找val&#xff0c;prev…

冰冰学习笔记:位图与布隆过滤器

欢迎各位大佬光临本文章&#xff01;&#xff01;&#xff01; 还请各位大佬提出宝贵的意见&#xff0c;如发现文章错误请联系冰冰&#xff0c;冰冰一定会虚心接受&#xff0c;及时改正。 本系列文章为冰冰学习编程的学习笔记&#xff0c;如果对您也有帮助&#xff0c;还请各位…

MySQL复制技术方案——半同步复制配置

Google为MySQL和InnoDB设计了一个大规模补丁集以量身打造服务器和存储引擎。其中一个修补程序可用于MySQL5.0版本&#xff0c;是半同步的复制补丁。MySQL已经打上了该补丁并在MySQL5.5中发布了。 半同步复制的理念是在允许更改操作继续执行前&#xff0c;确保更改操作至少被写…

34、基于STM32的电子时钟设计(DS1302)时钟、秒表、倒计时(Proteus仿真+程序)

编号&#xff1a;34 基于STM32的电子时钟设计&#xff08;DS1302&#xff09;时钟、秒表、倒计时 功能描述&#xff1a; 本系统由STM32F103系统LCD1602液晶显示按键模块DS1302时钟模块声光报警模块组成。 1、使用LCD1602显示当前日期、时间、星期 2、具有闹钟、倒计时、计时功…

【Java寒假打卡】Java基础-抽象类

【Java寒假打卡】Java基础-抽象类一、概述二、抽象类注意事项三、模板设计模式四、final关键字五、代码块一、概述 抽象方法&#xff1a;将共性的方法抽取到父类之后&#xff0c;发现该方法的实现逻辑无法在父类中给出具体明确&#xff0c;该方法就可定义为抽象方法抽象类&…

【C++初阶8-vector】熟悉的ta

前言 本期看看这位熟悉又陌生的朋友——vector。 博主水平有限&#xff0c;不足之处望请斧正&#xff01; 是什么 vecotr是序列容器&#xff0c;可变大小的数组。 *vector有矢量、向量的意思&#xff0c;用其命名可能想强调“序列”这个概念。 class template std::vecto…

独占指针 std::unique_ptr

学习智能指针之前需要知道的&#xff1a; 智能指针是原始指针的封装&#xff0c;在头文件<memory>中&#xff0c;优点就是自动分配内存&#xff0c;不用担心潜在的内存泄漏。不是所有的指针都可以封装成智能指针&#xff0c;很多时候原始指针更方便。各指针中&#xff0…

Webpack中常见的Loader?解决了什么问题?

一、是什么 loader 用于对模块的源代码进行转换&#xff0c;在 import 或"加载"模块时预处理文件 webpack做的事情&#xff0c;仅仅是分析出各种模块的依赖关系&#xff0c;然后形成资源列表&#xff0c;最终打包生成到指定的文件中。如下图所示&#xff1a; 在web…

【网络安全】——web渗透的前缀知识

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

ArcGIS基础实验操作100例--实验18合并表格

本实验专栏来自于汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验18 合并表格 目录 一、实验背景 二、实验数据 三、实验步骤 方法一&#xff1a;导出…