基于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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/31961.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【单片机】MSP430G2553单片机 Could not find MSP-FET430UIF on specified COM port 解决方案

文章目录 MSP430G2553开发板基础知识解决办法如何实施解决办法4步骤一步骤二步骤三 MSP430G2553开发板基础知识 MSP430G2553开发板如下图,上半部分就是UIF程序下载调试区域的硬件。个人觉得MSP430G2553开发板的这个部分没有做好硬件设计,导致很多系统兼…

计算机组成原理笔记-第1章 计算机系统概论

第一章 计算机系统概论 笔记PDF版本已上传至Github个人仓库:CourseNotes,欢迎fork和star,拥抱开源,一起完善。 该笔记是最初是没打算发网上的,所以很多地方都为了自我阅读方便,我理解了的地方就少有解释&a…

【鸿蒙】 模拟器运⾏

【鸿蒙】HUAWEI DevEco Studio安装-CSDN博客 【鸿蒙】创建第⼀个鸿蒙项⽬-CSDN博客 点击 Tools 菜单下的 Device Manager 点击 Install ,安装模拟器 下载模拟器相关的SDK,点击 Finish 选择安装⽬录,点击 New Emulator 选择设备类型&#…

代码随想录——分发饼干(Leetcode455)

题目链接 贪心 class Solution {public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);Arrays.sort(s);int count 0;for(int i 0, j 0; i < g.length && j < s.length; i, j){if(s[j] > g[i]){count;}else{i--;}}return count;} }

IDS使用规则

ids入侵检测系统 Snort(基于特征的入侵检测系统) 软硬件配套 snort官网链接&#xff1a;Snort - Network Intrusion Detection & Prevention System(https://www.snort.org) 环境&#xff1a;centos7,拖动需要开共享粘贴板和开双向 一、daq 1、预装dap所需程序 yum inst…

为什么五笔没人用了?

五笔输入法现在较少人使用的原因主要有以下几点&#xff1a; 学习门槛高&#xff1a;五笔输入法的学习难度相对较大&#xff0c;需要掌握所有的字根&#xff0c;全面了解编码的规律&#xff0c;并学习每个字的拆字原则&#xff0c;这要求用户有相当高的耐心和时间去学习和练习…

外星人Alienware m15R7 原厂Windows11系统

装后恢复到您开箱的体验界面&#xff0c;包括所有原机所有驱动AWCC、Mydell、office、mcafee等所有预装软件。 最适合您电脑的系统&#xff0c;经厂家手调试最佳状态&#xff0c;性能与功耗直接拉满&#xff0c;体验最原汁原味的系统。 原厂系统下载网址&#xff1a;http://w…

【MySQL进阶之路 | 高级篇】InnoDB存储结构

1. 数据库的存储结构 : 页 索引结构给我们提供了高效的索引方式&#xff0c;不过索引信息以及数据记录都是保存在文件上的.确切说是存储在页结构中.另一方面&#xff0c;索引是在存储引擎中实现的&#xff0c;MySQL服务器上的存储引擎负责对表中数据的读取和写入操作.不同的存…

Java | Leetcode Java题解之第165题比较版本号

题目&#xff1a; 题解&#xff1a; class Solution {public int compareVersion(String version1, String version2) {int n version1.length(), m version2.length();int i 0, j 0;while (i < n || j < m) {int x 0;for (; i < n && version1.charAt(…

速卖通测评成本低见效快,自养号测评的实操指南,快速积累销量和好评

对于初入速卖通的新卖家而言&#xff0c;销量和评价的积累显得尤为关键。由于新店铺往往难以获得平台活动的青睐&#xff0c;因此流量的获取成为了一大挑战。在这样的背景下&#xff0c;进行产品测评以积累正面的用户反馈和销售记录&#xff0c;成为了提升店铺信誉和吸引潜在顾…

Redis 的安装与部署

本文为Redis的Linux版单机部署。 上传 redis-3.2.8 源码到 /opt/software/ 解压到 /opt/module/ [huweihadoop101 software]$ tar -zxvf redis-3.2.8.tar.gz -C /opt/module/安装依赖 [huweihadoop101 software]$ sudo yum -y install gcc-c tclRedis是C语言编写的 编译安装…

kafka第二篇——主题创建,主题分区副本分配策略

目录 主题创建 引入 回顾主题&#xff0c;分区&#xff0c;副本关系 代码 主题分区副本分配策略 底层逻辑分析 主题创建 引入 为啥才开始在idea上跑代码&#xff0c;topic都没创建出来&#xff0c;就直接跑出来了? 这是因为AutoTopicCreationManger参数可以自动创建主题…

数据结构经典面试之链表——C#和C++篇

文章目录 一、链表的基本概念二、链表的操作三、定义链表的节点结构体&#xff08;C#&#xff09;四、定义链表的基本操作类&#xff08;C#&#xff09;五、创建一个链表实例并进行基本操作演示&#xff08;C#&#xff09;六、编写一个自定义链表操作函数&#xff08;C&#xf…

复分析——第4章——Fourier变换(E.M. Stein R. Shakarchi)

第4章 Fouier变换 Raymond Edward Alan Christopher Paley, Fellow of Trinity College, Cambridge, and International Research Fellow at the Massachusetts Institute of Technology and at Harvard University, was killed by an avalanche on April 7, 1933, whi…

鞠婧祎多个商标被丝芭传媒申请注册!

近日鞠婧祎与丝芭传媒合约引发网络关注&#xff0c;普推商标老杨经检索发现&#xff0c;丝芭传媒早在2016起就申请注册了“鞠婧祎”24个商标&#xff0c;涉及多个商标分类&#xff0c;这些基本都下商标注册证。 不管对经纪公司还是网红公司&#xff0c;有实力的基本都会对旗下的…

数据可视化实验四:Pyecharts数据可视化

目录 一、使用PyEcharts绘制全国肺炎确诊人数分布图 1.1 柱状图 1.1.2 代码实现 1.1.2 绘制结果 1.2 饼状图 1.2.1 代码实现 1.2.2 绘制结果 1.3 使用over lap实现图形叠加 1.3.1 代码实现 1.3.2 绘制结果 1.4 地图绘制-Map 1.4.1 代码实现 1.4.2 绘制结果 1.5 地…

51学习记录(一)——51介绍及震动感应灯

文章目录 前言一、STC89C522.内部结构及引脚 二、继电器原理及震动传感器原理三、项目搭建及实现 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、STC89C52 1.简介 所属系列&#xff1a;51单…

功率电源中器件的温升与极限工作温度

功率电源中器件的温升与极限工作温度 熟悉电子电路设计的朋友一定都知道,在电源整体设计中存在一些发热非常严重的器件,如整流桥、MOS管、快恢复二极管这些器件。而在功率电源中,电感和高频变压器则成为了发热现象的重灾区。那么在功率电源中,它们的合理温升应该是多少,在…

计算机网络 VLAN间路由单臂路由

一、理论知识 VLAN是一种将物理网络划分成多个逻辑网络的方法。不同的VLAN属于不同的网段&#xff0c;因此互相通信需要通过路由器进行路由。通常情况下&#xff0c;在同一VLAN内的设备可以直接通信&#xff0c;而不同VLAN之间的设备则需要通过路由器转发数据。本实验利用单臂…

html--404页面

<!DOCTYPE html> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <meta http-equiv"X-UA-Compatible" content"IEedge,chrome1"> <title>404 错误页面不存在&…