首页 > 数据库 >基于Redis和openresty实现高并发缓存架构

基于Redis和openresty实现高并发缓存架构

时间:2024-06-22 09:59:31浏览次数:21  
标签:缓存 end err Redis self redis ngx openresty local

目录

概述

   本文是对项目中 QPS 高并发相关问题的一种解决方案,利用 NginxRedis 的高并发、超低延迟响应,结合 Canal 进行实现。

openrestry官网

   当程序需要提供较高的并发访问时,往往需要在程序中引入缓存技术,通常都是使用Redis 作为缓存,如若再更进一步提升性能,不仅要使用 redis 还要提高 并发,使用能支持超高并发的组件,并将请求响应大部分落在这些组件中。

原本访问缓存逻辑

User—> Nginx -> Tomcat -> Redis

User—> Nginx -> Redis

相关组件可自由下载
懒人直通: openresty/openresty:1.25.3.1-4-alpine-fat redis-7.0.15 docker离线镜像安装包

缓存架构设计

基本数据库 mysql ,整合 canal , 异步的同步数据至 redis 中,主要是利用 redis 与 nginx 的低延迟与高并发。
在这里插入图片描述

  • HTML页面做缓存,浏览器端可以缓存HTML页面和其他静态资源,防止用户频繁刷新对后端造成巨大压力
  • Lvs实现记录不同协议以及不同用户请求链路缓存
  • Nginx这里会做HTML页面缓存配置以及Nginx自身缓存配置(本次没有做Nginx缓存)
  • 数据查找这里用Lua取代了其他语言查找,提高了处理的性能效率,并发处理能力将大大提升
  • 集成Canal实现数据库数据增量实时同步Redis

实践

代码

路由

nginx 路由配置

server {
    listen 8000;

    set $target_server '';

    location /test{
        access_by_lua_file /usr/local/openresty/nginx/lua/router.lua;
        proxy_pass $target_server;
    }
    location /cache{
        default_type text/html;
        content_by_lua_file /usr/local/openresty/nginx/lua/cache_redis.lua;
    }
}

业务

创建文件 cache_redis.lua

local redis = require "redis_iresty"
local cjson = require("cjson")
local ngx_ERR = ngx.ERR
local ngx_exit = ngx.exit
local ngx_print = ngx.print
-- local ngx_re_match = ngx.re.match
local ngx_var = ngx.var


-- 响应输出内容
-- body   http输出body内容
-- status http状态码
-- header http响应头,table格式
local function response(body,status,header)
    ngx.status = status
    if header then
        for key, val in pairs(header) do
            ngx.header[key] = val
        end
    end
    --ngx_print(body)
    ngx.say(cjson.encode(body))
    ngx_exit(ngx.status)
end


local opts = {
        ip = "10.32.36.142",
        port = "6379",
        password = "123456",
        db_index = 0
}

local red = redis:new(opts)

local status = 200
local header = {}
local content = {}

-- 返回的是一个table类型
local args = ngx.req.get_uri_args()
-- 获取名为"key"的参数
local key = args["key"]  

header['content_type'] = 'application/json; charset=utf-8'
local value = red:get(key)
content['data'] = value
content['msg'] = '数据获取成功'
content['key'] = key
response(content,status,header)

封装redis

网上寻找的 redis 二次封装 redis_iresty.lua

local redis_c = require "resty.redis"

local ok, new_tab = pcall(require, "table.new")
if not ok or type(new_tab) ~= "function" then
    new_tab = function (narr, nrec) return {} end
end

local _M = new_tab(0, 155)
_M._VERSION = '0.01'

