多级缓存
亿级流量的缓存方案
传统缓存的问题
传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,存在下面的问题:
- 请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈
- Redis缓存失效时,会对数据库产生冲击
多级缓存方案
多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压力,提升服务性能:
用户发起请求-->浏览器客户端缓存-->nginx本地缓存-->nginx缓存未命中,Redis缓存-->Redis缓存未命中,进程缓存-->进程缓存未命中,redis数据库
用作缓存的Nginx是业务Nginx,需要部署为集群,再有专门的Nginx用来做反向代理
目录
- JVM进程缓存
- Lua语法入门
- 多级缓存
- 缓存同步策略
JVM进程缓存
- 导入商品案例
- 初识Caffeine
- 实现进程缓存
安装MySQL教程
后续数据同步需要用到MySQL主从功能,需要在虚拟机中,利用Docker运行一个MySQL容器。
1.1准备目录
为了方便配置MySQL,先准备两个目录,用于挂载容器的数据和配置文件目录:
# 进入/tmp目录
cd /tmp
# 创建文件夹
mkdir mysql
# 进入mysql目录
cd mysql
1.2运行命令
进入mysql目录后,执行下面的Docker命令:
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
1.3修改配置
在/tmp/mysql/conf目录添加一个my.cnf文件,作为mysql的配置文件:
# 创建文件
touch /tmp/mysql/conf/my.cnf
文件的内容如下:
[mysqlId]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
server-id=1000
1.4重启
配置修改后,必须重启容器:
docker restart mysql
我们需要准备一个反向代理的nginx服务器,将静态的商品页面放到nginx目录中,页面需要的数据通过ajax向服务器(nginx业务集群)查询。
nginx的conf目录下的nginx.conf文件,关键的配置如下:
upstream nginx-cluster{
server 192.168.150.101:8081; #nginx-cluster集群,就是将来的nginx业务集群
}
server {
listen 80;
server_name localhost;
location /api {
proxy_pass http://nginx-cluster; #监听/api路径,反向代理到nginx-cluster集群
}
}
初识Caffeine
本地进程缓存
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:
分布式缓存,例如Redis:
- 优点:存储容量更大、可靠性更好、可以在集群键共享
- 缺点:访问缓存有网络开销
- 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
进程本地缓存,例如HashMap、GuaveCache
- 优点:读取本地内存,没有网络开销,速度更快
- 缺点:存储容量有限、可靠性较低、无法共享
- 场景:性能要求较高,缓存数据量较小
Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址:https://github.com/ben-manes/caffeine
Caffeine示例
可以通过item-service项目中的单元测试来学习Caffeine的使用
@Test
void testBasicOps() {
// 创建缓存对象
Cache<String,String> cache = Caffeine.newBuilder().build();
//存数据
cache.put("gf","luxifa");
//取数据,不存在则返回null
String gf = cache.getIfPresent("gf");
//取数据,不存在则去数据库查询
String defaultGF = cache.get("defaultGF", key -> {
//这里取数据库根据key查询value
return "degula";
});
}
Caffeine提供了三种缓存驱逐策略:
- 基于容量:设置缓存的数量上限
//创建缓存对象
Cache<String,String> cache = Caffeine.newBuilder()
.maximumSize(1) //设置缓存大小上限为 1
.build();
- 基于时间:设置缓存的有效时间
//创建缓存对象
Cache<String,String> cache = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(10) //设置缓存有效期为10秒,从最后一次写入开始计时
.build();
- 基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。
在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失败数据的驱逐。
实现进程缓存
案例:实现商品的查询的本地进程缓存
利用Caffeine实现下列需求:
- 给根据id查询商品的业务添加缓存,缓存为命中时查询数据库
- 给根据id查询商品库存的业务添加缓存,缓存未命中时查询数据库
- 缓存初始大小为100
- 缓存上限为1000
CaffeineConfig.java文章来源:https://www.toymoban.com/news/detail-488373.html
@Configuration
public class CaffeineConfig {
@Bean
public Cache<Long,Item> itemCache() {
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(10_000)
.build();
}
@Bean
public Cache<Long,ItemStock> stockCache() {
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(10_000)
.build();
}
}
ItemController.java文章来源地址https://www.toymoban.com/news/detail-488373.html
@RestController
@RequestMapping("item")
public class ItemController {
@Autowired
private IItemService itemService;
@Autowired
private IItemStockService stockService;
@Autowired
private Cache<Long,Item> itemCache;
@Autowired
private Cache<Long,ItemStock> stockCache;
@GetMapping("/{id}")
public Item findById(@PathVariable("id") Long id) {
return itemCache.get(id,key -> itemService.query()
.ne("status",3).eq("id",key)
.one()
);
}
@GetMapping("/stock/{id}")
public ItemStock findStockById(@PathVariable("id") Long id) {
return stockCache.get(id,key -> stockService.getById(id));
}
}
到了这里,关于微服务学习笔记--高级篇--(多级缓存意义及JVM进程缓存)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!