热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

微服务20_多级缓存01:JVM进程缓存

微服务20_多级缓存01:JVM进程缓存前言:什么是多级缓存?1、传统缓存的问题:2、多级缓存方案一、JVM进程缓存(Tomcat内部编写进程缓存)1、导入商品案例1.安装mysql2.导入Demo3




微服务20_多级缓存01:JVM进程缓存



  • 前言:什么是多级缓存?



    • 1、传统缓存的问题:

    • 2、多级缓存方案



  • 一、JVM进程缓存(Tomcat内部编写进程缓存)



    • 1、导入商品案例



      • 1.安装mysql

      • 2.导入Demo

      • 3. 商品查询页面 来调用controller的接口

      • 4.反向代理的配置:

      • 了解本地缓存和分布式缓存



    • 2、初始Caffeine

    • 3、实现进程缓存



      • 测试:







前言:什么是多级缓存?

缓存的作用是减轻数据库的压力,缩短服务相应的时间,从而提高整个并发的能力,Redis单节并发以及很高了,但是依然有上限,随着互联网的发展,用户体量越来越大,比如淘宝京东的流量能达到数亿级别的流量。那么多级缓存就是为了应对多级缓存高并发。


1、传统缓存的问题:

用户请求到达Tomcat服务器,然后优先查询redis,如果redis命中,直接返回。未命中就访问数据库。 也能很大的程度减少数据库压力。

问题:



  1. 用户请求直接进入Tomcat,再去redis查询。而Tomcat并发能力不如redis。从而Tomcat的并发性能成为了整个系统的瓶颈

  2. redis有淘汰策略,所以说缓存有过期的可能性。当redis缓存失效时,例如雪崩、穿透情况,造成大量请求到达数据库。


2、多级缓存方案

多级缓存就是充分利用请求处理的每个缓存,分别添加缓存,减轻Tomcat的压力,提升服务性能:



  • 第一级缓存:用户通过手机访问浏览器得到渲染。 浏览器缓存。


因为浏览器可以把返回的静态资源缓存到本地的,那么下次用户访问服务器时,只需要检查有没有变化,没有变化服务器直接返回304状态码,不用返回数据了。304:说明本地有。直接渲染本地存着的页面。 减少数据的传输,提高渲染和相应的速度。




  • 第二级缓存:Nginx本地缓存。浏览器本地缓存未成功,请求Nginx服务器.Ngin之前是用来做请求代理。在这里形成第二级缓存,称为Nginx本地缓存。 也可以做业务的编写。那么将数据缓存到nging本地,用户请求来了,如果有直接返回,不用到达Tomcat。


在Nginx内部去实现对redis、Tomcat的访问等等的编写,不再单单是业务代理服务器了,变成了web业务服务了,在里面写业务逻辑了。
做成集群:一个Nginx做反向代理,集群做本地缓存,做业务的Nginx的服务器。




  • 第三级缓存:redis缓存。

  • 第四级缓存:Tomcat进程缓存。

  • 第五级缓存:最后到达数据库缓存。

解决的问题:



  1. 大多数请求由Ngin来进行处理了,减少了对Tomcat的处理,从而Tomcat不会成为服务器的瓶颈了。

  2. Tomcat也有进程缓存,很大程度减少对数据库的查询

在这里插入图片描述


一、JVM进程缓存(Tomcat内部编写进程缓存)

Tomcat服务内部添加缓存, 业务进来以后优先查询进程缓存,缓存未命中,在去查数据库。
在这里插入图片描述


1、导入商品案例


1.安装mysql

后期做数据同步需要用到MySQL的主从功能,所以需要大家在虚拟机中,利用Docker来运行一个MySQL容器。



  1. 先进行将mysql5.7.25版本的mysql压缩包,放到Linux中,进行解压

  2. 准备挂载容器。为了方便后期配置MySQL,我们先准备两个目录,用于挂载容器的数据和配置文件目录:

