一、缓存架构
OpenResty 和 Redis 实现缓存架构,如下图所示:
1) 当请求过来时,先查找 Nginx 缓存,若缓存存在需要的数据,则直接返回。否则进入第二步;
2) 若 Nginx 缓存模块中不存在数据,则通过 Lua 脚本查询 Redis。若 Redis 中存在数据,则通过 Lua 脚本将数据存入 Nginx 缓存中,并返回查询到的数据。否则进入第三步;
3) 若 Redis 中也没有缓存,则通过 Lua 脚本查询 MySQL。若 MySQL 中存在数据,则通过 Lua 脚本存入 Redis 中,并返回查询到的数据。
二、OpenResty
OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
360、UPYUN、新浪、阿里云、酷狗音乐等都是 OpenResty 的深度用户。
综上所述, OpenResty 封装了 Nginx,并为 Nginx 提供了高性能的可扩展程序,使 Nginx 的抗压能力大幅提升,并且通过 Lua 脚本能使 Nginx 的抗压能力达到 10K ~ 1000K。
1) Docker 安装 OpenResty
# 创建简单的 oprenresty 容器
docker run -d --name openresty openresty/openresty
# 创建目录, lua 存放 lua 脚本, logs 存放日志文件, conf 存放配置文件
mkdir -p /mydata/openresty/lua /mydata/openresty/logs /mydata/openresty/conf
# 复制容器中的文件
docker cp openresty:/usr/local/openresty/nginx/conf/nginx.conf /mydata/openresty/conf/
# 删除容器
docker rm -f openresty
# 进入目录
cd /mydata/openresty
# 创建容器
docker run -d --name openresty -p 8989:80 --restart=always --privileged=true \
-v ${PWD}/conf/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf \
-v ${PWD}/logs:/usr/local/openresty/nginx/logs \
-v ${PWD}/lua:/usr/local/lua \
openresty/openresty
2) 出现下面的页面,即搭建成功。
3) 修改 `/mydata/openresty/conf/nginx.conf` 配置文件,并重新加载配置文件 `docker exec -it openresty nginx -s reload`。
三、Docker 搭建 Redis 和 MySQL
3.1 Docker 搭建 Redis
# 创建目录
mkdir -p /mydata/redis/conf /mydata/redis/data
# 下载对应版本的 redis,获取其中的 redis.conf 文件,将 redis.conf 复制到 conf 目录下
# https://download.redis.io/releases/redis-4.0.14.tar.gz
# 修改 redis.conf 的配置
vim /mydata/redis/conf/redis.conf
# 开启远程访问
bind 0.0.0.0
# 开启 aof 持久化
appendonly yes
# 默认no,改为 yes 意为以守护进程方式启动,可后台运行,除非kill进程,改为 yes 会使配置文件方式启动 redis 失败
daemonize no
# 创建容器
docker run -d -p 6379:6379 --name redis --restart=always --privileged=true \
-v ${PWD}/conf/redis.conf:/etc/redis/redis.conf \
-v ${PWD}/data:/data \
redis:4.0.14 \
redis-server /etc/redis/redis.conf
3.2 Docker 搭建 MySQL
# 创建目录
mkdir -p /mydata/mysql/conf /mydata/mysql/data /mydata/mysql/logs
# 创建数据库配置文件 my.cnf
vim /mydata/mysql/conf/my.cnf
# 进入目录
cd /mydata/mysql
# 创建容器
docker run -d --name mysql -p 3306:3306 -p 33060:33060 --privileged=true --restart=always \
-v ${PWD}/logs:/var/log/mysql \
-v ${PWD}/data:/var/lib/mysql \
-v ${PWD}/conf/my.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf \
-e MYSQL_ROOT_PASSWORD=root \
mysql:5.7
my.cnf 的配置:
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection = utf8_unicode-ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO ,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
接下来创建数据库 cache_test 和表 tb_user 用于测试。
四、Lua 脚本
-- 设置请求头响应的数据类型
ngx.header.content_type = "application/json;charest=utf8"
-- 获取 URL 中的请求参数
local uri_args = ngx.req.get_uri_args()
local id = uri_args["id"]
-- 获取 nginx 中的本地缓存
local cache_ngx = ngx.shared.dis_cache
-- 根据 ID 读取本地缓存数据
local contentCache = cache_ngx:get("content_cache_"..id)
-- 响应 nginx 缓存中的数据
ngx.say(contentCache)
-- 若 nginx 本地缓存中数据为空,读取 redis 中的数据,并存储至 nginx 本地缓存中
if contentCache == "" or contentCache == nil then
-- 引入 redis 模块
local redis = require("resty.redis")
-- redis 地址和端口
local ip = "192.168.125.140"
local port = 6379
-- 获取 redis 请求对象
local red = redis:new()
red:set_timeout(2000)
-- 发起 redis 连接
red:connect(ip, port)
-- 获取 redis 中响应的数据记录
local redisContent = red:get("content_"..id)
-- 若 redis 缓存中不存在对应的数据记录,则请求数据库,并存储至 redis 中
if ngx.null == redisContent then
-- 引入 openresty 中的 cjson 模块,用于序列化数据库查询结果
local cjson = require("cjson")
--引入 Mysql 模块
local mysql = require("resty.mysql")
-- 获取数据库请求对象
local db = mysql:new()
db:set_timeout(2000)
-- 数据库连接参数
local props = {
host = "192.168.125.140",
port = 3306,
database = "cache_test",
user = "root",
password = "root"
}
-- 发起数据库连接
local res = db:connect(props)
local sql = "select id, username from tb_user where id ="..id
-- 数据库查询结果中文乱码处理
-- db:query("SET NAMES utf8")
-- 执行 SQL
res = db:query(sql)
-- cjson 序列化数据库查询结果,即转成 json
local responseJson = cjson.encode(res)
-- 将数据库查询结果存储至 redis 中,并设置过期时间
red:set("content_"..id, responseJson)
red:expire("content_"..id, 2 * 60)
-- 响应数据库数据
ngx.say(responseJson)
db:close()
else
-- 将 redis 结果存储至 nginx 本地缓存中
cache_ngx:set("content_cache_"..id, redisContent, 2 * 60)
-- 响应 redis 数据
ngx.say(redisContent)
end
red:close()
else
-- 响应 nginx 本地缓存数据
ngx.say(contentCache)
end
五、测试结果
由于此时是第一次请求数据,openresty 的 nginx 缓存模块和 redis 中均不存在所需要的数据,因此直接从数据库中获取数据,同时将获取到的数据写入 Redis 中。
2) 发起第二次请求。此时 redis 中对应的数据,直接从 redis 中响应数据,并将查询到的数据写入 nginx 缓存模块中。
3) 发起第三次请求。此时 openresty 的 nginx 缓存模块存在对应的数据,直接从 nginx 缓存模块中返回数据。