传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,存在下面的问题:
多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压力,提升服务性能:
用户发起请求-->浏览器客户端缓存-->nginx本地缓存-->nginx缓存未命中,Redis缓存-->Redis缓存未命中,进程缓存-->进程缓存未命中,redis数据库
用作缓存的Nginx是业务Nginx,需要部署为集群,再有专门的Nginx用来做反向代理
后续数据同步需要用到MySQL主从功能,需要在虚拟机中,利用Docker运行一个MySQL容器。
为了方便配置MySQL,先准备两个目录,用于挂载容器的数据和配置文件目录:
# 进入/tmp目录
cd /tmp
# 创建文件夹
mkdir mysql
# 进入mysql目录
cd mysql
进入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
在/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
配置修改后,必须重启容器:
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集群
}
}
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:
分布式缓存,例如Redis:
进程本地缓存,例如HashMap、GuaveCache
Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址:https://github.com/ben-manes/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();
在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失败数据的驱逐。
利用Caffeine实现下列需求:
CaffeineConfig.java
@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
@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));
}
}