local commands = {
    "append",            "auth",              "bgrewriteaof",
    "bgsave",            "bitcount",          "bitop",
    "blpop",             "brpop",
    "brpoplpush",        "client",            "config",
    "dbsize",
    "debug",             "decr",              "decrby",
    "del",               "discard",           "dump",
    "echo",
    "eval",              "exec",              "exists",
    "expire",            "expireat",          "flushall",
    "flushdb",           "get",               "getbit",
    "getrange",          "getset",            "hdel",
    "hexists",           "hget",              "hgetall",
    "hincrby",           "hincrbyfloat",      "hkeys",
    "hlen",
    "hmget",              "hmset",      "hscan",
    "hset",
    "hsetnx",            "hvals",             "incr",
    "incrby",            "incrbyfloat",       "info",
    "keys",
    "lastsave",          "lindex",            "linsert",
    "llen",              "lpop",              "lpush",
    "lpushx",            "lrange",            "lrem",
    "lset",              "ltrim",             "mget",
    "migrate",
    "monitor",           "move",              "mset",
    "msetnx",            "multi",             "object",
    "persist",           "pexpire",           "pexpireat",
    "ping",              "psetex",            "psubscribe",
    "pttl",
    "publish",      --[[ "punsubscribe", ]]   "pubsub",
    "quit",
    "randomkey",         "rename",            "renamenx",
    "restore",
    "rpop",              "rpoplpush",         "rpush",
    "rpushx",            "sadd",              "save",
    "scan",              "scard",             "script",
    "sdiff",             "sdiffstore",
    "select",            "set",               "setbit",
    "setex",             "setnx",             "setrange",
    "shutdown",          "sinter",            "sinterstore",
    "sismember",         "slaveof",           "slowlog",
    "smembers",          "smove",             "sort",
    "spop",              "srandmember",       "srem",
    "sscan",
    "strlen",       --[[ "subscribe",  ]]     "sunion",
    "sunionstore",       "sync",              "time",
    "ttl",
    "type",         --[[ "unsubscribe", ]]    "unwatch",
    "watch",             "zadd",              "zcard",
    "zcount",            "zincrby",           "zinterstore",
    "zrange",            "zrangebyscore",     "zrank",
    "zrem",              "zremrangebyrank",   "zremrangebyscore",
    "zrevrange",         "zrevrangebyscore",  "zrevrank",
    "zscan",
    "zscore",            "zunionstore",       "evalsha"
}

local mt = { __index = _M }

local function is_redis_null( res )
    if type(res) == "table" then
        for k,v in pairs(res) do
            if v ~= ngx.null then
                return false
            end
        end
        return true
    elseif res == ngx.null then
        return true
    elseif res == nil then
        return true
    end

    return false
end

function _M.close_redis(self, redis)  
    if not redis then  
        return  
    end  
    --释放连接(连接池实现)
    local pool_max_idle_time = self.pool_max_idle_time --最大空闲时间 毫秒  
    local pool_size = self.pool_size --连接池大小  
        
    local ok, err = redis:set_keepalive(pool_max_idle_time, pool_size)  
    if not ok then  
        ngx.say("set keepalive error : ", err)  
    end  
end  

-- change connect address as you need
function _M.connect_mod( self, redis )
    redis:set_timeout(self.timeout)
                
        local ok, err = redis:connect(self.ip, self.port)
        if not ok then  
                ngx.say("connect to redis error : ", err)  
                return self:close_redis(redis)  
        end
        
        if self.password then ----密码认证
                local count, err = redis:get_reused_times()
                if 0 == count then ----新建连接,需要认证密码
                        ok, err = redis:auth(self.password)
                        if not ok then
                                ngx.say("failed to auth: ", err)
                                return
                        end
                elseif err then  ----从连接池中获取连接,无需再次认证密码
                        ngx.say("failed to get reused times: ", err)
                        return
                end
        end

    return ok,err;
end

function _M.init_pipeline( self )
    self._reqs = {}
end

function _M.commit_pipeline( self )
    local reqs = self._reqs

    if nil == reqs or 0 == #reqs then
        return {}, "no pipeline"
    else
        self._reqs = nil
    end

    local redis, err = redis_c:new()
    if not redis then
        return nil, err
    end

    local ok, err = self:connect_mod(redis)
    if not ok then
        return {}, err
    end

    redis:init_pipeline()
    for _, vals in ipairs(reqs) do
        local fun = redis[vals[1]]
        table.remove(vals , 1)

        fun(redis, unpack(vals))
    end

    local results, err = redis:commit_pipeline()
    if not results or err then
        return {}, err
    end

    if is_redis_null(results) then
        results = {}
        ngx.log(ngx.WARN, "is null")
    end
    -- table.remove (results , 1)

    --self.set_keepalive_mod(redis)
        self:close_redis(redis)  

    for i,value in ipairs(results) do
        if is_redis_null(value) then
            results[i] = nil
        end
    end

    return results, err
end


local function do_command(self, cmd, ... )
    if self._reqs then
        table.insert(self._reqs, {cmd, ...})
        return
    end

    local redis, err = redis_c:new()
    if not redis then
        return nil, err
    end

    local ok, err = self:connect_mod(redis)
    if not ok or err then
        return nil, err
    end

        redis:select(self.db_index)
        
    local fun = redis[cmd]
    local result, err = fun(redis, ...)
    if not result or err then
        -- ngx.log(ngx.ERR, "pipeline result:", result, " err:", err)
        return nil, err
    end

    if is_redis_null(result) then
        result = nil
    end

    --self.set_keepalive_mod(redis)
        self:close_redis(redis)  

    return result, err
end

for i = 1, #commands do
    local cmd = commands[i]
    _M[cmd] =
            function (self, ...)
                return do_command(self, cmd, ...)
            end
end

