手把手教你如何实现简单的本地缓存

小蜗牛,今天爬了多远?不急,继续爬总会到达终点。
朋友们,今天学习了多久?别慌,保持学习才会看到更好的自己。
觉得我的文章还不错的,欢迎大家还可以关注我的微信公众号: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简单的本地缓存实现

在这里插入图片描述

如果你觉得不错,有所收获,就非常值得了,感谢你的阅读,欢迎推荐给周围的小伙伴一起学习,共同进步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值