目录
1、背景
【问】:项目中哪些地方会用到缓存?为什么要引入缓存?如何使用缓存?引入缓存后会带来哪些问题?
还得结合项目中的业务来回答。引入缓存,其实主要有两个用途:高性能、高并发!
假设:某个操作非常频繁。比如:网站的商城首页 —— 需要频繁的从数据库里面获取商品数据,可能从数据库一顿各种操作下来,平均耗时 500 ms,随着请求频次越高,用户等待数据的返回结果时间越来越长,体验越来越差
如果此时,引入缓存,将数据库里面查询出来的商品数据信息,放入缓存服务里面,当用户再此发起查询操作的时候,直接从缓存服务里面获取,速度从耗时 500 ms,可能直接优化成 5 ms,体验上瞬间会上升好几个层次! —— 这就是引入缓存带来的高性能体验结果
引入缓存之前,以 mysql 数据库为例,单台机器一秒内的请求次数到达 2000 之后就会开始报警;引入缓存之后,以 redis 缓存服务器为例,单台机器一秒内的请求次数支持 110000 次,两者支持的并发量完全不是一个数量级的 —— 这就是引入缓存带来的高并发体验结果
尤其是对于流量很大的业务,引入缓存,给系统带来的提升是十分显著的
【问】:缓存和数据库为什么差距这么大?有什么区别?
在计算机中,数据的存储主要有两处:内存;磁盘
- 内存:内存的数据读写性能 远超磁盘的读写性能;虽然读写性能非常高,但是当电脑重启之后,数据会全部清除
- 磁盘:存入磁盘的数据,虽然读写性能很差,但是电脑重启之后数据不会丢失
因为两者的数据存储方案不同,造就了不同的实践用途
在项目中如何引入缓存呢?通常的做法如下:
- 当用户发起访问某数据的操作时,检查缓存服务里面是否存在,如果存在,直接返回;如果不存在,走数据库的查询服务
- 从数据库里面获取到有效数据之后,存入缓存服务,并返回给用户
- 当被访问的数据发生更新的时候,需要同时删除缓存服务,以便用户再次查询的时候,能获取到最新的数据
这对于简单的需要缓存的业务场景,能轻松应对。
但是面对复杂的业务场景和服务架构,尤其是对缓存要求比较高的业务,引入缓存的方式,也会跟着一起变化。
根据缓存面向的对象不同,缓存分为:本地缓存、分布式缓存和多级缓存
- 本地缓存:在单个计算机服务实例中,直接把数据缓存到内存中进行使用
本地缓存是直接从本地内存中读取,没有网络开销。例如:秒杀系统或者数据量小的缓存等,比远程缓存更合适
- 分布式缓存:现在的服务,大多都是以集群的方式来部署。即:同一个网站服务,同时在两台计算机里面部署,如用到的 session 会话,就无法同时共享,因此需要引入一个独立的缓存服务来连接两台服务器,这个独立部署的缓存服务就是分布式缓存
- 多级缓存:在实际的业务中,本地缓存和分布式缓存会同时结合进行使用,当收到访问某个数据的操作时,会优先从本地缓存服务(也叫一级缓存)查询,如果没有,再从分布式缓存服务(也叫二级缓存)里面获取,如果也没有,最后再从数据库里面获取;从数据库查询完成之后,在依次更新分布式缓存服务、本地缓存服务
2、手写一个简单的本地缓存
使用缓存的时候,比较关注两个地方:
- 内存持久化
- 支持缓存的数据自动过期清除
对于简单的数据缓存,我们完全可以自行编写一套缓存服务,实现过程如下:
①:创建一个缓存实体类
@Data
public class CacheEntity {
// 缓存键
private String key;
// 缓存键
private Object value;
// 过期时间
private Long expireTime;
}
②:缓存工具类
public class CacheUtil {
// 缓存数据
private final static Map<String, CacheEntity> CACHE_MAP = new ConcurrentHashMap<>();
// 定时器线程池,用于清理过期缓存
private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
static {
// 注册一个定时任务,服务启动 1000 毫秒后,每隔 500 毫秒执行一次
Runnable task = CacheUtil::clear;
executorService.scheduleAtFixedRate(task, 1000L, 500L, TimeUnit.MILLISECONDS);
}
// 添加缓存
public static void put(String key, Object value) {
put(key, value, 0L);
}
// 添加缓存
public static void put(String key, Object value, Long expire) {
CacheEntity cacheEntity = new CacheEntity();
cacheEntity.setKey(key);
cacheEntity.setValue(value);
if (expire > 0) {
// 计算过期时间
Long expireTime = System.currentTimeMillis() + Duration.ofSeconds(expire).toMillis();
cacheEntity.setExpireTime(expireTime);
}
CACHE_MAP.put(key, cacheEntity);
}
// 获取
public static Object get(String key) {
if (CACHE_MAP.containsKey(key)) {
return CACHE_MAP.get(key);
}
return null;
}
// 删除
public static void remove(String key) {
CACHE_MAP.remove(key);
}
// 清除过期缓存
public static void clear() {
if (CACHE_MAP.isEmpty()) {
return;
}
CACHE_MAP.entrySet().removeIf(entityEntry

1万+

被折叠的 条评论
为什么被折叠?



