Redis缓存预热
说明:项目中使用到Redis,正常情况,我们会在用户首次查询数据的同时把该数据按照一定命名规则,存储到Redis中,称为冷启动(如下图),这种方式在一些情况下可能会给数据库带来较大的压力。
因此,我们可以使用另一种方式,在项目启动的时候就提前把一些热点数据提前查询并保存到Redis中,称为缓存预热。
(冷启动)

环境准备
例如,现在我数据库中有以下用户的信息,我想在项目启动的时候就把这些数据存入到数据库中;

以下操作在CentOS系统中完成;
代码实现
第一步:安装OpenResty
OpenResty是基于Nginx的高性能Web平台,可方便地搭建能够超过并发、扩展性极高的动态Web应用、Web服务和动态网关。OpenResty具备以下特点:
-
Nginx的完整功能;
-
基于Lua语言,集成了大量精良的Lua库、第三方模块;
-
允许使用Lua自定义业务逻辑、自定义库;
可参考安装OpenResty,这不是本文的重点;
第二步:配置环境变量
安装完OpenResty之后,先配置一下Nginx的环境变量;
vi /etc/profile
在文件最下面,加入下面两行配置
export NGINX_HOME=/usr/local/openresty/nginx
export PATH=${NGINX_HOME}/sbin:$PATH

保存退出,在敲下面的命令,使配置生效
source /etc/profile

第三步:修改配置
OpenResty会默认安装在/usr/local/openresty路径下,可在该目录在看到一个Nginx目录,也就是说OpenResty拥有Nginx的功能。进入到Nginx目录,找到配置文件,进行自适应修改:

如下修改:
#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;}# 设置捕捉的请求路径location /user {# 默认的响应类型default_type application/json;# 响应结果由lua/user.lua文件来决定,待会儿我们来编写这个文件content_by_lua_file lua/user.lua;}}#lua 模块lua_package_path "/usr/local/openresty/lualib/?.lua;;";#c模块 lua_package_cpath "/usr/local/openresty/lualib/?.so;;"; # 共享字典,就是本地缓存,名称为user_cache,大小150mlua_shared_dict user_cache 150m;
}
第四步:启动测试
OpenResty启动、停止、重新加载配置的命令与Nginx基本一样,敲nginx启动OpenResty;
# 启动服务
nginx# 停止服务
ngxin -s stop# 重新加载配置
ngxin -s stop

敲IP地址加端口号,看到以下内容,即为安装成功

第五步:启动MySQL、Redis服务
接下来,使用Docker启动mysql、redis服务,注意使用云服务器需要开放相关端口,使用虚拟机需要关闭防火墙;
在启动mysql之前,先在/tmp目录下创建一个mysql文件夹,用于挂载容器的数据和配置文件目录;
# 进入/tmp目录
cd /tmp# 创建文件夹
mkdir mysql# 进入mysql目录
cd mysql

启动mysql服务,账号密码设置为:root、123456(注意以下命令需要在mysql目录下执行)
docker run \-p 3306:3306 \--name mysql \-v $PWD/conf:/etc/mysql/conf.d \-v $PWD/logs:/logs \-v $PWD/data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD=123456 \--privileged \-d \mysql:5.7.25

敲下面的命令,使用docker启动redis服务;
docker run --name redis -p 6379:6379 -d redis redis-server --appendonly yes

