[08] OpenResty
1. OpenResty 概述#
1.1 OpenResty 简介#
OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
OpenResty 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台,这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
OpenResty 的目标是让你的 Web 服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。
1.2 Nginx 的流程定义#
Nginx 实际把 HTTP 请求处理流程划分为了 11 个阶段,这样划分的原因是将请求的执行逻辑细分,各阶段按照处理时机定义了清晰的执行语义,开发者可以很容易分辨自己需要开发的模块应该定义在什么阶段。
阶段 | 简述 | 说明 |
---|---|---|
NGX_HTTP_POST_READ_PHASE | 解析 HTTP 请求头 | 解析 HTTP 请求头,并初始化包含请求信息的 ngx_http_request_t 结构体。 |
NGX_HTTP_SERVER_REWRITE_PHASE | 服务器 URL 重写 | 执行服务器级别的 URL 重写操作,比如删除或添加某个前缀。 |
NGX_HTTP_FIND_CONFIG_PHASE | 查找 server location 的配置 | 根据请求中的 URI 查找对应的 server 和 location 配置。 |
NGX_HTTP_REWRITE_PHASE | location URL 重写 | 执行 location 级别的 URL 重写操作,比如将请求重定向到另一个 URI。 |
NGX_HTTP_POST_REWRITE_PHASE | location URL 重写后再次处理 | 再次处理 URL 重写后的请求,以确保它们符合要求。 |
NGX_HTTP_PREACCESS_PHASE | 访问权限检查 | 检查是否允许客户端访问所请求的资源,即执行 access 模块的 ngx_http_access_handler() 方法。 |
NGX_HTTP_ACCESS_PHASE | 访问权限控制 | 也是访问控制阶段,但它会在请求的 content body 准备好之后执行,以便更准确地判断访问权限。 |
NGX_HTTP_POST_ACCESS_PHASE | 访问权限控制后再次处理 | 再次处理访问控制后的请求,以确保它们符合要求。 |
NGX_HTTP_CONTENT_PHASE | 处理请求内容 | 处理请求资源的内容,比如读取文件并发送响应数据给客户端。 |
NGX_HTTP_LOG_PHASE | 记录日志 | 记录访问日志,包括客户端 IP、请求时间、响应代码等信息。 |
NGX_HTTP_FLUSH_PHASE | 向客户端发送响应数据 | 会向客户端发送响应数据,并清除相关资源。 |
1.3 OpenResty 处理流程#
由于 Nginx 把一个请求分成了很多阶段,第三方模块就可以根据自己的行为,挂载到不同阶段处理达到目的,OpenResty 也应用了同样的特性。
不同的阶段,有不同的处理行为,这是 OpenResty 的一大特色,OpenResty 处理一个请求的流程参考下图。
指令 | 描述 |
---|---|
init_by_lua,init_by_lua_block | 运行在 Nginx loading-config 阶段,注册 Nginx Lua 全局变量,和一些预加载模块。是 Nginx Master 进程在加载 Nginx 配置时执行。 |
init_worker_by_lua | 在 Nginx starting-worker 阶段,即每个 Nginx worker 启动时会调用,通常用来 hook worker 进程,并创建 worker 进行的计时器,用来健康检查,或者设置熔断记时窗口等等。 |
access_by_lua | 在 access tail 阶段,用来对每次请求做访问控制,权限校验等等,能拿到很多相关变量。例如:请求体中的值,header 中的值,可以将值添加到 ngx.ctx,在其他模块进行相应的控制。 |
balancer_by_lua | 通过 Lua 设置不同的负载均衡策略,具体可以参考 lua-resty-balancer。 |
content_by_lua | 在 content 阶段,即 content handler 的角色,即对于每个 API 请求进行处理,注意不能与 proxy_pass 放在同一个 location 下。 |
proxy_pass | 真正发送请求的一部分,通常介于 access_by_lua 和 log_by_lua 之间。 |
header_filter_by_lua | 在 output-header-filter 阶段,通常用来重新响应头部,设置 cookie 等,也可以用来作熔断触发标记。 |
body_filter_by_lua | 对于响应体的 content 进行过滤处理。 |
log_by_lua | 记录日志即,记录一下整个请求的耗时、状态码等。 |
2. OpenResty 安装#
2.1 YUM 安装#
你可以在你的 CentOS 系统中添加 OpenResty 仓库,这样就可以便于未来安装或更新我们的软件包(通过 yum update 命令)。
(1)添加 OpenResty 仓库
$ yum install yum-utils
$ yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
(2)安装 OpenResty
$ yum install openresty
2.2 编译安装#
OpenResty 插件分为自带插件以及第三方插件,如果是自带插件直接激活就可以,如果是第三方插件需要手动下载插件添加进去,这里我们以本地缓存插件安装举例。
(1)安装编译环境
$ yum install -y make cmake gcc gcc-c++ autoconf automake libpng-devel libjpeg-devel zlib libxml2-devel ncurses-devel bison libtool-ltdl-devel libiconv libmcrypt mhash mcrypt pcre-devel openssl-devel freetype-devel libcurl-devel lua-devel readline-devel curl wget
(2)下载最新版源码
$ mkdir /usr/local/openresty && cd /usr/local/openresty
$ wget https://openresty.org/download/openresty-1.21.4.1.tar.gz
$ tar -zxvf openresty-1.21.4.1.tar.gz
(3)下载缓存插件
$ mkdir /usr/local/openresty/modules && cd /usr/local/openresty/modules
$ wget http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gz
$ tar -zxvf ngx_cache_purge-2.3.tar.gz
(4)编译
选择需要的插件启用:–with-Components
激活组件,–without
则是禁止组件,–add-module
是安装第三方模块。
$ ./configure --prefix=/usr/local/openresty --with-luajit --without-http_redis2_module --with-http_stub_status_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module --add-module=/usr/local/openresty/modules/ngx_cache_purge-2.3
这里禁用了 Redis 组件并安装了第三方缓存组件。
(5)安装
$ gmake && gmake install
出现如下界面表示安装成功:
(6)系统环境
$ vim /etc/profile
# ------------------------------------------------
export PATH=$PATH:/usr/local/openresty/nginx/sbin
# ------------------------------------------------
$ source /etc/profile
查看环境和安装的组件:
2.3 简单测试#
(1)修改配置文件 /usr/local/openresty/nginx/conf/nginx.conf
worker_processes 2;
error_log logs/error.log info;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
include conf.d/*.conf;
}
(2)创建配置文件目录
$ mkdir /usr/local/openresty/nginx/conf/conf.d
(3)创建 Nginx 配置文件
server {
server_name www.tree6x7.com;
charset utf-8;
location / {
default_type text/html;
content_by_lua '
ngx.say("<p>Hello, OpenResty!</p>")
';
}
}
(4)启动 OpenResty
$ nginx -c /usr/local/openresty/nginx/conf/nginx.conf
(5)使用 curl 来测试是否能正常访问
[root@centos7 local]# curl http://127.0.0.1
<p>Hello, OpenResty!</p>
3. 常用命令#
命令 | 描述 |
---|---|
ngx.arg | 指令参数,如跟在 content_by_lua_file 后面的参数。 |
ngx.var | 请求变量,ngx.var.VARIABLE 引用某个变量,lua 使用 Nginx 内置的绑定变量。ngx.var.remote_addr 为获取远程的地址。 |
ngx.ctx | 请求的 lua 上下文,每次请求的上下文可以在 ctx 里记录,每次请求上下文的一些信息,如 request_id , access_key 等。 |
ngx.header | 响应头,ngx.header.HEADER 引用某个头 |
ngx.status | 响应码 |
ngx.log | 输出到 error.log |
ngx.send_headers | 发送响应头 |
ngx.headers_sent | 响应头是否已发送 |
ngx.resp.get_headers | 获取响应头 |
ngx.is_subrequest | 当前请求是否是子请求 |
ngx.location.capture | 发布一个子请求 |
ngx.location.capture_multi | 发布多个子请求 |
ngx.print | 输出响应 |
ngx.say | 输出响应,自动添加‘\n‘ |
ngx.flush | 刷新响应 |
ngx.exit | 结束请求 |
4. 商品详情页#
4.1 系统架构#
一、中小型公司
很多中小型电商的商品详情页可能一分钟都没有一个访问,这种的话,就谈不上并发设计,一个 tomcat 就能搞定。
还有一种中小型公司呢?虽然说公司不大,但是也是有几十万日活,然后几百万用户,他们的商品详情用,采取的方案可能是全局的一个静态页面。
就是我们有把商品详情页直接做成一个静态页面,然后这样子每次全量的更新,把数据全部静态放到 Redis 里面,每次数据变化的时候,我们就通过一个 Java 服务去渲染这个数据,然后把这个静态页面推送到到文件服务器。
这种方案的缺点,如果商品很多,那么渲染的时间会很长,达不到实时的效果。文件服务器性能高,Tomcat 性能差,压力都在 Tomcat 服务器了。只能处理一些静态的东西,如果动态数据很多,比如有库存的,你不可能说每次去渲染,然后推送到文件服务器,那不是更加慢?
二、大型公司
(1)生成静态页
添加修改页面的时候生成静态页,这个地方生成的是一个通用的静态页,敏感数据比如价格、商品名称等,通过占位符来替换,然后将生成的静态页的链接,以及敏感数据同步到 Redis 中,如果只修改价格不需要重新生成静态页,只需要修改 Redis 敏感数据即可。
(2)推送到文件服务器
这里的文件服务器泛指能够提供静态文件处理的文件服务器,Nginx 代理静态文件、Tomcat 以及 OSS 等都算静态文件服务器,生成完静态文件后将文件推送到文件服务器,并将请求连接存放进 Redis 中。
(3)布隆过滤器过滤请求
Redis和nginx的速度很快,但是如果有人恶意请求不存在的请求会造成redis很大的开销,那么可以采用布隆过滤器将不存在的请求过滤出去。
(4)Lua 直连 Redis 读取数据
因为 Java 连接 Redis 进行操作并发性能很弱,相对于 OpenResty 来说性能差距很大,这里使用 OpenResty,读取 Redis 中存放的 URL 以及敏感数据。
(5)OpenResty 渲染数据
从 Redis 获取到 URL 后 Lua 脚本抓取模板页面内容,然后通过 Redis 里面的敏感数据进行渲染然后返回前端,因为都是 Lua 脚本操作,性能会很高。
4.2 环境准备#
下面我们就基于这个架构来安装和搭建所需要的环境。
a. 配置文件服务器#
我们的的文件服务器页面在 nginx-server 的代码中可以通过 http://IP:PORT/template.html 访问。
b. 配置资源反向代理#
通过 Nginx 来配置资源服务器。
upstream dynamicserver {
server 10.45.12.70:9002 fail_timeout=60s max_fails=3;
keepalive 256;
}
server {
server_name www.resources.com 127.0.0.1;
default_type text/html;
charset utf-8;
location ~ .*$ {
index index.jsp index.html;
proxy_pass http://dynamicserver;
# 表示重试超时时间是3s
proxy_connect_timeout 30;
proxy_send_timeout 10;
proxy_read_timeout 10;
# 表示在6s内允许重试3次,只要超过其中任意一个设置,Nginx 会结束重试并返回客户端响应
proxy_next_upstream_timeout 60s;
proxy_next_upstream_tries 3;
}
}
在宿主机配置映射:
192.168.6.160 www.resources.com
然后通过访问 www.resources.com/template.html 访问测试:
4.3 安装布隆过滤器#
一、插件形式安装
下载布隆过滤器插件源码,进行解压编译。
$ wget https://github.com/RedisBloom/RedisBloom/archive/v2.2.4.tar.gz
$ tar RedisBloom-2.2.4.tar.gz && cd RedisBloom-2.2.4
$ make
编译得到动态库 redisbloom.so:
启动 Redis 时,如下启动即可加载 bloom filter 插件:
- 配置文件形式配置
# 在Redis配置文件(redis.conf)中加入该模块即可 vim redis.conf # ------------------------------------------------- loadmodule /opt/module/RedisBloom-2.2.4/redisbloom.so # -------------------------------------------------
- 启动命令挂载
# 容量100w, 容错率万分之一, 占用空间是4M $ redis-server redis.conf --loadmodule /opt/module/RedisBloom-2.2.4/redisbloom.so INITIAL_SIZE 1000000 ERROR_RATE 0.0001
二、Docker 安装
$ docker run -d -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
三、布隆过滤器常用命令
命令 | 功能 | 参数 |
---|---|---|
BF.RESERVE | 创建一个大小为capacity,错误率为error_rate的空的Bloom | BF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion] [NONSCALING] |
BF.ADD | 向key指定的Bloom中添加一个元素item | BF.ADD {key} |
BF.MADD | 向key指定的Bloom中添加多个元素 | BF.MADD {key} {item} [item...] |
BF.INSERT | 向key指定的Bloom中添加多个元素,添加时可以指定大小和错误率,且可以控制在Bloom不存在的时候是否自动创建 | BF.INSERT {key} [CAPACITY {cap}] [ERROR {error}] [EXPANSION expansion] [NOCREATE] [NONSCALING] ITEMS |
BF.EXISTS | 检查一个元素是否可能存在于key指定的Bloom中 | BF.EXISTS {key} |
BF.MEXISTS | 同时检查多个元素是否可能存在于key指定的Bloom中 | BF.MEXISTS {key} {item} [item...] |
BF.SCANDUMP | 对Bloom进行增量持久化操作 | BF.SCANDUMP {key} |
BF.LOADCHUNK | 加载SCANDUMP持久化的Bloom数据 | BF.LOADCHUNK {key} {iter} |
BF.INFO | 查询key指定的Bloom的信息 | BF.INFO |
BF.DEBUG | 查看BloomFilter的内部详细信息(如每层的元素个数、错误率等) | BF.DEBUG |
4.4 部署 Redis 集群#
(1)创建目录
$ mkdir -p /opt/module/redis-cluster/
$ mkdir -p /opt/module/redis-cluster/node{1..6}
(2)编写配置文件
$ cat > /opt/module/redis-cluster/redis.conf<< EOF
protected-mode no
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 1
always-show-logo yes
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
rdb-del-sync-files no
dir ./
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-diskless-load disabled
repl-disable-tcp-nodelay no
replica-priority 100
acllog-max-len 128
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
lazyfree-lazy-user-del no
oom-score-adj no
oom-score-adj-values 0 200 800
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
jemalloc-bg-thread yes
loadmodule /etc/redis/redisbloom.so
EOF
(3)配置 docker-compose.yml 文件
version: '2'
services:
redis01:
image: redis
hostname: redis01
container_name: redis01
networks:
docker-network:
ipv4_address: 172.18.0.2
ports:
- "7001:6379"
volumes:
- "/opt/module/redis-cluster/redis.conf:/etc/redis/redis.conf"
- "/opt/module/redis-cluster/data/node1:/data"
- "/opt/module/redis-cluster/redisbloom.so:/etc/redis/redisbloom.so"
command:
redis-server /etc/redis/redis.conf
redis02:
image: redis
hostname: redis02
container_name: redis02
networks:
docker-network:
ipv4_address: 172.18.0.3
ports:
- "7002:6379"
volumes:
- "/opt/module/redis-cluster/redis.conf:/etc/redis/redis.conf"
- "/opt/module/redis-cluster/data/node2:/data"
- "/opt/module/redis-cluster/redisbloom.so:/etc/redis/redisbloom.so"
command:
redis-server /etc/redis/redis.conf
redis03:
image: redis
hostname: redis03
container_name: redis03
networks:
docker-network:
ipv4_address: 172.18.0.4
ports:
- "7003:6379"
volumes:
- "/opt/module/redis-cluster/redis.conf:/etc/redis/redis.conf"
- "/opt/module/redis-cluster/data/node3:/data"
- "/opt/module/redis-cluster/redisbloom.so:/etc/redis/redisbloom.so"
command:
redis-server /etc/redis/redis.conf
redis04:
image: redis
hostname: redis04
container_name: redis04
networks:
docker-network:
ipv4_address: 172.18.0.5
ports:
- "7004:6379"
volumes:
- "/opt/module/redis-cluster/redis.conf:/etc/redis/redis.conf"
- "/opt/module/redis-cluster/data/node4:/data"
- "/opt/module/redis-cluster/redisbloom.so:/etc/redis/redisbloom.so"
command:
redis-server /etc/redis/redis.conf
redis05:
image: redis
hostname: redis05
container_name: redis05
networks:
docker-network:
ipv4_address: 172.18.0.6
ports:
- "7005:6379"
volumes:
- "/opt/module/redis-cluster/redis.conf:/etc/redis/redis.conf"
- "/opt/module/redis-cluster/data/node5:/data"
- "/opt/module/redis-cluster/redisbloom.so:/etc/redis/redisbloom.so"
command:
redis-server /etc/redis/redis.conf
redis06:
image: redis
hostname: redis06
container_name: redis06
networks:
docker-network:
ipv4_address: 172.18.0.7
ports:
- "7006:6379"
volumes:
- "/opt/module/redis-cluster/redis.conf:/etc/redis/redis.conf"
- "/opt/module/redis-cluster/data/node6:/data"
- "/opt/module/redis-cluster/redisbloom.so:/etc/redis/redisbloom.so"
command:
redis-server /etc/redis/redis.conf
networks:
docker-network:
ipam:
config:
- subnet: 172.18.0.0/16
gateway: 172.18.0.1
这里把上边本地编译好的 redisbloom.so 拷贝过来,并挂载到 /etc/redis/redisbloom.so 目录。
(4)使用 Python3 对应的 pip 来安装 docker-compose 并启动布隆过滤器集群
$ yum install python3
$ yum install python3-pip
$ pip3 install --upgrade pip
$ pip3 install --user docker-compose
$ docker-compose up -d
当你使用 pip3 install --user docker-compose
安装了 docker-compose 后,如果在命令行中输入 docker-compose
时出现"command not found"的错误消息,这通常是因为 ~/.local/bin
路径没有加入到你的环境变量中。
你可以通过修改 ~/.bashrc
或 ~/.bash_profile
文件来将 ~/.local/bin
路径添加到你的 $PATH
环境变量中。可以使用以下命令来进行修改:
$ echo "export PATH=$PATH:~/.local/bin" >> ~/.bashrc
$ source ~/.bashrc
执行以上命令后,再次尝试运行 docker-compose up -d
命令,应该就可以正常工作了。
(5)配置集群
# 进入一个容器
$ docker exec -ti redis01 /bin/bash
# 执行创建集群命令
$ redis-cli --cluster create 172.18.0.2:6379 172.18.0.3:6379 172.18.0.4:6379 172.18.0.5:6379 172.18.0.6:6379 172.18.0.7:6379 --cluster-replicas 1
(6)登陆集群测试一下
# 进入一个Redis集群节点内部
docker exec -ti redis01 /bin/bash
# 以集群方式登录172.18.0.2:3306节点
redis-cli -h 172.18.0.2 -c
# 在Redis中添加一个布隆过滤器:错误率是0.01,数量是1w
BF.RESERVE bf_test 0.01 10000 NONSCALING
# 在 bf_test 的布隆过滤器添加一个key
BF.ADD bf_test testKey1
# 验证布隆过滤器key是否存在
BF.EXISTS bf_test testKey1
# 验证布隆过滤器key是否存在
BF.EXISTS bf_test testKey2
4.5 连接 Redis 集群#
一、下载安装 lua_resty_redis
https://github.com/steve0511/resty-redis-cluster
lua_resty_redis 它是一个基于 rockspec API 的为 ngx_lua 模块提供 Lua Redis 客户端的驱动。
将 resty-redis-cluster/lib/resty 下面的文件拷贝到 openresty/lualib/resty。总共两个文件:rediscluster.lua、xmodem.lua。
$ mv ~/resty-redis-cluster-master.zip /opt/module/openresty-plugins
$ cd /opt/module/openresty-plugins
$ unzip resty-redis-cluster-master.zip
$ cp resty-redis-cluster-master/lib/resty/* /usr/local/openresty/lualib/resty/
二、把连接、操作 Redis 集群 单独封装成一个模块
(1)创建目录专门用来存放 Lua 脚本
$ mkdir /usr/local/openresty/script
(2)在脚本目录下,创建 redisUtils.lua 文件
-- 操作Redis集群,封装成一个模块
-- 引入依赖库
local redis_cluster = require "resty.rediscluster"
-- 配置Redis集群链接信息
local config = {
name = "testCluster", --rediscluster name
serv_list = { --redis cluster node list(host and port),
{ip="192.168.6.160", port = 7001},
{ip="192.168.6.160", port = 7002},
{ip="192.168.6.160", port = 7003},
{ip="192.168.6.160", port = 7004},
{ip="192.168.6.160", port = 7005},
{ip="192.168.6.160", port = 7006},
},
keepalive_timeout = 60000, --redis connection pool idle timeout
keepalive_cons = 1000, --redis connection pool size
connection_timout = 1000, --timeout while connecting
max_redirection = 5
}
-- 定义一个对象
local lredis = {}
-- 创建查询数据get
function lredis.get(key)
local red = redis_cluster:new(config)
local res, err = red:get(key)
if err then
ngx.log(ngx.ERR,"执行get错误:",err)
return false
end
return res
end
-- 执行hgetall方法并封装成table
function lredis.hgetall(hash_key)
local red = redis_cluster:new(config)
local flat_map, err = red:hgetall(hash_key)
if err then
ngx.log(ngx.ERR,"执行hgetall错误:",err)
return false
end
local result = {}
for i = 1, #flat_map, 2 do
result[flat_map[i]] = flat_map[i + 1]
end
return result
end
-- 判断key中的item是否在布隆过滤器中
function lredis.bfexists(key,item)
local red = redis_cluster:new(config)
-- 通过eval执行脚本
local res,err = red:eval([[
local key=KEYS[1]
local val= ARGV[1]
local res,err=redis.call('bf.exists',key,val)
return res
]],1,key,item)
if err then
ngx.log(ngx.ERR,"过滤错误:",err)
return false
end
return res
end
return lredis
(3)配置 Lua 脚本路径
我们编写了 Lua 脚本需要交给 Nginx 服务器去执行,我们需要将创建一个 lua 目录,并在全局的 Nginx 配置文件中配置 lua 目录,配置参数使用 lua_package_path。
worker_processes 1;
# 注意!error日志输出info级别日志
error_log logs/error.log info;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 注意!配置Redis本地缓存
lua_shared_dict redis_cluster_slot_locks 100k;
# 注意!Lua脚本路径
lua_package_path "/usr/local/openresty/script/?.lua;;";
include conf.d/*.conf;
}
(4)在 Nginx 配置文件嵌入 Lua 脚本进行测试
在 /usr/local/openresty/nginx/conf/conf.d 目录下,新建 test.server.conf 用来测试各个模块。
server {
listen 9999;
charset utf-8;
location /test {
default_type text/html;
content_by_lua '
local lrredis = require("redisUtils")
-- 尝试读取redis中key的值
local value = lrredis.get("key")
-- 判断key是否在bf_taxi的布隆过滤器中
local bfexist = lrredis.bfexists("bf_taxi","key")
local htest = lrredis.hgetall("h_taxi")
ngx.log(ngx.INFO, "key的值是",value)
ngx.log(ngx.INFO, "bf_taxi布隆过滤器key的状态",bfexist)
ngx.log(ngx.INFO, "h_taxi[url]的值是",htest["url"])
';
}
}
记得执行 nginx -s reload
命令
(5)设置 Redis 数据
# 登录Redis
docker exec -ti redis01 /bin/bash
redis-cli -h 172.18.0.2 -c
# 设置key
set key value11
# 设置hash
hset h_taxi url http://www.baidu.com
# 创建布隆过滤器
BF.RESERVE bf_taxi 0.01 10000 NONSCALING
# 布隆过滤器添加key
BF.ADD bf_taxi key
(6)访问 http://192.168.6.160:9999/test 并查看 Nginx 日志 tail -f /usr/local/openresty/nginx/logs/error.log
4.6 请求参数封装#
Nginx 为了能够处理请求需要获取请求数据,需要将获取请求参数的 lua 脚本进行封装。
(1)创建 requestUtils.lua 文件
-- 定义一个对象
local lreqparm={}
-- 获取请求参数的方法
function lreqparm.getRequestParam()
-- 获取请求方法get/post
local request_method = ngx.var.request_method
-- 定义参数变量
local args = nil
if "GET" == request_method then
args = ngx.req.get_uri_args()
elseif "POST" == request_method then
ngx.req.read_body()
args = ngx.req.get_post_args()
end
return args
end
return lreqparm
(2)测试脚本
server {
listen 9999;
charset utf-8;
# ...
location /testreq {
default_type text/html;
content_by_lua '
local lreqparm = require("requestUtils")
local params = lreqparm.getRequestParam()
local title = params["title"]
if title ~= nil then
ngx.say("<p>请求参数的Title是:</p>"..title)
return
end
ngx.say("<P>没有输入title请求参数<P>")
';
}
}
记得执行 nginx -s reload
命令
(3)访问 http://192.168.6.160:9999/testreq?title=ceshi
4.7 抓取模版内容封装#
(1)下载安装 lua-resty-http
https://github.com/ledgetech/lua-resty-http
将 lua-resty-http-master/lib/resty 下的所有文件复制到 openresty/lualib/resty,总共两个文件 http.lua、http_headers.lua。
$ mv ~/lua-resty-http-master.zip /opt/module/openresty-plugins
$ cd /opt/module/openresty-plugins
$ unzip lua-resty-http-master.zip
$ cp lua-resty-http-master/lib/resty/* /usr/local/openresty/lualib/resty/
(2)因为需要从远程服务器抓取远程页面的内容,需要用到 http 模块,将其封装起来,创建 requestHtml.lua。
-- 引入Http库
local http = require "resty.http"
-- 定义一个对象
local lgethtml={}
function lgethtml.gethtml(requesturl)
-- 创建客户端
local httpc = http:new()
local resp,err = httpc:request_uri(requesturl,
{
method = "GET",
headers = {["User-Agent"]="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36"}
})
-- 关闭连接
httpc:close()
if not resp then
ngx.say("request error:",err)
return
end
local result = {}
-- 获取状态码
result.status = resp.status
result.body = resp.body
return result
end
return lgethtml
(3)测试脚本
server {
listen 9999;
charset utf-8;
# ...
# 配置路径重写
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
rewrite ^/(.*) http://www.resources.com/$1 permanent;
}
location /testgetHtml {
default_type text/html;
content_by_lua '
local lgethtml = require("requestHtml")
local url = "http://127.0.0.1/template.html"
local result = lgethtml.gethtml(url);
ngx.log(ngx.INFO, "状态是", result.status)
ngx.log(ngx.INFO, "body是", result.body)
ngx.say(result.body)
';
}
}
记得执行 nginx -s reload
命令
(4)访问 http://192.168.6.160:9999/testgetHtml
4.8 模版渲染配置#
(1)下载安装lua-resty-template
解压后可以看到 lib/resty 下面有一个 template.lua,这个就是我们所需要的,在 template 目录中还有两个 lua 文件,将这两个文件复制到 /usr/openResty/lualib/resty 中即可。
# wget https://github.com/bungle/lua-resty-template/archive/v1.9.tar.gz
$ mv ~/lua-resty-template-1.9.tar.gz /opt/module/openresty-plugins
$ tar -zxvf lua-resty-template-1.9.tar.gz
$ mv lua-resty-template-1.9
使用方式:
local template = require "resty.template"
-- Using template.new
local html=[[<html>
<body>
<h1>{{message}}</h1>
</body>
</html>
]]
template.render(html, { message = params["title"] })
(2)在 Nginx 配置文件中嵌入 Lua 脚本执行测试
server {
listen 9999;
charset utf-8;
location /testtemplate {
default_type text/html;
content_by_lua '
local lreqparm = require("requestUtils")
local template = require "resty.template"
local params = lreqparm.getRequestParam()
local html=[[<html>
<body>
<h1>{{message}}</h1>
</body>
</html>
]]
template.render(html, { message = params["title"] })
';
}
}
记得执行 nginx -s reload
命令
(3)访问 http://192.168.6.160:9999/testtemplate?title=ceshi
4.9 整理业务分析#
整个调用流程如下,需要使用 Lua 脚本编写,整体流程分为 7 步。
上面我们将各个组件都给封装了,下面我们需要将各个组件组合起来。
(1)在 /usr/local/openresty/script 目录下创建 requestTemplateRendering.lua
local template = require("resty.template")
local lrredis = require("redisUtils")
local lgethtml = require("requestHtml")
local lreqparm = require("requestUtils")
-- 获取请求参数
local reqParams = lreqparm.getRequestParam()
-- 定义本地缓存
local html_template_cache = ngx.shared.html_template_cache
-- 获取请求ID的参数
local reqId = reqParams["id"];
ngx.log(ngx.INFO, "requestID:", reqId);
-- 校验参数
if reqId==nil then
ngx.say("缺少ID参数");
return
end
-- 布隆过滤器检查id是否存在
local bfexist = lrredis.bfexists("bf_taxi",reqId)
ngx.log(ngx.INFO, "布隆过滤器检验:", bfexist)
-- 校验key不存在直接返回
if bfexist==0 then
ngx.say("布隆过滤器校验key不存在...")
return
end
-- 拼接hget的key
local hgetkey = "hkey_".. reqId
-- 通过hget获取map的所有数据
local templateData = lrredis.hgetall(hgetkey);
if next(templateData) == nil then
ngx.say("redis没有存储数据...")
return
end
-- 获取模板价格数据
local amount = templateData["amount"]
ngx.log(ngx.INFO, "amount:", amount)
if amount == nil then
ngx.say("价格数据未配置");
return
end
-- 获取本地缓存对象
ngx.log(ngx.INFO, "开始从缓存中获取模板数据----")
local html_template = html_template_cache:get(reqId)
-- 判断本地缓存是否存在
if html_template == nil then
-- 获取模板url中的数据
ngx.log(ngx.INFO, "缓存中不存在数据开始远程获取模板")
local url = templateData["url"]
ngx.log(ngx.INFO, "从缓存中获取的url地址:", url)
if url == nil then
ngx.say("URL路径未配置");
return
end
-- 抓取远程url的html
ngx.log(ngx.INFO, "开始抓取模板数据:", url)
local returnResult = lgethtml.gethtml(url);
-- 判断抓取模板是否正常
if returnResult==nil then
ngx.say("抓取URL失败...");
return
end
-- 判断html状态
if returnResult.status==200 then
html_template = returnResult.body
--设置模板缓存为一小时
ngx.log(ngx.INFO, "将模板数据加入到本地缓存")
html_template_cache:set(reqId,html_template,60 * 60)
end
end
ngx.log(ngx.INFO, "缓存中获取模板数据结束----")
-- 模板渲染
-- 编译得到一个lua函数
local func = template.compile(html_template)
local context = {amount=amount}
ngx.log(ngx.INFO, "开始渲染模板数据")
--执行函数,得到渲染之后的内容
local content = func(context)
-- 通过ngx API输出
ngx.say(content)
(2)添加本地缓存
在 Nginx 主配置文件中添加模板缓存
lua_shared_dict html_template_cache 10m;
(3)在 /usr/local/openresty/nginx/conf/conf.d 下新建 Nginx 配置文件 template.conf
server {
listen 8888;
charset utf-8;
# 配置路径重写
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
rewrite ^/(.*) http://www.resources.com/$1 permanent;
}
# 删除本地缓存
location /delete {
default_type text/html;
content_by_lua '
local lreqparm = require("requestUtils")
-- 获取请求参数
local reqParams = lreqparm.getRequestParam()
-- 定义本地缓存
local html_template_cache = ngx.shared.html_template_cache
-- 获取请求ID的参数
local reqId = reqParams["id"];
ngx.log(ngx.INFO, "requestID:", reqId);
-- 校验参数
if reqId==nil then
ngx.say("缺少ID参数");
return
end
-- 获取本地缓存对象
html_template_cache:delete(reqId);
ngx.say("清除缓存成功");
';
}
location /template {
default_type text/html;
content_by_lua_file /usr/local/openresty/script/requestTemplateRendering.lua;
}
}
记得执行 nginx -s reload
命令
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?