Redis + OpenResty 多级缓存

Redis + OpenResty 多级缓存

码农世界 2024-05-13 前端 70 次浏览 0个评论

多级缓存

初识 OpenResty

OpenResty® - 开源官方站

基于 Nginx的高性能 Web 平台,用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

  • 具备Nginx的完整功能
  • 基于Lua语言进行扩展,集成了大量精良的 Lua 库、第三方模块
  • 允许使用Lua自定义业务逻辑、自定义库

    OpenResty 的安装

    安装

    首先你的Linux虚拟机必须联网

    1)安装开发库

    首先要安装OpenResty的依赖开发库,执行命令:

    yum install -y pcre-devel openssl-devel gcc --skip-broken
    

    2)安装OpenResty仓库

    你可以在你的 CentOS 系统中添加 openresty 仓库,这样就可以便于未来安装或更新我们的软件包(通过 yum check-update 命令)。运行下面的命令就可以添加我们的仓库:

    yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
    

    如果提示说命令不存在,则运行:

    yum install -y yum-utils 
    

    然后再重复上面的命令

    3)安装OpenResty

    然后就可以像下面这样安装软件包,比如 openresty:

    yum install -y openresty
    

    4)安装opm工具

    opm是OpenResty的一个管理工具,可以帮助我们安装一个第三方的Lua模块。

    如果你想安装命令行工具 opm,那么可以像下面这样安装 openresty-opm 包:

    yum install -y openresty-opm
    

    5)目录结构

    默认情况下,OpenResty安装的目录是:/usr/local/openresty

    看到里面的nginx目录了吗,OpenResty就是在Nginx基础上集成了一些Lua模块。

    6)配置nginx的环境变量

    打开配置文件:

    vi /etc/profile
    

    在最下面加入两行:

    export NGINX_HOME=/usr/local/openresty/nginx
    export PATH=${NGINX_HOME}/sbin:$PATH
    

    NGINX_HOME:后面是OpenResty安装目录下的nginx的目录

    然后让配置生效:

    source /etc/profile
    
    启动和运行

    OpenResty底层是基于Nginx的,查看OpenResty目录的nginx目录,结构与windows中安装的nginx基本一致:

    所以运行方式与nginx基本一致:

    # 启动nginx
    nginx
    # 重新加载配置
    nginx -s reload
    # 停止
    nginx -s stop
    

    nginx的默认配置文件注释太多,影响后续我们的编辑,这里将nginx.conf中的注释部分删除,保留有效部分。

    修改/usr/local/openresty/nginx/conf/nginx.conf文件,内容如下:

    #user  nobody;
    worker_processes  1;
    error_log  logs/error.log;
    events {
        worker_connections  1024;
    }
    http {
        include       mime.types;
        default_type  application/octet-stream;
        sendfile        on;
        keepalive_timeout  65;
        server {
            listen       8081;
            server_name  localhost;
            location / {
                root   html;
                index  index.html index.htm;
            }
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
        }
    }
    

    在Linux的控制台输入命令以启动nginx:

    nginx
    

    然后访问页面:http://192.168.150.101:8081,注意ip地址替换为你自己的虚拟机IP:

    OpenResty快速入门

    1、在nginx.conf的http下面,添加对OpenResty的Lua模块的加载

    #lua 模块
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    #c模块     
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";  
    

    2、在nginx.conf的server下面,添加对/api/item这个路径的监听

    location /api/item {
        # 响应类型,这里返回json
        default_type application/json;
        # 响应数据由 lua/item.lua这个文件来决定
        content_by_lua_file lua/item.lua;
    }
    

    3、在nginx目录创建文件夹:lua,在lua文件夹下,新建文件:item.lua

    -- 返回假数据,这里的ngx.say()函数,就是写数据到Response中
    ngx.say('{"id":10001,"data":"hello"}')
    

    4、重新加载配置

    nginx -s reload
    

    请求参数处理

    路径占位符 /item/1001
    # 1.正则表达式匹配: 
    location ~ /item/(\d+) {
        content_by_lua_file lua/item.lua;
    }
    
    -- 2. 匹配到的参数会存入ngx.var数组中,
    -- 可以用角标获取
    local id = ngx.var[1]
    
    请求头 id: 1001
    -- 获取请求头,返回值是table类型
    local headers = ngx.req.get_headers()
    
    Get请求参数 ?id=1001
    -- 获取GET请求参数,返回值是table类型
    local getParams = ngx.req.get_uri_args()
    
    Post表单参数 id=1001
    -- 读取请求体
    ngx.req.read_body()
    -- 获取POST表单参数,返回值是table类型
    local postParams = ngx.req.get_post_args()
    
    JSON参数 {“id”: 1001}
    -- 读取请求体
    ngx.req.read_body()
    -- 获取body中的json参数,返回值是string类型
    local jsonBody = ngx.req.get_body_data()
    

    查询Tomcat

    nginx内部发送Http请求

    nginx提供了内部API用以发送http请求

    local resp = ngx.location.capture("/path",{
            method = ngx.HTTP_GET,   -- 请求方式
            args = {a=1,b=2},  -- get方式传参数
            body = "c=3&d=4" -- post方式传参数
        })
    

    注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理。

    但是我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理:

    封装http查询的函数

    把http查询的请求封装为一个函数,放到OpenResty函数库中

    在/usr/local/openresty/lualib目录下创建common.lua文件

    -- 封装函数,发送http请求,并解析响应
    local function read_http(path, params)
        local resp = ngx.location.capture(path,{
                method = ngx.HTTP_GET,
                args = params,
            })
        if not resp then
            -- 记录错误信息,返回404
            ngx.log(ngx.ERR, "http not found, path: ", path , ", args: ", args)
            ngx.exit(404)
        end
        return resp.body
    end
    -- 将方法导出
    local _M = {  
        read_http = read_http
    }  
    return _M
    
    使用Http函数查询数据

    修改item.lua文件

    -- 引入自定义工具模块
    local common = require("common")
    local read_http = common.read_http
    -- 获取路径参数
    local id = ngx.var[1]
    -- 根据id查询商品
    local itemJSON = read_http("/item/".. id, nil)
    -- 根据id查询商品库存
    local itemStockJSON = read_http("/item/stock/".. id, nil)
    
    JSON结果处理

    OpenResty提供了一个cjson的模块用来处理JSON的序列化和反序列化。

    openresty/lua-cjson: Lua CJSON is a fast JSON encoding/parsing module for Lua (github.com)

    -- 引入cjson模块
    local cjson = require "cjson"
    -- 序列化
    local obj = {
        name = 'jack',
        age = 21
    }
    local json = cjson.encode(obj)
    -- 反序列化
    local json = '{"name": "jack", "age": 21}'
    local obj = cjson.decode(json);
    print(obj.name)
    
    Tomcat集群的负载均衡
    # 反向代理配置,将/item路径的请求代理到tomcat集群        
    location /item {
        proxy_pass 	http://tomcat-cluster;
    }
    # tomcat集群配置
    upstream tomcat-cluster{
        # 对请求的 URI 进行哈希处理
        hash $request_uri;
        server 192.168.150.1:8081;
        server 192.168.150.1:8082;
    }
    

    Redis缓存预热

    冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力。

    缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中。

    @Component
    public class RedisHandler implements InitializingBean {
        @Autowired
        private StringRedisTemplate redisTemplate;
        @Override
        public void afterPropertiesSet() throws Exception { 
            // 初始化缓存 ... 
        }
    }
    

    查询Redis缓存

    OpenResty的Redis模块

    OpenResty提供了操作Redis的模块

    -- 引入redis模块
    local redis = require("resty.redis")
    -- 初始化Redis对象
    local red = redis:new()
    -- 设置Redis超时时间
    red:set_timeouts(1000, 1000, 1000)
    

    封装函数,用来释放Redis连接,其实是放入连接池

    -- 关闭redis连接的工具方法,其实是放入连接池
    local function close_redis(red)  
        local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒  
        local pool_size = 100 --连接池大小  
        local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  
        if not ok then  
            ngx.log(ngx.ERR, "放入Redis连接池失败: ", err)  
        end  
    end  
    

    封装函数,从Redis读数据并返回

    -- 查询redis的方法 ip和port是redis地址,key是查询的key
    local function read_redis(ip, port, key)
        -- 获取一个连接
        local ok, err = red:connect(ip, port)
        if not ok then
            ngx.log(ngx.ERR, "连接redis失败 : ", err)
            return nil
        end
        -- 查询redis
        local resp, err = red:get(key)
        -- 查询失败处理
        if not resp then
            ngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)
        end
        --得到的数据为空处理
        if resp == ngx.null then
            resp = nil
            ngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)
        end
        close_redis(red)
        return resp
    end
    

    业务实现

    -- 封装函数,先查询redis,再查询http
    local function read_data(key, path, params)
        -- 查询redis
        local resp = read_redis("127.0.0.1", 6379, key)
        -- 判断redis是否命中
        if not resp then
            -- Redis查询失败,查询http
            resp = read_http(path, params)
        end
        return resp
    end
    

    Nginx本地缓存

    开启共享词典,在nginx.conf的http下添加配置:

    # 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m
    lua_shared_dict item_cache 150m; 
    

    操作共享字典

    -- 获取本地缓存对象
    local item_cache = ngx.shared.item_cache
    -- 存储, 指定key、value、过期时间,单位s,默认为0代表永不过期
    item_cache:set('key', 'value', 1000)
    -- 读取
    local val = item_cache:get('key')
    

    修改后的查询逻辑

    -- 封装函数,先查询本地缓存,再查询redis,再查询http
    local function read_data(key, expire, path, params)
        -- 读取本地缓存
        local val = item_cache:get(key)
        if not val then
            -- 缓存未命中,记录日志
            ngx.log(ngx.ERR, "本地缓存查询失败, key: ", key , ", 尝试redis查询")
            -- 查询redis
            val = read_redis("127.0.0.1", 6379, key)
            -- 判断redis是否命中
            if not val then
                ngx.log(ngx.ERR, "Redis缓存查询失败, key: ", key , ", 尝试http查询")
                -- Redis查询失败,查询http
                val = read_http(path, params)
            end
        end
        -- 写入本地缓存
        item_cache:set(key, val, expire)
        return val
    end
    -- 根据id查询商品
    local itemJSON = read_data('item:id:' .. id, 1800, "/item/".. id, nil)
    -- 根据id查询商品库存
    local itemStockJSON = read_data('item:stock:id:' .. id, 60, "/item/stock/".. id, nil)
    

转载请注明来自码农世界,本文标题:《Redis + OpenResty 多级缓存》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,70人围观)参与讨论

还没有评论,来说两句吧...

Top