# 进入/tmp目录
cd /tmp
# 创建文件夹
mkdir mysql
# 进入mysql目录
cd mysql


  1. 运行docker命令,运行mysql 。进入mysql目录后,执行下面的Docker命令:
    推荐在/tmp/mysql的文件下,运行下面的命令: 因为指定挂载路径是:$pwd。当前路径下创建文件,并挂载。

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=123 \
--privileged \
-d \
mysql:5.7.25

docker查看当前运行的容器。docker ps-a是查看所有容器



  1. 修改配置:添加字符集
    在/tmp/mysql/conf目录添加一个my.cnf文件,作为mysql的配置文件:

[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
server-id=1000

添加后:并重启mysql: docker restart mysql



  1. 用navicat连接虚拟机的数据库:
    在这里插入图片描述
    创建表名:heima
    表字段分析:



  • 商品信息表:

CREATE TABLE `tb_item` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id',
`title` varchar(264) NOT NULL COMMENT '商品标题',
`name` varchar(128) NOT NULL DEFAULT '' COMMENT '商品名称',
`price` bigint(20) NOT NULL COMMENT '价格(分)',
`image` varchar(200) DEFAULT NULL COMMENT '商品图片',
`category` varchar(200) DEFAULT NULL COMMENT '类目名称',
`brand` varchar(100) DEFAULT NULL COMMENT '品牌名称',
`spec` varchar(200) DEFAULT NULL COMMENT '规格',
`status` int(1) DEFAULT '1' COMMENT '商品状态 1-正常,2-下架,3-删除',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `status` (`status`) USING BTREE,
KEY `updated` (`update_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=50002 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='商品表';


  • 库存信息表:

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 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;

为什么要做两张表格呢?
一个商品的数量是非常多的。
一方面数据解耦,字段太多查询效率太低了。
另一方面需要数据需要缓存,那么一条整个数据去做缓存,如果一个字段做了修改,那么整条数据全部失效了,就得所有信息都得去数据库做加载。


例如:将所有信息存入一张表中,那么库存改了以后,就得重新加载该条数据。 那么如果将分为多个表,当库存修改了以后,只需要将库存表进行加载即可,不需要获取所有的商品信息了。



2.导入Demo

基于mybatis-plus快速实现单表的增删改查:



  • 分页查询商品

  • 新增商品

  • 修改商品

  • 修改库存

  • 删除商品

  • 根据id查询商品

  • 根据id查询库存

在这里插入图片描述



  1. application.yml文件:
    mysql放到了Linux的docker中了,所以说:
    数据库的URL:是自己虚拟机的ip地址。
    在这里插入图片描述
    需要修改为自己的虚拟机地址信息、还有账号和密码。

修改后,启动服务,访问:http://localhost:8081/item/10001即可查询数据


3. 商品查询页面 来调用controller的接口

商品查询是购物页面,与商品管理的页面是分离的。
商品查询页面放在Nginx反向代理服务器上面,作为静态资源服务器,用户来请求商品页面的时候,Nginx返给用户。先返回的只是静态页面,数据再从后台查询了【Nginx本地、redis、Tomcat、数据库】
部署方式如图:
在这里插入图片描述

我们需要准备一个反向代理的nginx服务器,来部署静态资源。如上图红框所示,将静态的商品页面放到nginx目录中。
页面需要的数据通过ajax向服务端(nginx业务集群)查询。

将其拷贝到一个非中文目录下,运行这个nginx服务。

运行命令:

start nginx.exe

然后访问 http://localhost/item.html?id=10001即可:


localhost 访问的是Nginx
item.html 访问的商品页面
id=10001 是具体的商品
在这里插入图片描述


请求地址是:localhost/api/item/10001
并没有添加端口,请求到了80端口的nginx服务器了

用户请求被Nginx反向代理拿到了,它不处理,而是代理到nginx业务集群中去,在去做多级缓存。 所以要完成反向代理的配置

在这里插入图片描述


4.反向代理的配置:

nginx/conf/nginx.conf


location /api
以api开头的请求,就被Nginx配置拦截到了,反向代理到负载均衡的配置:http://nginx-cluster
nginx-cluster:是业务集群,本地缓存、redis缓存、Tomcat缓存


现在,页面是假数据展示的。我们需要向服务器发送ajax请求,查询商品数据。

打开控制台,可以看到页面有发起ajax查询数据:
而这个请求地址同样是80端口,所以被当前的nginx反向代理了。
查看nginx的conf目录下的nginx.conf文件:

在这里插入图片描述


#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;

# nginx的业务集群,做Nginx本地缓存、redis缓存、Tomcat缓存
upstream nginx-cluster{
server 192.168.75.111:8081;
}
server {
listen 80;
server_name localhost;
location /api {
proxy_pass http://nginx-cluster;
}
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

了解本地缓存和分布式缓存

缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:

分布式缓存:例如redis



  • 优点:存储量更大、可靠性更好、可以在集群间共享。

  • 缺点:访问缓存有网咯开销。


独立于Tomcat之外的,Tomcat访问redis时要发起网络请求,所以说有网络开销。




  • 场景:缓存数量较大、可靠性要求高、需要在集群间共享

进程本地缓存:例如hashMap、GuavaCache



  • 优点:读取本地内存,没有网络开销,速度更快。

  • 缺点:存储容量有限,可靠性较低、无法共享

  • 场景:性能要求较高,缓存数据量较小




2、初始Caffeine

专业的进程缓存技术。

Caffeine是一个基于java8开发的,提供了近乎最佳命中率的高性能的本地缓存。目前Spring内部的缓存使用的就是Caffeine。
官网:ttps://github.com/ben-manes/caffeine
官方读写性能测试:
在这里插入图片描述
通过下面的例子进行学习Caffeine的使用:

/*
基本用法测试
*/

@Test
void testBasicOps() {
// 创建缓存对象 。 创建工厂,在build构建出缓存对象。
Cache<String, String> cache = Caffeine.newBuilder().build();
// 存数据 gril frend女朋友的意思 key,value的形式
cache.put("gf", "迪丽热巴");
// 取数据,不存在则返回null
String gf = cache.getIfPresent("gf");
System.out.println("gf = " + gf);
// 取数据,不存在则去数据库查询
// 根据Key查缓存未命中,再去查数据库。存入数据库、在返回
String defaultGF = cache.get("defaultGF", key -> {
// 这里可以去数据库根据 key查询value
return "柳岩";
});
System.out.println("defaultGF = " + defaultGF);
}

Caffeine既然是缓存的一种,肯定需要有缓存的清除策略,不然的话内存总会有耗尽的时候。

Caffeine提供了三种缓存驱逐策略:



  • 方法一:基于容量:设置缓存的数量上限

// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1) // 设置缓存大小上限为 1
.build();

在这里插入图片描述



  • 方法二:基于时间:设置缓存的有效时间

// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
// 设置缓存有效期为 10 秒,从最后一次写入开始计时
.expireAfterWrite(Duration.ofSeconds(10))
.build();

在这里插入图片描述



  • 方法三:基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。


注意:在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。



3、实现进程缓存

案例:实现商品的查询本地进程缓存
利用Caffeien实现下列需求:



  • 给根据id查询商品的业务添加缓存,缓存未命中时查询数据库

  • 给根据id查询商品库存的业务添加缓存,缓存未命中时查询数据库

  • 缓存初始大小为100

  • 缓存上限为10000



  1. 添加Bean对象Cache
    首先,我们需要定义两个Caffeine的缓存对象,分别保存商品、库存的缓存数据。

在item-service的com.heima.item.config包下定义CaffeineConfig类:

package com.heima.item.config;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.heima.item.pojo.Item;
import com.heima.item.pojo.ItemStock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CaffeineConfig {
@Bean
public Cache<Long, Item> itemCache(){
return Caffeine.newBuilder()
// 初始化大小为100
.initialCapacity(100)
// 上限是10000,分隔符方便读数据
.maximumSize(10_000)
.build();
}

@Bean
public Cache<Long, ItemStock> itemStockCache(){
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(10_000)
.build();
}
}


  1. 修改controller类,先进行缓存中拿数据,未中再去数据库拿数据,如果拿到数据后放到缓存后,在返回用户数据。
    然后,修改item-service中的com.heima.item.web包下的ItemController类,添加缓存逻辑:

@Autowired
private Cache<Long,Item> itemCache;
@Autowired
private Cache<Long,ItemStock> itemStockCache;
//根据id查询商品
@GetMapping("/{id}")
public Item findById(@PathVariable("id") Long id){
// .get(先去缓存查数据,未中去数据库查缓存) key==id
return itemCache.get(id,key->itemService.query()
// ne 是不等于。状态码不等于3的
.ne("status", 3)
//eq 是等于 数据库中的“id" == key 也就是等等与前端传过来的Id
.eq("id", key)
// 查一条数据过来
.one()
);
}
//根据id查询库存
@GetMapping("/stock/{id}")
public ItemStock findStockById(@PathVariable("id") Long id){
return itemStockCache.get(id,key->stockService.getById(key));
}
}

测试:

当添加缓存之前,每当刷新一下页面,就会去数据库进行查询一次:
在这里插入图片描述
添加缓存之后,只去数据库查询一次,再重新刷新页面,不会去数据库进行查询了,而是直接返回缓存信息即可。
在这里插入图片描述
在这里插入图片描述



推荐阅读
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 本文介绍了Redis中RDB文件和AOF文件的保存和还原机制。RDB文件用于保存和还原Redis服务器所有数据库中的键值对数据,SAVE命令和BGSAVE命令分别用于阻塞服务器和由子进程执行保存操作。同时执行SAVE命令和BGSAVE命令,以及同时执行两个BGSAVE命令都会产生竞争条件。服务器会保存所有用save选项设置的保存条件,当满足任意一个保存条件时,服务器会自动执行BGSAVE命令。此外,还介绍了RDB文件和AOF文件在操作方面的冲突以及同时执行大量磁盘写入操作的不良影响。 ... [详细]
  • svnWebUI:一款现代化的svn服务端管理软件
    svnWebUI是一款图形化管理服务端Subversion的配置工具,适用于非程序员使用。它解决了svn用户和权限配置繁琐且不便的问题,提供了现代化的web界面,让svn服务端管理变得轻松。演示地址:http://svn.nginxwebui.cn:6060。 ... [详细]
  • Centos下安装memcached+memcached教程
    本文介绍了在Centos下安装memcached和使用memcached的教程,详细解释了memcached的工作原理,包括缓存数据和对象、减少数据库读取次数、提高网站速度等。同时,还对memcached的快速和高效率进行了解释,与传统的文件型数据库相比,memcached作为一个内存型数据库,具有更高的读取速度。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • MySQL语句大全:创建、授权、查询、修改等【MySQL】的使用方法详解
    本文详细介绍了MySQL语句的使用方法,包括创建用户、授权、查询、修改等操作。通过连接MySQL数据库,可以使用命令创建用户,并指定该用户在哪个主机上可以登录。同时,还可以设置用户的登录密码。通过本文,您可以全面了解MySQL语句的使用方法。 ... [详细]
  • 分享css中提升优先级属性!important的用法总结
    web前端|css教程css!importantweb前端-css教程本文分享css中提升优先级属性!important的用法总结微信门店展示源码,vscode如何管理站点,ubu ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • 目录浏览漏洞与目录遍历漏洞的危害及修复方法
    本文讨论了目录浏览漏洞与目录遍历漏洞的危害,包括网站结构暴露、隐秘文件访问等。同时介绍了检测方法,如使用漏洞扫描器和搜索关键词。最后提供了针对常见中间件的修复方式,包括关闭目录浏览功能。对于保护网站安全具有一定的参考价值。 ... [详细]
  • 面试经验分享:华为面试四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试
    最近有朋友去华为面试,面试经历包括四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试。80%的人都在第一轮电话面试中失败,因为缺乏基础知识。面试问题涉及 ... [详细]
author-avatar
钟孝健V
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有