第六步:编写代码
服务启动后,来进行代码的编写,以下是一个简单的SpringBoot项目,DAO层使用的是Mybatis-plus,只有两个接口,一个查询所有用户信息,一个根据用户ID查询用户信息。
controller层代码
import java.util.List;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;/*** 查询所有用户信息* @return*/@GetMapping("list")public List<User> getUsers() {return userService.list();}/*** 根据ID查询用户信息* @param id* @return*/@GetMapping("{id}")public User getUserById(@PathVariable Long id) {return userService.getById(id);}
}
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.hzy</groupId><artifactId>caffeine_demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version><relativePath/></parent><dependencies><!--web依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!--mysql驱动--><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!--druid数据库连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version></dependency><!--测试类--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--hutool工具包依赖--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.6</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
配置文件,注意IP地址和端口号,如果使用的是云服务器,注意MySQL连接时需要加上useSSL=false;
server:port: 8081
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://IP地址:3306/db_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8username: rootpassword: 123456mvc:pathmatch:matching-strategy: ant_path_matcherredis:host: IP地址
mybatis-plus:type-aliases-package: com.hzy.pojoconfig-locations: classpath/mapper/*.xml
第七步:启动测试
这些环境搭建完成后,应该启动一下项目,看是否能正常跑起来;
(启动正常)

(测试接口,也没问题)

第八步:缓存预热实现
创建一个RedisHandler类,该类的作用是,在项目启动时访问数据库,查询所有用户信息并存入到数据库中;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hzy.pojo.User;
import com.hzy.service.UserService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.List;@Component
public class RedisHandler implements InitializingBean {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate UserService userService;private static final ObjectMapper MAPPER = new ObjectMapper();@Overridepublic void afterPropertiesSet() throws Exception {// 初始化缓存// 1.查询所有用户信息List<User> userList = userService.list();// 2.放入缓存for (User user : userList) {// 2.1.将user对象序列化为JSONString json = MAPPER.writeValueAsString(user);// 2.2.设置key前缀,存入redisredisTemplate.opsForValue().set("user:id:" + user.getId(), json);}}
}
第九步:编写lua文件
接下来,需要编写两个lua文件,一个是通用操作(common.lua),一个是redis的查询操作(user.lua),内容分别如下:
common.lua文件在 /usr/local/openresty/lualib/common.lua 里面,使用以下命令编辑,如下:
vi /usr/local/openresty/lualib/common.lua
内容如下:
-- 导入redis
local redis = require('resty.redis')
-- 初始化redis
local red = redis:new()
red:set_timeouts(1000, 1000, 1000)-- 关闭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 thenngx.log(ngx.ERR, "放入redis连接池失败: ", err)end
end-- 查询redis的方法 ip和port是redis地址,key是查询的key
local function read_redis(ip, port, key)-- 获取一个连接local ok, err = red:connect(ip, port)if not ok thenngx.log(ngx.ERR, "连接redis失败 : ", err)return nilend-- 查询redislocal resp, err = red:get(key)-- 查询失败处理if not resp thenngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)end--得到的数据为空处理if resp == ngx.null thenresp = nilngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)endclose_redis(red)return resp
end-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)local resp = ngx.location.capture(path,{method = ngx.HTTP_GET,args = params,})if not resp then-- 记录错误信息,返回404ngx.log(ngx.ERR, "http查询失败, path: ", path , ", args: ", args)ngx.exit(404)endreturn resp.body
end
-- 将方法导出
local _M = { read_http = read_http,read_redis = read_redis
}
return _M
user.lua文件是自定义的,我们在openresty目录下创建一个lua文件夹,用于存放该文件夹
cd /usr/local/openrestymkdir lua

user.lua文件内容如下:
-- 导入common函数库
local common = require('common')
local read_http = common.read_http
local read_redis = common.read_redis
-- 导入cjson库
local cjson = require('cjson')
-- 导入共享词典,本地缓存
local user_cache = ngx.shared.user_cache-- 封装查询函数
function read_data(key, expire, path, params)-- 查询本地缓存local val = user_cache:get(key)if not val thenngx.log(ngx.ERR, "本地缓存查询失败,尝试查询Redis, key: ", key)-- 查询redisval = read_redis("127.0.0.1", 6379, key)-- 判断查询结果if not val thenngx.log(ngx.ERR, "redis查询失败,尝试查询http, key: ", key)-- redis查询失败,去查询httpval = read_http(path, params)endend-- 查询成功,把数据写入本地缓存user_cache:set(key, val, expire)-- 返回数据return val
end-- 获取路径参数
local id = ngx.var[1]-- 查询商品信息
local userJSON = read_data("user:id:" .. id, 1800, "/user/" .. id, nil)-- JSON转化为lua的table
local user = cjson.decode(userJSON)-- 把user序列化为json 返回结果
ngx.say(cjson.encode(user))
第十步:重启项目
此时在云服务器上进入redis容器,打开redis客户端,查看数据内容;
# 进入redis容器
docker exec -it redis bash
# 打开redis客户端
redis-cli
# 查看redis所有的键值
keys *
什么都没有,nothing;

此时,我们重启项目;

再敲命令,查看redis所有值,可以看到所有的数据都已经存入了,可根据key查询到每一条用户的信息;

到此,Redis缓存预热已完成;
总结
Redis缓存预热的执行是与项目启动一起的,不需要用户发送请求,是在项目启动时自发的操作。
相关文章:
Redis缓存预热
说明:项目中使用到Redis,正常情况,我们会在用户首次查询数据的同时把该数据按照一定命名规则,存储到Redis中,称为冷启动(如下图),这种方式在一些情况下可能会给数据库带来较大的压力…...
Android 耗时分析(adb shell/Studio CPU Profiler/插桩Trace API)
1.adb logcat 查看冷启动时间和Activity显示时间: 过滤Displayed关键字,可看到Activity的显示时间 那上面display后面的是时间是指包含哪些过程的时间呢? 模拟在Application中沉睡1秒操作,冷启动情况下: 从上可知&…...
保护隐私与安全的防关联、多开浏览器
随着互联网的不断发展,我们越来越离不开浏览器这个工具,它为我们提供了便捷的网络浏览体验。然而,随着我们在互联网上的活动越来越多,我们的个人信息和隐私也日益暴露在网络风险之下。在这种背景下,为了保护个人隐私和…...
CloudStudio搭建Next框架博客_抛开电脑性能在云端编程(沉浸式体验)
文章目录 ⭐前言⭐进入cloud studio工作区指引💖 注册coding账号💖 选择cloud studio💖 cloud studio选择next.js💖 安装react的ui框架(tDesign)💖 安装axios💖 代理请求跨域&#x…...
【FPGA IP系列】FIFO深度计算详解
FIFO(First In First Out)是一种先进先出的存储结构,经常被用来在FPGA设计中进行数据缓存或者匹配传输速率。 FIFO的一个关键参数是其深度,也就是FIFO能够存储的数据条数,深度设计的合理,可以防止数据溢出,也可以节省…...
JavaScript中语句和表达式
在JavaScript编程中,Statements和Expressions都是代码的构建块,但它们有不同的特点和用途。 ● Statements(语句)是执行某些操作的完整命令;每个语句通常以分号结束。例如,if语句、for语句、switch语句、函…...
打卡力扣题目十
#左耳听风 ARST 打卡活动重启# 目录 一、题目 二、解决方法一 三、解决方法二 关于 ARTS 的释义 —— 每周完成一个 ARTS: ● Algorithm: 每周至少做一个 LeetCode 的算法题 ● Review: 阅读并点评至少一篇英文技术文章 ● Tips: 学习至少一个技术技巧 ● Shar…...
UniApp实现API接口封装与请求方法的设计与开发方法
UniApp实现API接口封装与请求方法的设计与开发方法 导语:UniApp是一个基于Vue.js的跨平台开发框架,可以同时开发iOS、Android和H5应用。在UniApp中,实现API接口封装与请求方法的设计与开发是一个十分重要的部分。本文将介绍如何使用UniApp实…...
利用小波分解信号,再重构
function [ output_args ] example4_5( input_args ) %EXAMPLE4_5 Summary of this function goes here % Detailed explanation goes here clc; clear; load leleccum; s leleccum(1:3920); % 进行3层小波分解,小波基函数为db2 [c,l] wavedec(s,3,db2); %进行…...
QT数据库编程
ui界面 mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include <QButtonGroup> #include <QFileDialog> #include <QMessageBox> MainWindow::MainWindow(QWidget* parent): QMainWindow(parent), ui(new Ui::M…...
基于stm32单片机的直流电机速度控制——LZW
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 一、实验目的二、实验方法三、实验设计1.实验器材2.电路连接3.软件设计(1)实验变量(2)功能模块a)电机接收信号…...
实际项目中使用mockjs模拟数据
项目中的痛点 自己模拟的数据对代码的侵入程度太高,接口完成后要删掉对应的代码,导致接口开发完后端同事开发完,前端自己得加班;接口联调的时间有可能会延期,接口完成的质量参差不齐;对于数据量过大的模拟…...
【家庭公网IPv6】
家庭公网IPv6 这里有两个网站: 1、 IPV6版、多地Tcping、禁Ping版、tcp协议、tcping、端口延迟测试,在本机搭建好服务器后,可以用这个测试外网是否可以访问本机; 2、 IP查询ipw.cn,这个可以查询本机的网络是否IPv6访问…...
【iOS】Frame与Bounds的区别详解
iOS的坐标系 iOS特有的坐标是,是在iOS坐标系的左上角为坐标原点,往右为X正方向,向下为Y正方向。 bounds和frame都是属于CGRect类型的结构体,系统的定义如下,包含一个CGPoint(起点)和一个CGSiz…...
SpringBoot百货超市商城系统 附带详细运行指导视频
文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码 一、项目演示 项目演示地址: 视频地址 二、项目介绍 项目描述:这是一个基于SpringBoot框架开发的百货超市系统。首先,这是一个很适合SpringBoot初学者学习的项目,代…...
【实践篇】推荐算法PaaS化探索与实践 | 京东云技术团队
作者:京东零售 崔宁 1. 背景说明 目前,推荐算法部支持了主站、企业业务、全渠道等20业务线的900推荐场景,通过梳理大促运营、各垂直业务线推荐场景的共性需求,对现有推荐算法能力进行沉淀和积累,并通过算法PaaS化打造…...
持续贡献开源力量,棱镜七彩加入openKylin
近日,棱镜七彩签署 openKylin 社区 CLA(Contributor License Agreement 贡献者许可协议),正式加入openKylin 开源社区。 棱镜七彩成立于2016年,是一家专注于开源安全、软件供应链安全的创新型科技企业。自成立以来&…...
Kafka的消费者如何管理偏移量?
在Kafka中,消费者可以通过管理和跟踪偏移量(offset)来确保消费者在消费消息时的准确性和可靠性。偏移量表示消费者在特定分区中已经消费的消息的位置。以下是几种常见的偏移量管理方式: 手动提交偏移量:消费者可以通过…...
IntelliJ IDEA流行的构建工具——Gradle
IntelliJ IDEA,是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。 如…...
nacos源码打包及相关配置
nacos 本地下载后,需要 install 下: mvn clean install -Dmaven.test.skiptrue -Dcheckstyle.skiptrue -Dpmd.skiptrue -Drat.skiptruenacos源码修改后,重新打包生成压缩包命令:在 distribution 目录中运行: mvn -Pr…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
DiscuzX3.5发帖json api
参考文章:PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下,适配我自己的需求 有一个站点存在多个采集站,我想通过主站拿标题,采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...
