一、缓存架构

OpenRestyRedis 实现缓存架构,如下图所示:

openresty使用lua脚本 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) 出现下面的页面,即搭建成功。

openresty使用lua脚本 openresty redis_nginx_02

  3) 修改 `/mydata/openresty/conf/nginx.conf` 配置文件,并重新加载配置文件 `docker exec -it openresty nginx -s reload`。

openresty使用lua脚本 openresty redis_redis_03

三、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 用于测试。

openresty使用lua脚本 openresty redis_数据_04

四、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 中。

openresty使用lua脚本 openresty redis_openresty使用lua脚本_05


openresty使用lua脚本 openresty redis_openresty使用lua脚本_06


  2) 发起第二次请求。此时 redis 中对应的数据,直接从 redis 中响应数据,并将查询到的数据写入 nginx 缓存模块中。

openresty使用lua脚本 openresty redis_openresty使用lua脚本_07


  3) 发起第三次请求。此时 openresty 的 nginx 缓存模块存在对应的数据,直接从 nginx 缓存模块中返回数据。

openresty使用lua脚本 openresty redis_nginx_08