参考文档-Redis快速入门指南-中文
参考文档-Redis 教程
一、启动并连接
本地启动一个redis
或者用redis cloud
免费账户,都可以。
1 | docker run --name CommonTrain -p 6379:6379 -itd redis:7.2 |
然后下载REDISINSIGHT。
二、支持的数据类型
- 字符串
string
- 哈希
hash
- 列表
list
- 集合
set
- 有序集合
sorted set
- 位图
bitmaps
- 基数统计
hyperLogLogs
参考文档-Redis快速入门指南-中文
参考文档-Redis 教程
本地启动一个redis
或者用redis cloud
免费账户,都可以。
1 | docker run --name CommonTrain -p 6379:6379 -itd redis:7.2 |
然后下载REDISINSIGHT。
string
hash
list
set
sorted set
bitmaps
hyperLogLogs
缓存击穿就是大量并发访问同一个热点数据,一旦这个热点数据缓存失效,则请求压力都来到数据库。
1 | @Override |
使用缓存预热,把数据提前放入缓存,然后根据过期时间,发布合理的定时任务,主动去更新缓存,让热点数据永不过期。
Redis之缓存穿透问题解决方案实践SpringBoot3+Docker
当一种请求,总是能越过缓存,调用数据库,就是缓存穿透。
比如当请求一个数据库没有的数据,那么缓存也不会有,然后就一直请求,甚至高并发去请求,对数据库压力会增大。
key
具有某种规则,那么可以对key增加校验机制,不符合直接返回。Redisson
布隆过滤器null
为value
,也插入redis
缓存,但设置较短的过期时间。docker-compose示例如下,redis.conf从这里下载
1 | redis: |
1 | <!-- redis --> |
1 | spring: |
要演示的代码很简单,就是一个携带courseId
请求过来,调用下面的service
函数,然后查询数据库。
1 | @Override |
当我们使用redis改造时,基本代码如下
1 | @Override |
我这里的id
没规则,所以加不了,跳过。
读取yaml
配置
1 | @Data |
配置RedissonClient
1 | @Slf4j |
把布隆过滤器加到service
,如下
1 | private RBloomFilter<String> bloomFilter; |
当数据库没有此数据,以null
为value
,也插入redis
缓存,但设置较短的过期时间。
1 | //后查询数据库 |
Redis之缓存穿透问题解决方案实践SpringBoot3+Docker
缓存雪崩,指大量的缓存失效,大量的请求又同时落在数据库。主要的一种诱因是key设置的过期时间都一样。
加锁,每次只让一个线程可以访问数据库,随后存入缓存。性能太差。
最简单有效的解决办法是设置不同的过期时间。比如
1 | int timeout = 10 + new Random().nextInt(20); |
使用缓存预热,把数据提前放入缓存,然后根据过期时间,发布合理的定时任务,主动去更新缓存。
缓存预热参考代码如下。
1 | @Component |
至于定时任务,可以使用xxl-job。具体使用方法,可以参考这个文章
Docker部署xxl-job调度器并结合SpringBoot测试
通过本文章,可以完成多级缓存架构中的缓存同步。
连接在上一次multiCache
项目中运行的mysql
容器,创建canal
用户。
1 | CREATE USER canal IDENTIFIED BY 'canal'; |
在docker/mysql/conf/my.cnf
添加如下配置
1 | server-id=1000 |
添加canal
服务块到docker-compose.yml
,如下
1 | canal: |
1 | docker pull canal/canal-server:v1.1.7 |
任意启动一个canal-server
容器,将里面的/home/admin/canal-server/conf
文件夹复制到宿主机,对应docker/canal/conf
文件夹。
删除此临时容器。
修改docker/canal/conf/canal.properties
如下条目
1 | canal.destinations=example |
修改docker/canal/conf/example/instance.properties
如下条目
1 | canal.instance.master.address=172.30.3.2:3306 |
pom.xml
1 | <dependency> |
application.yml
1 | canal: |
这是canal-spring-boot-starter
官方仓库,含使用文档
新建canal.ItemHandler
类,内容如下
1 | package com.heima.item.canal; |
修改pojo.Item
类,如下
1 | package com.heima.item.pojo; |
到此为止,docker-compose.yml
内容应该如下
1 | version: '3.8' |
删除原来的multiCache
,重新启动各项服务。
1 | docker-compose -p multi-cache up -d |
启动springboot
程序。
springboot
不断输入类似如下日志,属于正常监听canal
消息中。
1 | 09:27:17:175 INFO 1 --- [l-client-thread] t.j.c.client.client.AbstractCanalClient : 获取消息 Message[id=-1,entries=[],raw=false,rawEntries=[]] |
访问http://localhost:8081/item/10001
,此时信息为tomcat
查询数据库所得数据,而后存入Caffeine
缓存。
访问http://localhost:8080/item.html?id=10001
,此时信息为Redis
缓存数据。
然后,
访问http://localhost:8081/
来到商品管理页面。
修改id=10001的数据的商品分类
确认后
springboot日志出现类似如下日志
1 | 09:31:29:234 INFO 1 --- [l-client-thread] t.j.c.client.client.AbstractCanalClient : 获取消息 Message[id=1,entries=[header { |
这里可以先用redis
连接工具查询数据,发现rediis
已被更新。
再次访问http://localhost:8081/item/10001
直接向springboot
的controller
发送请求,发现caffeine
数据更新,并且springboot
日志没有出现查询记录,说明走的是caffeine
。
通过本文章,可以完成多级缓存架构中的Lua缓存。
在docker/docker-compose.yml
中添加nginx服务块。
1 | nginx: |
删除原来docker里的multiCache
项目并停止springboot
应用。
nginx
部分配置如下,监听端口为8080
,并且将请求反向代理至172.30.3.11
,下一小节,将openresty
固定在172.30.3.11
。
1 | upstream nginx-cluster { |
重新启动multiCache
看看nginx前端网页效果。
1 | docker-compose -p multi-cache up -d |
访问http://localhost:8080/item.html?id=10001
查询id=10001商品页
这里是假数据,前端页面会向/api/item/10001
发送数据请求。
在docker/docker-compose.yml
中添加openresty1
服务块。
1 | openresty1: |
前端向后端发送/api/item/10001
请求关于id=10001商品信息。
根据nginx
的配置内容,这个请求首先被nginx
拦截,反向代理到172.30.3.11
(即openresty1
)。
1 | upstream nginx-cluster { |
openresty1
收到的也是/api/item/10001
,同时,openresty
将/api/item/(\d+)
请求代理到指定lua
程序,在lua
程序中完成数据缓存。
因此,openresty
的conf/conf.d/default.conf
如下
1 | upstream tomcat-cluster { |
conf/nginx.conf
在http
块最后添加3行,引入依赖。
1 | #lua 模块 |
common.lua
被挂载到lualib
,表示可以被其他lua当做库使用,内容如下
1 | -- 创建一个本地缓存对象item_cache |
item.lua
是处理来自形如/api/item/10001
请求的程序,内容如下
1 | -- include |
lua
对item
(tb_item表)和stock
(tb_stock表)两个信息都有缓存,并使用cjson
库将两者合并后返回到前端。expire
时效性的问题,如果后台改变了数据,但是openresty
关于此数据的缓存未过期,前端得到的是旧数据
。nginx
反向代理的能力,还能介入lua
程序进行扩展。到此为止,docker-compose.yml应该如下
1 | version: '3.8' |
启动各项服务
1 | docker-compose -p multi-cache up -d |
启动springboot
程序。
清空openresty
容器日志。
访问http://localhost:8080/item.html?id=10001
查看openresty
容器日志,可以看到两次commonUtils.read_data
都没有缓存,于是代理到tomcat
,可以看到springboot
日志出现查询相关记录。
1 | 2024-01-12 11:45:53 2024/01/12 03:45:53 [error] 7#7: *1 [lua] common.lua:99: read_data(): redis cache miss, try tomcat, key: item:id:10001, client: 172.30.3.3, server: localhost, request: "GET /api/item/10001 HTTP/1.0", host: "nginx-cluster", referrer: "http://localhost:8080/item.html?id=10001" |
再次访问此网址,强制刷新+禁用浏览器缓存+更换浏览器
间隔超过4s但小于1800s时,日志如下,只出现一次miss。
1 | 2024-01-12 11:48:04 2024/01/12 03:48:04 [error] 7#7: *4 [lua] common.lua:99: read_data(): redis cache miss, try tomcat, key: item:stock:id:10001, client: 172.30.3.3, server: localhost, request: "GET /api/item/10001 HTTP/1.0", host: "nginx-cluster", referrer: "http://localhost:8080/item.html?id=10001" |
再次访问此网址,强制刷新+禁用浏览器缓存+更换浏览器
间隔小于4s,日志如下,未出现miss。
1 | 2024-01-12 11:49:16 172.30.3.3 - - [12/Jan/2024:03:49:16 +0000] "GET /api/item/10001 HTTP/1.0" 200 486 "http://localhost:8080/item.html?id=10001" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0" |
对于openresty
高可用,可以部署多个openresty docker
实例,并在nginx
的docker/nginx/conf/conf.d/default.conf
的upstream nginx-cluster
将多个openresty地址添加进去即可。比如
1 | upstream nginx-cluster { |
多个openresty
无论是conf还是lua都保持一致即可。
并且使用hash $request_uri
负载均衡作为反向代理策略,防止同一请求被多个实例缓存数据。
对于springboot
程序高可用,也是类似。可以部署多个springboot docker
实例,并在openresty
的docker/openresty1/conf/conf.d/default.conf
的upstream nginx-cluster
将多个springboot地址添加进去即可。比如
1 | upstream tomcat-cluster { |
通过本文章,可以完成多级缓存架构中的进程缓存。
在item-service
中引入caffeine
依赖
1 | <dependency> |
创建config.CaffeineConfig
类
1 | @Configuration |
在ItemController
中注入两个Cache
对象,并修改业务逻辑
1 | @RestController |
Idea结合Docker将springboot放入docker容器中运行,并指定使用multi-cache_multi-cache
网络,以及固定172.30.3.4
地址。
详细参考如下文章
启动好后,可以看到springboot
容器和mysql容器处于同一网络下。(Docker Desktop for Windows插件PortNavigator)
访问http://localhost:8081/item/10001
可以看到springboot
日志输出如下
1 | 02:45:58:841 DEBUG 1 --- [nio-8081-exec-1] c.h.item.mapper.ItemMapper.selectOne : ==> Preparing: SELECT id,name,title,price,image,category,brand,spec,status,create_time,update_time FROM tb_item WHERE (status <> ? AND id = ?) |
当我们二次访问此网址,强制刷新+禁用浏览器缓存+更换浏览器,springboot
日志都没有新的查询记录,说明使用了Caffeine
缓存。
基于Docker Compose单机实现多级缓存架构2024
Name | Version |
---|---|
Docker Desktop for Windows | 4.23.0 |
Openjdk | 8 |
MySQL | 8.2.0 |
Redis | 7.2 |
Canal | 1.1.7 |
OpenResty | 1.21.4.3-3-jammy-amd64 |
Lua | - |
Caffeine | - |
多级缓存实现过程比较长,将拆分为多个文章
分步讲述。如果一切顺利,大致会得到如下一个多级缓存架构:
本专栏主要对Lua缓存
、Redis缓存
、Caffeine缓存
进行实践,以及缓存同步
实践。依次为以下几篇:
对于高可用,集群等扩展,例如下图的构造,本专栏只包含部分展开
但并不提供实践指导
。
通过本文章,可以完成多级缓存架构中的Redis缓存。
在docker/docker-compose.ym
l中,添加redis服务块
1 | redis: |
在spirngboot
项目启动时,将固定的热点数据提前加载到redis
中。
pom.xml
添加如下依赖
1 | <dependency> |
application.yml
添加如下配置
1 | spring: |
新建config.RedisHandler
类,内容如下,主要是重写afterPropertiesSet
,完成缓存预热逻辑,saveItem
和deleteItemById
函数给之后的章节使用。
1 | @Component |
改进openresty
的docker/openresty1/lualib/common.lua
,如下
1 | local redis = require('resty.redis') |
item.lua
不需要用改动。
到此为止,docker-compose.yml
内容应该如下
1 | version: '3.8' |
删除原来的multiCache
,重新启动各项服务。
1 | docker-compose -p multi-cache up -d |
启动springboot
程序。
springboot
程序启动后,出现查询日志,查看redis
数据库发现自动存入了数据。
清空openresty
容器日志,访问http://localhost:8080/item.html?id=10001
,查看日志,发现两次commonUtils.read_data
都只触发到查询redis
,没到查询tomcat
。
1 | 2024-01-12 16:06:18 2024/01/12 08:06:18 [error] 7#7: *1 [lua] common.lua:59: read_data(): local cache miss, try redis, key: item:id:10001, client: 172.30.3.3, server: localhost, request: "GET /api/item/10001 HTTP/1.0", host: "nginx-cluster", referrer: "http://localhost:8080/item.html?id=10001" |
查看springboot
程序日志,也没有查询记录,说明redis
缓存命中成功。
对于redis
高可用集群,可以参考以下专栏文章。
https://blog.csdn.net/m0_51390969/category_12546314.html?spm=1001.2014.3001.5482
克隆此项目到本地
https://github.com/Xiamu-ssr/MultiCache
来到start
目录下,分别有以下文件夹
docker
:docker相关文件item-service
:springboot项目在docker/docker-compose.yml
中已经定义好如下mysql
块
1 | mysql: |
my.cnf
如下
1 | [mysqld] |
运行以下命令启动docker-compose
1 | docker-compose -p multi-cache up -d |
之后使用数据库连接工具连接mysql
容器,创建heima
数据库,并对其执行docker/mysql/item.sql
脚本。
用idea
打开item-service
文件夹,等待idea
加载本springboot
项目。
如果在
docker-compose
中服务ip改动,请注意一些可能关联的地方也需要做同样改动,比如item-service
的application.yml
1 | spring: |
观察controller
1 | package com.heima.item.web; |