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缓存预热的执行是与项目启动一起的,不需要用户发送请求,是在项目启动时自发的操作。

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

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

相关文章

不懂这些专业名词,你很难成为有水平的项目经理——数据分析篇

大家好&#xff0c;我是老原。 前段时间我们项目组招了个新人小林&#xff0c;让他去和产品经理对下产品上线情况&#xff0c;等到下班也没等来反馈。 第二天在茶水间遇到了产品经理就问了一嘴&#xff0c;才知道已经对接到位了。 一问小林才知道&#xff0c;他完全不知道产…

《剑指offer》(4)二叉树篇

二叉树深度有两种递归思路&#xff1a; &#xff08;1&#xff09;递归返回当前的深度&#xff0c;当root是空时&#xff0c;返回0 &#xff08;2&#xff09;将当前深度和节点一起传入递归&#xff0c;设置全局变量&#xff0c;每经过一个节点就更新全局变量的值。 方法一&a…

高速公路巡检无人机,为何成为公路巡检的主流工具

随着无人机技术的不断发展&#xff0c;无人机越来越多地应用于各个领域。其中&#xff0c;在高速公路领域&#xff0c;高速公路巡检无人机已成为公路巡检的得力助手。高速公路巡检无人机之所以能够成为公路巡检中的主流工具&#xff0c;主要是因为其具备以下三大特性。 一、高速…

iOS——Block回调

先跟着我实现最简单的 Block 回调传参的使用&#xff0c;如果你能举一反三&#xff0c;基本上可以满足了 OC 中的开发需求。已经实现的同学可以跳到下一节。 首先解释一下我们例子要实现什么功能&#xff08;其实是烂大街又最形象的例子&#xff09;&#xff1a; 有两个视图控…

Vector - CAPL - 诊断模块函数(连接管理)

CanTpCreateConnection - 创建TP连接 功能&#xff1a;使用给定的地址模式&#xff08;add人Mode&#xff09;创建新连接&#xff0c;可用于诊断数据的收发。 说明&#xff1a;无法更改已有连接的寻址模式&#xff1b;如果确实有需要&#xff0c;可以关闭当前连接后再创建一个…

复习之linux系统的引导修复

启动Linux系统时&#xff0c;需要先通电&#xff0c;接着系统会自动进行bios初始化&#xff0c;对硬件进行检测并初始化硬件时钟&#xff0c;之后就进入了 Linux系统引导过程。Linux系统引导过程的具体内容和引导修复方法将在下文中进行详细介绍。由于我们在引导修复时需要利用…

Mac 终端快捷键设置:如何给 Mac 中的 Terminal 设置 Ctrl+Alt+T 快捷键快速启动

Mac 电脑中正常是没有直接打开终端命令行的快捷键指令的&#xff0c;但可以通过 commandspace 打开聚焦搜索&#xff0c;然后输入 ter 或者 terminal 全拼打开。但习惯了 linux 的同学会觉得这个操作很别扭。于是我们希望能通过键盘按键直接打开。 操作流程如下&#xff1a; 1…

LangChain+ChatGLM整合LLaMa模型(二)

开源大模型语言LLaMa LLaMa模型GitHub地址添加LLaMa模型配置启用LLaMa模型 LangChainChatGLM大模型应用落地实践&#xff08;一&#xff09; LLaMa模型GitHub地址 git lfs clone https://huggingface.co/huggyllama/llama-7b添加LLaMa模型配置 在Langchain-ChatGLM/configs/m…

16.M端事件和JS插件

16.1移动端 移动端也有自己独特的地方 ●触屏事件touch (也称触摸事件)&#xff0c;Android 和I0S都有。 ●touch对象代表一个触摸点。触摸点可能是一根手指&#xff0c;也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。 ●常见的触屏事件如下: …

VS2017中Qt工程报错:无法解析的外部符号 __imp_CommandLineToArgvW,该符号在函数 WinMain 中被引用

工程报错:无法解析的外部符号 __imp_CommandLineToArgvW&#xff0c;该符号在函数 WinMain 中被引用 解决方法&#xff1a; 在输入的附加依赖项中增加 shell32.lib

