OpenResty+lua+redis实现广告缓存

OpenResty+lua+redis实现广告缓存

一、需求

ngx_openresty是一个基于 NGINX的lua可编程模块,在性能方面有着出色的性能,配合redis做二级缓存效果,nginx开启一级本地缓存。

实验数据库sql

/*
 Navicat Premium Data Transfer

 Source Server         : 本机mysql
 Source Server Type    : MySQL
 Source Server Version : 50728
 Source Host           : localhost:3306
 Source Schema         : demo

 Target Server Type    : MySQL
 Target Server Version : 50728
 File Encoding         : 65001

 Date: 25/05/2020 10:58:31
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_ad
-- ----------------------------
DROP TABLE IF EXISTS `tb_ad`;
CREATE TABLE `tb_ad`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '广告主键',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'URL',
  `status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '0:无效  1:有效',
  `position` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '广告位置',
  `image` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图片路径',
  `start_time` datetime(0) NULL DEFAULT NULL COMMENT '开始时间',
  `end_time` datetime(0) NULL DEFAULT NULL COMMENT '到期时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_ad
-- ----------------------------
INSERT INTO `tb_ad` VALUES (1, 'https://www.baidu.com/', '1', 'web_index_lb', 'https://kins.oss-cn-shenzhen.aliyuncs.com/yhzb/2020-03-11/ca21b3b17d6f4757b991dd86b8cef3fa-VIP-680.jpeg', '2020-05-22 10:58:08', '2021-06-01 10:58:14');

SET FOREIGN_KEY_CHECKS = 1;

二、一二级缓存实现

查询mysql中上架的所有广告转换为json字符串,放入redis中作为二级缓存,等真正的第一次查询会把缓存加入一级本地缓存。当广告修改时候需要请求,需要请求预热接口。

nginx.conf文件配置

#user  nobody;
user root root;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
    #包含redis初始化模块
    lua_shared_dict dis_cache 5m;  #共享内存开启
    
    server {
        listen       80;
        server_name  localhost;
        charset utf-8;
        #access_log  logs/host.access.log  main;
        # 添加预热和读取接口
        location /ad_loading{
            content_by_lua_file /root/lua/ad_loading.lua;
        }
                location /ad_read {
                        content_by_lua_file /root/lua/ad_read.lua;
                }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }        
    }
}

ad_loading.lua预热脚本

ngx.header.content_type="application/json;charset=utf8"
local cjson = require("cjson")
local mysql = require("resty.mysql")
local uri_args = ngx.req.get_uri_args()
local position = uri_args["position"]

local db = mysql:new()
db:set_timeout(1000)  
local props = {  
    host = "127.0.0.1",  
    port = 3306,  
    database = "business",  
    user = "root",  
    password = "root"  
}

local res = db:connect(props)  
local select_sql = "select url,image from tb_ad where status ='1' and position='"..position.."' and start_time<= NOW() AND end_time>= NOW()"  
res = db:query(select_sql)  
db:close()  

local redis = require("resty.redis")
local red = redis:new()
red:set_timeout(2000)

local ip ="127.0.0.1"
local port = 6379
red:connect(ip,port)
--Redis Authentication
local result, err = red:auth("redis")
if not result then
    ngx.say("failed to authenticate: ", err)
    return
end

red:set("ad_"..position,cjson.encode(res))
red:close()

ngx.say("{flag:true}")

ad_read.lua读取脚本

--设置响应头类型
ngx.header.content_type="application/json;charset=utf8"
--获取请求中的参数ID
local uri_args = ngx.req.get_uri_args();
local position = uri_args["position"];

--获取本地缓存
local cache_ngx = ngx.shared.dis_cache;
--根据ID 获取本地缓存数据
local adCache = cache_ngx:get('ad_cache_'..position);

if adCache == "" or adCache == nil then

    local redis = require("resty.redis");
    local red = redis:new()
    red:set_timeout(2000)
    local ok, err = red:connect("127.0.0.1", 6379)
    --Redis Authentication
    local result, err = red:auth("redis")
    if not result then
        ngx.say("failed to authenticate: ", err)
        return
    end
    local rescontent=red:get("ad_"..position)
    ngx.say(rescontent)
    red:close()
    --将redis中获取到广告数据存入nginx本地缓存
    cache_ngx:set('ad_cache_'..position, rescontent, 10*60);
else
     --nginx本地缓存中获取到数据直接输出
     ngx.say(adCache)
end

三、拓展方向

lua + nginx可以作为网关进行限流、流控和多级缓存使用,内存小可用性也非常高,配合一些lua模块可以做一些深入拓展。
阅读 186

推荐阅读