微服务学习笔记--高级篇--(多级缓存意义及JVM进程缓存)

多级缓存

亿级流量的缓存方案


传统缓存的问题

传统的缓存策略一般是请求到达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

@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));
	}
}

你可能感兴趣的:(缓存,微服务,学习)