装饰器模式(Decorator)

装饰器模式是一种结构型设计模式&#xff0c;用来动态地给一个对象增加一些额外的职责。就增加对象功能来说&#xff0c;装饰器模式比生成子类实现更为灵活。装饰器模式的别名为包装器(Wrapper)&#xff0c;与适配器模式的别名相同&#xff0c;但它们适用于不同的场合。 Decor…

《长安的荔枝》阅读笔记

《长安的荔枝》阅读笔记 2023年6月9号在杭州的小屋读完&#xff0c;作者以“一骑红尘妃子笑”的典故&#xff0c;想象拓展出来的荔枝使李善德&#xff0c;为了皇帝要求在贵妃寿辰&#xff0c;六月一号那天要吃到10斤的荔枝。需要从广州运送到长安即如今的西安。本来以为这个差事…

Elasticsearch:如何将整个 Elasticsearch 索引导出到文件 - Python 8.x

在实际的使用中&#xff0c;我们有时希望把 Elasticsearch 的索引保存到 JSON 文件中。在之前&#xff0c;我写了一篇管如何备份 Elasticsearch 索引的文章 “Elasticsearch&#xff1a;索引备份及恢复”。在今天&#xff0c;我们使用一种 Python 的方法来做进一步的探讨。你可…

RabbitMQ安装说明文档-v2.0

rabbitmq安装 说明&#xff1a;请使用资料里提供的CentOS-7-x86_64-DVD-1810.iso 安装虚拟机. 1. 安装依赖环境 在线安装依赖环境&#xff1a; yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel m4 ncurses-devel …

瞄准产业应用,大模型加持的深兰科技AI虚拟数字人落地业务场景

伴随ChatGPT的问世&#xff0c;在技术与商业运作上都日渐发展成熟的AI数字人产业正持续升温。 目前的AI数字人不仅拥有超高“颜值”&#xff0c;同时还拥有更为丰富的、细腻的表情和动作。更有甚者&#xff0c;AI数字人已经具备自定义构建知识图谱、自主对话、不断学习成长的能…

C++类与对象(下)

目录 初始化列表单参数构造函数的隐式类型转换 static成员友元友元函数友元类 内部类匿名对象了解&#xff1a;编译器优化练习题 初始化列表 构造函数体中的语句只能将其称为赋初值&#xff0c;而不能称作初始化。因为初始化只能初始化一次&#xff0c;而构造函数体内可以多次…

【论文】【生成对抗网络五】Wasserstein GAN (WGAN)

【题目、作者】&#xff1a; 紫色&#xff1a;要解决的问题或发现的问题 红色&#xff1a;重点内容 棕色&#xff1a;关联知识&#xff0c;名称 绿色&#xff1a;了解内容&#xff0c;说明内容 论文地址&#xff1a; 论文下载 本篇文章仅为原文翻译&#xff0c;仅作参考。…

jar命令的安装与使用

场景&#xff1a; 项目中经常遇到使用WinR软件替换jar包中的文件&#xff0c;有时候存在WinRAR解压替换时提示没有权限&#xff0c;此时winRAR不能用还有有什么方法替换jar包中的文件。 方法&#xff1a; 使用jar命令进行修改替换 问题&#xff1a; 执行jar命令报错jar 不…

[用go实现解释器]笔记1-词法分析

本文是《用go实现解释器》的读书笔记 ​ https://malred-blog​malred.github.io/2023/06/03/ji-suan-ji-li-lun-ji-shu-ji/shi-ti/go-compile/yong-go-yu-yan-shi-xian-jie-shi-qi/go-compiler-1/#toc-heading-6http://个人博客该笔记地址 ​github.com/malred/malanghttp:/…

Ubuntu网络设置之固定IP详解

尊敬的家人们&#xff0c;欢迎观看我的文章&#xff01;今天&#xff0c;我们将为您介绍Ubuntu22.04操作系统中固定IP的设置方法&#xff0c;帮助您更好地管理网络连接并提高网络稳定性。 什么是固定IP&#xff1f; 在网络中&#xff0c;IP地址是设备在网络上的唯一标识。通常…