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

2024-06-23 02:20

本文主要是介绍基于Redis和openresty实现高并发缓存架构,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 概述
  • 缓存架构设计
  • 实践
    • 代码
      • 路由
      • 业务
      • 封装redis
    • 效果

概述

   本文是对项目中 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 = statusif header thenfor key, val in pairs(header) dongx.header[key] = valendend--ngx_print(body)ngx.say(cjson.encode(body))ngx_exit(ngx.status)
endlocal 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" thennew_tab = function (narr, nrec) return {} end
endlocal _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" thenfor k,v in pairs(res) doif v ~= ngx.null thenreturn falseendendreturn trueelseif res == ngx.null thenreturn trueelseif res == nil thenreturn trueendreturn false
endfunction _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)  endif self.password then ----密码认证local count, err = redis:get_reused_times()if 0 == count then ----新建连接,需要认证密码ok, err = redis:auth(self.password)if not ok thenngx.say("failed to auth: ", err)returnendelseif err then  ----从连接池中获取连接,无需再次认证密码ngx.say("failed to get reused times: ", err)returnendendreturn ok,err;
endfunction _M.init_pipeline( self )self._reqs = {}
endfunction _M.commit_pipeline( self )local reqs = self._reqsif nil == reqs or 0 == #reqs thenreturn {}, "no pipeline"elseself._reqs = nilendlocal redis, err = redis_c:new()if not redis thenreturn nil, errendlocal ok, err = self:connect_mod(redis)if not ok thenreturn {}, errendredis:init_pipeline()for _, vals in ipairs(reqs) dolocal fun = redis[vals[1]]table.remove(vals , 1)fun(redis, unpack(vals))endlocal results, err = redis:commit_pipeline()if not results or err thenreturn {}, errendif is_redis_null(results) thenresults = {}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) doif is_redis_null(value) thenresults[i] = nilendendreturn results, err
endlocal function do_command(self, cmd, ... )if self._reqs thentable.insert(self._reqs, {cmd, ...})returnendlocal redis, err = redis_c:new()if not redis thenreturn nil, errendlocal ok, err = self:connect_mod(redis)if not ok or err thenreturn nil, errendredis: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, errendif is_redis_null(result) thenresult = nilend--self.set_keepalive_mod(redis)self:close_redis(redis)  return result, err
endfor i = 1, #commands dolocal cmd = commands[i]_M[cmd] =function (self, ...)return do_command(self, cmd, ...)end
endfunction _M.new(self, opts)opts = opts or {}local timeout = (opts.timeout and opts.timeout * 1000) or 1000local db_index= opts.db_index or 0local ip = opts.ip or '127.0.0.1'local port = opts.port or 6379local password = opts.passwordlocal pool_max_idle_time = opts.pool_max_idle_time or 60000local pool_size = opts.pool_size or 100return 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)
endreturn _M

效果

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

这篇关于基于Redis和openresty实现高并发缓存架构的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1086042

相关文章

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定