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

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 = " + 

以上是关于Redis进阶学习08--多级缓存的主要内容,如果未能解决你的问题,请参考以下文章

ehcahe + redis 实现多级缓存

Redis进阶学习总结(Docker搭建环境)

Redis进阶学习总结(Docker搭建环境)

Redis进阶学习02---Redis替代Session和Redis缓存

Redis进阶学习06--分布式缓存--上

Redis进阶学习07--分布式缓存--下