基于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,一经查实,立即删除!

相关文章

0117__Makefile 中:= ?= += =的区别

Makefile 中: ? 的区别-CSDN博客

【单片机】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 选择设备类型&#…

【ARMv8/ARMv9 硬件加速系列 2 -- ARM NEON 加速运算介绍】

文章目录 ARM NEONNEON 向量寄存器NEON 寄存器使用方式NEON 寄存器的视图NEON 寄存器别名NEON 寄存器的用途ARM NEON 在ARMv8架构中,引入了一组新的寄存器,称为向量寄存器(Vector Registers),用于支持高效的向量和浮点计算。这些寄存器是SIMD(Single Instruction, Multi…

PCL common中常见的基础功能函数

文章目录 一、common模块中的头文件二、common模块中的基本函数1、angles.h2、centroid.h1)计算给定一群点的3D中心点,并且返回一个三维向量2)计算给定的三维点云的协方差矩阵。3)计算正则化的3*3的协方差矩阵以及给定点云数据的中心点4)利用一组点的指数对其进行一般的、…

【SQL】in 和 exists 的区别

在 SQL 中,IN 和 EXISTS 都用于过滤查询结果,但它们在使用场景和性能方面有一些区别。下面是详细的解释: 1. IN IN 操作符用于检查某个值是否存在于一个指定的列表或子查询的结果集中。IN 常用于较小的数据集和静态值列表。 语法 SELECT …

报错:has naming conflicts with other components, ignored

在 Vue 项目编译时,出现如下报错 [unplugin-vue-components] component "xxx"(xxx/Index.vue) has naming conflicts with other components, ignored. [unplugin-vue-components] component "xxx"(xxx/Index.vue) has naming conflicts with …

FFmpeg源码:bytestream2_peek_be32函数分析

一、bytestream2_peek_be32函数的声明 FFmpeg源码中bytestream2_peek_be32函数的声明如下: DEF(unsigned int, be32, 4, AV_RB32, AV_WB32) 该宏定义有多层。把它简化,其函数声明可以等价于: static unsigned int bytestream2_peek_be32(G…

GSettings(一)——Root 权限的服务调用Gsetting失效

错误描述 当在 root 权限下拉起的服务中,调用 Gsetting 设置 key,无法实现持久化,并且命令行查看 key 并没有设置成功,命令行运行会输出如下警告: (process:183768): dconf-WARNING **: 10:55:36.400: failed to comm…

代码随想录——分发饼干(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;} }

springboot中获取某个注解下面的某个方法参数值

要获取方法的参数值&#xff0c;你首先需要有一个类的实例&#xff0c;并且需要能够实际调用该方法。在Java中&#xff0c;方法的参数值是在方法被调用时由调用者传入的&#xff0c;因此&#xff0c;你不能直接从外部“获取”一个未调用方法的参数值。 下面是一个简单的示例&a…

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;成为了提升店铺信誉和吸引潜在顾…

QGraphicsItem 的函数 itemChange 缩放和旋转无信号发出

如果在使用 QGraphicsItem 的 itemChange 函数时&#xff0c;发现缩放和旋转没有信号发出&#xff0c;可能是因为以下几个原因&#xff1a; 未设置正确的标志&#xff1a;确保你已经设置了 QGraphicsItem::ItemSendsGeometryChanges 标志。这个标志告诉 QGraphicsItem 在几何形…