Redis进阶学习08--多级缓存
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis进阶学习08--多级缓存相关的知识,希望对你有一定的参考价值。
Redis进阶学习08--多级缓存
什么是多级缓存
传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图:
存在下面的问题:
• 请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈
• Redis缓存失效时,会对数据库产生冲击
多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压力,提升服务性能:
- 浏览器访问静态资源时,优先读取浏览器本地缓存
- 访问非静态资源(ajax查询数据)时,访问服务端
- 请求到达nginx后,优先读取Nginx本地缓存
- 如果Nginx本地缓存未命中,则去直接查询Redis(不经过Tomcat)
- 如果Redis查询未命中,则查询Tomcat
- 请求进入Tomcat后,优先查询JVM进程缓存
- 如果JVM进程缓存未命中,则查询数据库
在多级缓存架构中,Nginx内部需要编写本地缓存查询、Redis查询、Tomcat查询的业务逻辑,因此这样的nginx服务不再是一个反向代理服务器,而是一个编写业务的Web服务器了。
因此这样的业务Nginx服务也需要搭建集群来提高并发,再有专门的nginx服务来做反向代理,如图:
另外,我们的Tomcat服务将来也会部署为集群模式:
当然redis也可以部署为集群模式,mysql也可以部署为集群模式,nginx反向代理也可以配置多台,然后通过vip漂移,实现反向代理的统一接口访问
可见,多级缓存的关键有两个:
-
一个是在nginx中编写业务,实现nginx本地缓存、Redis、Tomcat的查询
-
另一个就是在Tomcat中实现JVM进程缓存
其中Nginx编程则会用到OpenResty框架结合Lua这样的语言。
JVM进程缓存
环境准备
docker安装mysql
- docker安装mysql—5.7
先准备一个my.cnf配置文件:
[mysqld]
#跳过域名解析
skip-name-resolve
#指定服务器级别的字符集
character_set_server=utf8
#指定数据存放目录
datadir=/dhy/mysql-new-1/data
#MySQL服务的ID
server-id=1000
执行以下docker命令:
docker run \\
-p 3307:3306 \\
--name mysql-new-1 \\
-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
或者采用docker-compose方式–推荐
version: "3.3"
services:
mysql-new-1:
container_name: "msyql-new-1"
image: "mysql:5.7"
ports:
- "3307:3306"
volumes:
- "$PWD/conf:/etc/mysql/conf.d"
- "$PWD/logs:/logs"
- "$PWD/data:/var/lib/mysql"
environment:
#root用户密码
MYSQL_ROOT_PASSWORD: 126433
# docker容器的时区纠正一下
TZ: Asia/Shanghai
MYSQL_USER: dhy #自定义数据库的用户,权限只作用于MYSQL_DATABASE配置的数据库
MYSQL_PASSWORD: dhy #自定义数据库的用户,权限只作用于MYSQL_DATABASE配置的数据库
privileged: true #一定要设置为true,不然数据卷可能挂载不了,启动不起
command:
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
restart: always
注意:如果先执行了第一种方式创建mysql容器,然后再执行第二种方式进行创建,并且数据目录位置不变,那么第二种方式设置的用户密码啥的都会无效,因为第一次创建时,已经将密码持久化到data目录下了,因此需要删除再重新创建一遍data目录才可以
sql文件如下:
create database item;
use item;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_item
-- ----------------------------
DROP TABLE IF EXISTS `tb_item`;
CREATE TABLE `tb_item` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id',
`title` varchar(264) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品标题',
`name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '商品名称',
`price` bigint(20) NOT NULL COMMENT '价格(分)',
`image` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品图片',
`category` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '类目名称',
`brand` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '品牌名称',
`spec` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '规格',
`status` int(1) NULL DEFAULT 1 COMMENT '商品状态 1-正常,2-下架,3-删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `status`(`status`) USING BTREE,
INDEX `updated`(`update_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 50002 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '商品表' ROW_FORMAT = COMPACT;
-- ----------------------------
-- Records of tb_item
-- ----------------------------
INSERT INTO `tb_item` VALUES (10001, 'RIMOWA 21寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4', 'SALSA AIR', 16900, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp', '拉杆箱', 'RIMOWA', '\\"颜色\\": \\"红色\\", \\"尺码\\": \\"26寸\\"', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
INSERT INTO `tb_item` VALUES (10002, '安佳脱脂牛奶 新西兰进口轻欣脱脂250ml*24整箱装*2', '脱脂牛奶', 68600, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t25552/261/1180671662/383855/33da8faa/5b8cf792Neda8550c.jpg!q70.jpg.webp', '牛奶', '安佳', '\\"数量\\": 24', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
INSERT INTO `tb_item` VALUES (10003, '唐狮新品牛仔裤女学生韩版宽松裤子 A款/中牛仔蓝(无绒款) 26', '韩版牛仔裤', 84600, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t26989/116/124520860/644643/173643ea/5b860864N6bfd95db.jpg!q70.jpg.webp', '牛仔裤', '唐狮', '\\"颜色\\": \\"蓝色\\", \\"尺码\\": \\"26\\"', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
INSERT INTO `tb_item` VALUES (10004, '森马(senma)休闲鞋女2019春季新款韩版系带板鞋学生百搭平底女鞋 黄色 36', '休闲板鞋', 10400, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t1/29976/8/2947/65074/5c22dad6Ef54f0505/0b5fe8c5d9bf6c47.jpg!q70.jpg.webp', '休闲鞋', '森马', '\\"颜色\\": \\"白色\\", \\"尺码\\": \\"36\\"', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
INSERT INTO `tb_item` VALUES (10005, '花王(Merries)拉拉裤 M58片 中号尿不湿(6-11kg)(日本原装进口)', '拉拉裤', 38900, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t24370/119/1282321183/267273/b4be9a80/5b595759N7d92f931.jpg!q70.jpg.webp', '拉拉裤', '花王', '\\"型号\\": \\"XL\\"', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
-- ----------------------------
-- Table structure for tb_item_stock
-- ----------------------------
DROP TABLE IF EXISTS `tb_item_stock`;
CREATE TABLE `tb_item_stock` (
`item_id` bigint(20) NOT NULL COMMENT '商品id,关联tb_item表',
`stock` int(10) NOT NULL DEFAULT 9999 COMMENT '商品库存',
`sold` int(10) NOT NULL DEFAULT 0 COMMENT '商品销量',
PRIMARY KEY (`item_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;
-- ----------------------------
-- Records of tb_item_stock
-- ----------------------------
INSERT INTO `tb_item_stock` VALUES (10001, 99996, 3219);
INSERT INTO `tb_item_stock` VALUES (10002, 99999, 54981);
INSERT INTO `tb_item_stock` VALUES (10003, 99999, 189);
INSERT INTO `tb_item_stock` VALUES (10004, 99999, 974);
INSERT INTO `tb_item_stock` VALUES (10005, 99999, 18649);
SET FOREIGN_KEY_CHECKS = 1;
然后就是各位自己动手去搭建一个springboot项目,连接这个数据库,然后完成相关CURD简单接口测试,这里不做展示
docker安装nginx
因为我们的项目是动静分离的,静态资源全部放在了nginx上面,因此我们还需要利用docker安装一台nginx,然后将相关静态资源放入nginx中
我们需要准备一个反向代理的nginx服务器,如上图红框所示,将静态的商品页面放到nginx目录中。
页面需要的数据通过ajax向服务端(nginx业务集群)查询。
docker安装nginx步骤:
- docker pull nginx(已有镜像,跳过此步骤)
- 创建nginx挂载目录
mkdir -p html conf.d ssl log
- 运行nginx容器
docker run -d -p 80:80 --name nginx --privileged=true nginx
- 拷贝必要配置文件到宿主机
docker cp 用于容器与主机之间的数据拷贝,
语法 :
docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
参数 :
- CONTAINER : 运行中的容器ID
- -L :保持源目标中的链接
使用参考:
文件:将主机/www/1.conf 拷贝到容器96f7f14e99ab的test目录下
docker cp /www/1.conf 96f7f14e99ab:/test/
文件:将容器96f7f14e99ab中www目录下的12.conf文件,拷贝到主机的/目录中
docker cp 96f7f14e99ab:/www/2.conf /
目录:将主机/www/runoob目录拷贝到容器96f7f14e99ab中,目录重命名为www
docker cp /www/runoob 96f7f14e99ab:/www
目录:将容器96f7f14e99ab的/www目录拷贝到主机的/tmp目录中
docker cp 96f7f14e99ab:/www /tmp/
我们这里需要做的就是将容器中的nginx.conf拷贝到宿主机目录下
docker cp nginx:/etc/nginx/nginx.conf ./
- 删除nginx示例容器
docker rm -f nginx
- 生成最终的nginx容器
docker run
-itd
--restart=always
-p 80:80
-p 443:443
-v $PWD/html:/usr/share/nginx/html
-v $PWD/conf.d/:/etc/nginx/conf.d
-v $PWD/ssl/:/etc/nginx/ssl
-v $PWD/log/:/var/log/nginx
-v $PWD/nginx.conf:/etc/nginx/nginx.conf
--name nginx
--privileged=true
nginx
docker-compose.yml方式来管理nginx
version: "3.3"
services:
nginx-proxy:
container_name: "nginx-proxy"
image: "nginx"
ports:
- "80:80"
- "433:433"
volumes:
- "$PWD/nginx-proxy/html:/etc/nginx/html"
- "$PWD/nginx-proxy/conf.d:/etc/nginx/conf.d"
- "$PWD/nginx-proxy/ssl:/etc/nginx/ssl"
- "$PWD/nginx-proxy/log:/var/log/nginx"
- "$PWD/nginx-proxy/nginx.conf:/etc/nginx/nginx.conf"
privileged: true
restart: always
如果我们修改了配置文件,想进行热更新的话:
docker exec -it nginx-proxy nginx -s reload
nginx配置文件和静态资源管理:
user root;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events
worker_connections 1024;
http
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#nginx集群负载均衡配置
upstream nginx-cluster
server 具体服务器地址:8081;
server 具体服务器地址:8082;
server
listen 80;
server_name 具体服务器地址;
location /api
proxy_pass http://nginx-cluster;
#gzip on;
include /etc/nginx/conf.d/*.conf;
conf.d目录下面的default.conf
server
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
#兜底解决方案
location /
#所有静态资源都会去这个目录下面找
root /usr/share/nginx/html;
#如果只是/,那么取查找root指定的目录下查找首页资源
index index.html index.htm;
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html
root /usr/share/nginx/html;
静态资源统一放到html静态资源目录下面即可:
访问item.html进行测试:
反向代理
现在,页面是假数据展示的。我们需要向服务器发送ajax请求,查询商品数据。
打开控制台,可以看到页面有发起ajax查询数据:
而这个请求地址同样是80端口,所以被当前的nginx反向代理了。
查看nginx的conf目录下的nginx.conf文件:
其中的192.168.150.101是我的虚拟机IP,也就是我的Nginx业务集群要部署的地方:
初识Caffeine
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:
- 分布式缓存,例如Redis:
- 优点:存储容量更大、可靠性更好、可以在集群间共享
- 缺点:访问缓存有网络开销
- 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
- 进程本地缓存,例如HashMap、GuavaCache:
- 优点:读取本地内存,没有网络开销,速度更快
- 缺点:存储容量有限、可靠性较低、无法共享
- 场景:性能要求较高,缓存数据量较小
我们今天会利用Caffeine框架来实现JVM进程缓存。
Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址:https://github.com/ben-manes/caffeine
Caffeine的性能非常好,下图是官方给出的性能对比:
可以看到Caffeine的性能遥遥领先!
缓存使用的基本API:
/*
基本用法测试
*/
@Test
void testBasicOps()
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder().build();
// 存数据
cache.put("gf", "迪丽热巴");
// 取数据,不存在则返回null
String gf = cache.getIfPresent("gf");
System.out.println("gf = " + gf);
// 取数据,不存在则去数据库查询
String defaultGF = cache.get("defaultGF", key ->
// 这里可以去数据库根据 key查询value
return "柳岩";
);
System.out.println("defaultGF = " + 以上是关于Redis进阶学习08--多级缓存的主要内容,如果未能解决你的问题,请参考以下文章