function _M.new(self, opts)
    opts = opts or {}
    local timeout = (opts.timeout and opts.timeout * 1000) or 1000
    local db_index= opts.db_index or 0
        local ip = opts.ip or '127.0.0.1'
        local port = opts.port or 6379
        local password = opts.password
        local pool_max_idle_time = opts.pool_max_idle_time or 60000
        local pool_size = opts.pool_size or 100

    return setmetatable({
            timeout = timeout,
            db_index = db_index,
                        ip = ip,
                        port = port,
                        password = password,
                        pool_max_idle_time = pool_max_idle_time,
                        pool_size = pool_size,
            _reqs = nil }, mt)
end

return _M

效果

在这里插入图片描述
在这里插入图片描述
  环境说明:搭建基本上使用了 docker,使用无线网, 单次请求一大半落在了个位数 毫秒级内,最慢情况下,基本不会超过 60ms

标签:缓存,end,err,Redis,self,redis,ngx,openresty,local
From: https://blog.csdn.net/2301_79691134/article/details/139650177

相关文章

  • 一对一聊天源码,你是否了解ERedis的扩容机制?
    一对一聊天源码,你是否了解ERedis的扩容机制?Redis的扩容时机Redis会在如下两种情况触发扩容。1、如果没有fork子进程在执行RDB或者AOF的持久化,一旦满足ht[0].used>=ht[0].size,此时触发扩容;2、如果有fork子进程在执行RDB或者AOF的持久化时,则需要满足ht[0].used>5*ht......
  • Spring (72)如何在Spring中使用缓存
    在Spring框架中,使用缓存是一种有效的方式来提高应用程序性能,减少数据库或者计算密集型操作的负担。Spring提供了一个声明式的缓存抽象,它允许开发者通过注解来简单地将缓存应用到应用程序中。下面我们将深入探讨Spring缓存的使用,结合源码解析和代码演示。1.SpringCache抽......
  • Redis项目实战——黑马点评(优惠卷秒杀部分)
    优惠券秒杀4.1全局唯一ID思路每个店铺都可以发布优惠券:当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题:id的规律性太明显受单表数据量的限制场景分析:如果我们的id具有太明显的规则,用户或者说商业对手很容易猜测出......
  • SpringBoot+AOP+Redis自定义注解实现防重复提交
    1.哪些因素会引起重复提交?开发项目过程中可能会出现下面这些情况:前端下单按钮重复点击导致订单创建多次网速等原因造成页面卡顿,用户重复刷新提交请求黑客或恶意用户使用Postman等http工具重复恶意提交表单2.重复提交会带来哪些问题?重复提交带来的问题:会导致数据......
  • Redis 分布式锁
    Redis分布式锁分布式锁的演变本地锁(单机用)利用redis进行分布式锁使用set防止死锁加过期时间使用setnx防止A请求未执行完锁过期删除B请求加锁后A完成后误删该锁使用Hash结构,规定每个请求只能删除自己的锁保证并发安全,申请锁和加过期时间需要原子性,用lua脚本......
  • redisson WRONGPASS invalid username-password pair or user is disable
    1、技术架构:若依微服务框架<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2021.1</version></dependency><dependency>......
  • redis自学(47)服务端优化
    持久化配置Redis的持久化虽然可以保证数据安全,但也会带来很多额外的开销,因此持久化请遵循下列建议:①用来做缓存的redis实例尽量不要开启持久化功能②建议关闭RDB持久化功能,使用AOF持久化(RDB的数据安全性一直是有问题的,两次RDB的时间比较长,又不能频繁的RDB,因为耗时久而且需......
  • 并发业务使用redis分布式锁
    伴随着业务体量的上升,我们的qps与并发问题越来越明显,这时候就需要用到让代码一定情况下进行串行执行的工具:锁1.业务场景代码@Override@Transactional(rollbackFor=Exception.class)publicObjecttestBatch(Useruser){LambdaQueryWrapper<Us......
  • [转帖]Redis如何绑定CPU
    https://wenfh2020.com/2023/10/08/https/ 发布时间:2022-03-0809:44:39 阅读:649 作者:小新 栏目:开发技术开发者测试专用服务器限时活动,0元免费领,库存有限,领完即止!点击查看>>这篇文章主要介绍了Redis如何绑定CPU,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这......
  • 【车载网络协议】【Ethernet】【ARP】【第三章-缓存介绍】
    一、概念简介ARP(AddressResolutionProtocol)是一种用于将网络层地址(IP地址)解析为数据链路层地址(MAC地址)的协议。ARPCache是ARP协议在计算机或网络设备上维护的一个重要数据结构,用于存储IP地址与对应的MAC地址之间的映射关系。本文将介绍ARPCache的作用、工作原理......