小蜗牛,今天爬了多远?不急,继续爬总会到达终点。
朋友们,今天学习了多久?别慌,保持学习才会看到更好的自己。
觉得我的文章还不错的,欢迎大家还可以关注我的微信公众号:Java菜鸟进阶之路
文章目录
简介
概念
1、Cache是高速缓冲存储器一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问
2、凡是位于速度相差较大的两种硬件/软件之间的,用于协调两者数据传输速度差异的结构,均可称之为Cache
场景
1.操作系统磁盘缓存:
作用:减少磁盘机械操作
2.数据库缓存
作用:减少文件系统I/O
3.应用程序缓存
作用:减少对数据库的查询
4.Web服务器缓存
作用:减少应用服务器请求
5.客户端浏览器缓存
作用:减少对网站的访问
Java缓存
在Java应用中,其缓存主要就是为了将常用的内容采取其他的介质存储,以便再一次使用时,而无需重复去执行相同的工作,提升数据效率性。主要场景就是基于查询数据。
在如今随着Java服务逐渐由单机变为多服务器的发展,衍生出不同的缓存概念,分为:【本地缓存和分布式缓存】。
【本地缓存】:顾名思义,就是将需要缓存的内容放置于服务器内存当中,采取以内存空间换时间的思维,达到查询数据加速效果;
【 分布式缓存】:基于多机场景,将需要缓存的内容放置于具有分布式效果,且多机能够统一读取操作的介质当中。当各自服务器需要操作时,都统一操作该介质,以保持整个服务器集群都是操作的同一个介质,达到数据一致性,而避免每个服务器单独缓存,减少单台服务器的内存资源的损耗。
常用方案
本地缓存
集合结构
采取hashMap,hashSet,conCurrentMap等具有key/value数据结构的集合类实现
Guava Cache(很强大)
Guava Cache 是一个全内存的本地缓存实现,它提供了线程安全的实现机制。整体上来说 Guava Cache 是本地缓存的不二之选,因为其简单易用,性能好。Guava Cache 不是一个单独的缓存框架,而是 Guava 中的一个模块。
Guava Cache 的优点体现在三个方面:
本地缓存,读取效率高,不受网络因素影响。
拥有丰富的功能,操作简单。
线程安全。
Guava Cache 的不足之处也体现在三个方面:
缓存为本地缓存,不能持久化数据。
单机缓存,受机器内存限制,当应用重启数据时会丢失。
分布式部署时无法保证数据的一致性。
分布式缓存
Memcached
Memcached是一个自由开源的,高性能,分布式内存对象缓存系统。
Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。
Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者是页面渲染的结果。
Memcached简洁而强大。它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的很多问题。它的API兼容大部分流行的开发语言。
本质上,它是一个简洁的key-value存储系统。
一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。
Redis
Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
1、Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
2、Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
3、Redis支持数据的备份,即master-slave模式的数据备份。
Redis 优势
1、性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
2、丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
3、原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
4、丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
Cacheonix
Cacheonix是一个开源分布式java缓存,能够以集群方式扩展java应用,其特点是横向扩展能力和最短的GC暂停时间。
Cacheonix提供严格一致性的分布式缓存,没有主从节点之分,所有节点都包含共享的彼此复制的数据. 这能在集群中节点失败时提供不会中断的服务,Cacheonix能提供最低延迟地将负载扩散开来,使用
Cacheonix保存重要数据在内存中,能确保数据总是可用的。
Tair
Tair是由淘宝网自主开发的Key/Value结构数据存储系统,在淘宝网有着大规模的应用。您在登录淘宝、查看商品详情页面或者在淘江湖和好友“捣浆糊”的时候,都在直接或间接地和Tair交互。
Tair是一个Key/Value结构数据的解决方案,它默认支持基于内存和文件的两种存储方式,分别和我们通常所说的缓存和持久化存储对应。
Tair除了普通Key/Value系统提供的功能,比如get、put、delete以及批量接口外,还有一些附加的实用功能,使得其有更广的适用场景
温馨提示:因为本文的重点不是详细讲解如上内容,只是说明一下有这样的方式(当然,远远不只这些,还有很多优秀的开源框架,个人能力有限目前仅用过这些),所以,如果想更加的深入了解可以多去看看哦。
本地缓存代码实现
提示: 基于单例模式+ConCurrentHashMap数据结构+SecureRandom随机数+懒处理模式(非主动删除)的实现。
其还具有很多可以优化的地方,当然不是十分完美,主要是提供一定本地缓存实现的思维。
package com.csdn;
import org.springframework.util.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.lang.ref.SoftReference;
import java.security.SecureRandom;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @ Author :scw
* @ Date :Created in 上午 11:00 2021/1/10 0010
* @ Description:本地缓存工具类(单例模式)
* @ Modified By:
* @Version: $version$
*/
public class LocalCacheUtil {
/**
* 采取饿汉式初始化对象(线程安全)
*/
private static LocalCacheUtil cacheUtil = new LocalCacheUtil();
/**
* 存储缓存对象
* key:自定义设计,便于查询
* value:采取soft便于OOM的场景的自释放
* concurrentHashMap: 用于线程安全的处理
*/
private static Map<String, SoftReference<CacheDetail>> cacheMap = new ConcurrentHashMap<>();
/**
* 用于产生随机数
* 这里采取secureRandom因为其种子更为安全
*/
private static SecureRandom secureRandom = new SecureRandom();
/**
* cache最大的缓存key个数
* PS:因为是懒缓存所以不会主动删除key,防止map中存太多对象
*/
private static final int MAX_CACHE_KEY_NUMBER = 2048;
/**
* 获取 缓存实例操作对象
* PS:采取饿汉式模式(因为较简单,且是线程安全,效率高)
* @return 缓存工具操作对象
*/
public static LocalCacheUtil getInstance(){
return cacheUtil;
}
/**
* 添加 缓存 内容(需指定key和cacheDetail对象)
* PS: 适用于 手动设置 缓存内容和时间
* @param key 缓存key(需保证唯一否则会被map中的key覆盖)
* @param obj 需缓存对象和过期时间(默认为0)
*/
public synchronized void addCacheData(String key, CacheDetail obj){
if(StringUtils.isEmpty(key)){
return;
}
// 因为采取的concurrentMap其key和value不能为null
if(obj == null || obj.getObject() == null){
return;
}
// 判断缓存对象是否超过最大数限制
if( cacheMap.size() > MAX_CACHE_KEY_NUMBER){
// 采取LRU方法,移除过期时间最近的数据(PS:防止无用数据一直存在内存而无法被回收)
List<String> keys = cacheMap.entrySet().stream()
.filter(cur -> cur != null && cur.getValue() != null)
.sorted(Comparator.comparing(e -> e.getValue().get().getCacheExpiredTime()))
.map(entry -> entry.getKey())
.collect(Collectors.toList());
if(CollectionUtils.isNotEmpty(keys)){
this.removeCacheData(keys.get(0));
}
}
//添加缓存内容
cacheMap.put(key, new SoftReference<CacheDetail>(obj));
}
/**
* 添加 缓存 内容(需指定key和对象即可)
* PS: 适用于 缓存时间为随机时间,避免[缓存雪崩]
* @param key 缓存key(需保证唯一否则会被map中的key覆盖)
* @param obj 需缓存对象
*/
public synchronized void addCacheData(String key, Object obj){
if(StringUtils.isEmpty(key)){
return;
}
if(obj == null){
return;
}
// 设置随机时间(PS:这个可以自定义,这里就是1分钟到10分钟之间)
long randomTime = (secureRandom.nextInt(56)+5) * 1000;
long expiredTime = System.currentTimeMillis() + randomTime;
CacheDetail cacheDetail = new CacheDetail(obj, expiredTime);
this.addCacheData(key, cacheDetail);
}
/**
* 根据key 获取对应的缓存对象
* @param key 缓存key
* @return 缓存对象
*/
public Object getCacheData(String key){
SoftReference<CacheDetail> cacheDetail = cacheMap.get(key);
// 判断是否超过了缓存的过期时间
if(cacheDetail != null && System.currentTimeMillis() > cacheDetail.get().getCacheExpiredTime()){
return cacheDetail.get().getObject();
}
return null;
}
/**
* 根据key 判断是否存在缓存对象
* @param key 缓存key
* @return true 有效 false 无效
*/
public boolean judgeIsExistCacheData(String key){
return this.getCacheData(key) != null;
}
/**
* 删除 缓存内容
* @param key 需要删除的key
*/
public synchronized void removeCacheData(String key){
// 判断需要删除的key是否为空
if(StringUtils.isNotBlank(key)){
cacheMap.remove(key);
}
}
/**
* 缓存明细对象
*/
private class CacheDetail{
/**
* 缓存对象
*/
private Object object;
/**
* 缓存过期时间
*/
private long cacheExpiredTime;
public CacheDetail(Object object, long cacheExpiredTime){
this.object = object;
this.cacheExpiredTime = cacheExpiredTime;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public long getCacheExpiredTime() {
return cacheExpiredTime;
}
public void setCacheExpiredTime(long cacheExpiredTime) {
this.cacheExpiredTime = cacheExpiredTime;
}
}
}
调用方法
超级无敌简单,直接如下即可:
// 添加缓存 (随机时间)
LocalCacheUtil.getInstance().addCacheData("key_123", 需要缓存对象);
// 添加缓存(自定义失效时间)
LocalCacheUtil.getInstance().addCacheData("key_456", CacheDetail缓存对象);
其中CacheDetail对象自己构造即可啦
// 获取缓存对象
Object cache = LocalCacheUtil.getInstance().getCacheData("key_123");
.....
就不一一解释了,看上面的例子也发现很简单啦
彩蛋
利用Guava简单的本地缓存实现
如果你觉得不错,有所收获,就非常值得了,感谢你的阅读,欢迎推荐给周围的小伙伴一起学习,共同进步。