redis进阶学习08--多级缓存(代码片段)

大忽悠爱忽悠 大忽悠爱忽悠     2023-03-11     163

关键词:

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

MySQL之my.cnf配置文件详解大全

执行以下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

缓存框架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 = " + 

微服务框架springcloud微服务架构多级缓存48多级缓存48.7redis缓存预热(代码片段)

...详解springcloud微服务技术栈课程|黑马程序员Java微服务】多级缓存文章目录微服务框架多级缓存48多级缓存48.7Redis缓存预热48.7.1添加Redis缓存的需求48.7.2冷启动和缓存预热48.7.3缓存预热48多级缓存48.7Redis缓存预热48.7.1添加Redis缓存的... 查看详情

计算机多级缓存架构和mesi缓存一致性协议(代码片段)

...议)做下介绍,为后面的JMM模型、volatile关键字的学习打下基础。一,现代计算机硬件基本结构1.总线上面图中大部分都比较好理解(如果想详细了解的话可以看下计算机操 查看详情

redis从入门到进阶第6讲:缓存雪崩击穿穿透场景与解决方案(代码片段)

本文已收录于专栏🍅《Redis从入门到进阶》🍅专栏前言  本专栏开启,目的在于帮助大家更好的掌握学习Redis,同时也是为了记录我自己学习Redis的过程,将会从基础的数据类型开始记录,直到一些更多... 查看详情

redis进阶学习01---基础回顾(代码片段)

Redis进阶学习01---基础回顾redis使用docker安装常用命令常用通用命令String类型常用命令key的层级表示Hash类型常用命令List类型set类型sortedSet类型Redis客户端JedisJedis连接池SpringDataRedis快速入门序列化问题+源码追踪分析替换序列化... 查看详情

redis学习(代码片段)

文章目录一.redis1.什么是Redis2.Redis有哪些优缺点3.为什么要用Redis/为什么要用缓存4.为什么要用Redis而不用map/guava做缓存?5.Redis为什么这么快二.数据类型1.Redis有哪些数据类型2.String3.List4.Set5.Hash6.Zset三.事务1.什么是事务?2.Redis... 查看详情

基于多级缓存的充电系统优化实践(代码片段)

基于多级缓存的充电系统优化实践原文是发表在文章中,刚看了下文章主要用于转载,因此在随笔中重新发布一下。摘要正如前文中《海量并发下充电业务优化实践》所述,在充电过程中由于涉及到大量的实时数据处理,随着设... 查看详情

探讨下如何更好的使用缓存——redis缓存的特殊用法以及与本地缓存一起构建多级缓存的实现(代码片段)

本篇文章,我们就一起聊一聊如何来更好的使用缓存,探寻下如何降低缓存交互过程的性能损耗、如何压缩缓存的存储空间占用、如何保证多个操作命令原子性等问题的解决策略,让缓存在项目中可以发挥出更佳的效果。大家好... 查看详情

mybatis进阶功能介绍(代码片段)

前言MyBatis本是apache的一个开源项目iBatis,而我们对于mybatis上的使用包括最基本的使用CRUD上的使用,而本篇文章会除了会介绍CRUD的使用、以及集成redis,以及如何开启多级缓存,多数据源,以及Mybatis插件实现原理... 查看详情

eureka源码之server端的多级缓存机制(代码片段)

一、前言上一讲我们讲到了Eureka注册中心的Server端有三级缓存来保存注册信息,可以利用缓存的快速读取来提高系统性能。我们再来细看下:一级缓存:只读缓存readOnlyCacheMap,数据结构ConcurrentHashMap。相当于数据库。二级缓存:... 查看详情

django2.2cache缓存的设计以及几种方式的多级或单级缓存处理(代码片段)

...---》  https://blog.csdn.net/AkiraNicky/article/details/82533316多级缓存(Django内置了多中种缓存并存的方式的处理)一、django几种缓存的配置 其中配置中有几点要说明,Cache的缓存默认是default(这里的名字是可以自己取的,就像... 查看详情

vue进阶知识总结(代码片段)

vue进阶知识总结脚手架文件结构关于不同版本的Vuevue.config.js配置文件ref属性props配置项mixin(混入)插件scoped样式总结TodoList案例webStorage组件的自定义事件全局事件总线(GlobalEventBus)消息订阅与发布(pubsub)nextTickVu... 查看详情

vue进阶知识总结(代码片段)

vue进阶知识总结脚手架文件结构关于不同版本的Vuevue.config.js配置文件ref属性props配置项mixin(混入)插件scoped样式总结TodoList案例webStorage组件的自定义事件全局事件总线(GlobalEventBus)消息订阅与发布(pubsub)nextTickVu... 查看详情

redis应用问题解决(代码片段)

...存雪崩3.1、简介3.2、解决方案将缓存失效时间分散开构建多级缓存架构使用锁或队列高可用服务降级概念图缓存使用流程1、缓存穿透1.1、简介场景:缓存和数据库中都没有的数据,而用户不断发起请求。每次从缓存中都... 查看详情

redis应用问题解决(代码片段)

...存雪崩3.1、简介3.2、解决方案将缓存失效时间分散开构建多级缓存架构使用锁或队列高可用服务降级概念图缓存使用流程1、缓存穿透1.1、简介场景:缓存和数据库中都没有的数据,而用户不断发起请求。每次从缓存中都... 查看详情

redis学习总结(下)——哨兵模式集群应用问题解决(代码片段)

Redis学习总结(下)——哨兵模式、集群、应用问题解决一、哨兵模式(sentinel)1.1哨兵模式的简介1.2启动哨兵模式1.2.1部署哨兵节点(配置三个哨兵)1.2.2演示故障转移二、集群2.1Redis集群搭建2.2slots(插槽)2... 查看详情

flask-web缓存redis——架构缓存模式淘汰策略雪崩穿透(代码片段)

一、缓存的架构计算机体系结构中的缓存:多级缓存构建本地缓存方法:使用全局变量,一般适用于保存非常非常高频的数据项目的方案SQLAlchemy起到一定的本地缓存作用在同一请求中多次相同的查询只查询数据库一... 查看详情

2021最新阿里java面试流程,进阶学习

学习内容大致内容:1、架构师筑基(Linux基础与进阶+Netty框架+Mysql+并发编程+JVM性能调优+Tomcat)2、开源框架(主要涉及SSM框架)3、高性能架构(Mysql高性能存储实战+Redis缓存数据库+Zoo... 查看详情

探讨下如何更好的使用缓存——redis缓存的特殊用法以及与本地缓存一起构建多级缓存的实现(代码片段)

大家好,又见面了。通过前面的文章,我们一起剖析了GuavaCache、Caffeine、Ehcache等本地缓存框架的原理与使用场景,也一同领略了以Redis为代表的集中式缓存在分布式高并发场景下无可替代的价值。现在的很多大型高并发系统都是... 查看详情