zoukankan      html  css  js  c++  java
  • [Java EE]缓存技术初探

    1 背景

    高并发下,为提高 频繁 查询 大量 可能常用的 数据库数据的 查询效率。

    大部分情况下,单机用Google Guava(Cache/LoadCache) / ehcache,分布式用redismemcache,各有各的好处,现在企业都是应用很多种中间件供后端程序员选择。

    2 缓存技术

    什么是缓存?

    1 - Cache是高速缓冲存储器 一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问
    2 - 凡是位于速度相差较大的两种硬件/软件之间的,用于协调两者数据传输速度差异的结构,均可称之为 Cache
    3 - 缓存技术设计思想: 典型的空间换时间

    2-1 分类

    • 操作系统磁盘缓存(加速/减少磁盘机械操作) / 数据库缓存(加速/减少访问文件系统I/O) / 【应用程序缓存】(加快/减少对数据库的查询) / Web服务器缓存(加速/减少应用服务器请求) / 浏览器缓存(加速/减少对网站的访问)

    • 分布式缓存 / 本地缓存

    • 介质: 基于内存缓存 / 基于磁盘缓存 / 基于中间件[数据库]缓存(Redis/Memcache/...,本质:内存+磁盘) / 基于JVM缓存(本质:基于内存)

    2-2 缓存开源组件

    OSCache / Java Caching System(JCS) / EHCache / JCache / ShiftOne / SwarmCache / TreeCache / JBossCache / WhirlyCache
    
    Google Guava(Cache/LoadingCache)
    

    2-3 缓存的指标

    • 命中率
    • 最大容量
    • 清空策略(过期策略)

    先进先出算法(FIFO)
    first in first out ,最先进入缓存得数据在缓存空间不够情况下(超出最大元素限制时)会被首先清理出去

    最不经常使用算法(LFU)
    Less Frequently Used ,一直以来最少被使用的元素会被被清理掉。这就要求缓存的元素有一个hit 属性,在缓存空间不够得情况下,hit 值最小的将会被清出缓存

    最近最少使用算法(LRU)
    Least Recently Used ,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存

    最近最常使用算法(MRU)
    这个缓存算法最先移除最近最常使用的条目。一个MRU算法擅长处理一个条目越久,越容易被访问的情况。

    自适应缓存替换算法(ARC)
    在IBM Almaden研究中心开发,这个缓存算法同时跟踪记录LFU和LRU,以及驱逐缓存条目,来获得可用缓存的最佳使用。

    2-4 基于JVM缓存的实现方案

    • 方案1: HashMap
    • 方案2: CocurrentHashMap
    • 方案3: 开源组件(Google Guava: Cache / LoadingCache)
    Cache/LoadingCache 均继承自 CocurrentHashMap
    

    2-5 缓存产生的问题

    • Q1: 缓存数据与源数据一致性问题(数据同步)
    解决方法
    1) write back(写回策略): 更新数据源数据时,只更新缓存的数据。当缓存需要被替换(挤出)时,才将缓存中更新的值写回磁盘。
    在写回策略中,为了减少写操作,缓存数据单元通常还设有1个脏位(dirty bit),用于标识该块在被载入后,是否发生过更新。
    若1个缓存数据单元在被置换回内存之前,从未被写入过,则:可以免去回写操作;
    写回的优点是:节省了大量的写操作
    
    2) write through(写通策略): 更新数据源数据时,同时更新缓存的数据。
    
    • Q2: 缓存数据存放时间问题
    • Q3: 缓存的多线程并发控制问题

    3 基于Google Guava开源组件的JVM缓存实现

    需求背景: 一项目中多个接口、频繁地批量查询数据库一类数据————发布的数据服务信息,又要求3s内立即做出响应。 (存在高并发问题)

    3-1 IDataServiceInfoCacheService

    package xxx.service;
    
    import com.google.common.cache.LoadingCache;
    import com.yyy.DataServiceInfo;
    
    import java.util.Map;
    
    /**
     * @date: 2020/11/12  16:58:05
     * @description: 缓存数据服务信息
     */
    
    public interface IDataServiceInfoCacheService {
        /**
         * 从缓存中 获取 数据服务信息
         *  若缓存中不存在该信息,将自动从数据库中加载,再返回
         * @param serviceId
         * @return
         * @throws Exception
         */
        public DataServiceInfo get(String serviceId) throws Exception;
    
        //public void put(String serviceId, DataServiceInfo dataServiceInfo);
    
        //public void putAll(Map<? extends String, ? extends DataServiceInfo> dataServiceInfoMap);
    
        public long size();
    
        public void remove(String serviceId);
    
        public void removeAll(Iterable<Long> serviceIds);
    
        public void removeAll();
    }
    

    3-2 DataServiceCacheServiceImpl

    package xxx.service.impl;
    
    import com.xxx..Dept;
    import com.xxx.ServerSystem;
    import com.xxx.BusinessException;
    import com.google.common.cache.*;
    import com.xxx.BmsCacheService;
    import com.xxx.LoggerUtil;
    import com.xxx.Tools;
    import com.xxx.DataServiceInfo;
    import com.xxx.DataServiceInfoCacheService;
    import com.xxx.ServiceInfoMapper;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.annotation.PostConstruct;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @date: 2020/11/12  17:28:03
     * @description: ...
     */
    @Service
    public class DataServiceCacheServiceImpl implements IDataServiceInfoCacheService {
        private static LoadingCache<String, DataServiceInfo> DATA_SERVICE_INFO_CACHE;
    
        @Autowired
        private ServiceInfoMapper serviceInfoMapper;
        @Autowired
        private BmsCacheService bmsCacheService;
    
        @PostConstruct // 解决 【静态变量】初始化时调用【实例方法】问题
        public void init() {
            DATA_SERVICE_INFO_CACHE = CacheBuilder
                    .newBuilder() //只能通过其静态方法newBuilder()来获得CacheBuilder的实例
                    .concurrencyLevel(8) // 设置并发级别为8,并发级别是指可以同时写缓存的线程数
                    .expireAfterWrite(120, TimeUnit.SECONDS)// 设置写缓存后120秒钟过期
                    .initialCapacity(1000)// 设置缓存容器的初始容量为1000
                    .maximumSize(10000)// 设置缓存最大容量为10000,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                    .recordStats()// 设置要统计缓存的命中率
                    .removalListener(getRemovalListener())
                    .build(getCacheLoader());
        }
    
        @Transactional(readOnly = true)
        protected Map<String, Object> getSourceSystemInfo(String serviceId) throws Exception {
    
            Map<String, Object> data = new HashMap<String, Object>();
    
            Map<String, Object> provides = serviceInfoMapper.getServiceProvideSystems(serviceId);
            if (provides != null) {
                if (provides.get("provideSystemId") != null) {
                    String provideSystemIds = provides.get("provideSystemId").toString();
                    String[] provideIds = provideSystemIds.split(",");
                    StringBuilder systemNames = new StringBuilder();
                    for (String id : provideIds) {
                        ServerSystem sys = bmsCacheService.getSysById(id);
                        if (sys != null) {
                            systemNames.append(sys.getSystemName()).append(",");
                        }
                    }
                    data.put("provideSystemIds", provideSystemIds);
                    if (provideIds.length > 0) {
                        data.put("provideSystemNames", systemNames.deleteCharAt(systemNames.length() - 1));
                    }
                }
    
                if (provides.get("deptId") != null) {
                    String deptIds = provides.get("deptId").toString();
                    String[] provideIds = deptIds.split(",");
                    StringBuilder departNames = new StringBuilder();
                    for (String id : provideIds) {
                        Dept dept = bmsCacheService.getDeptById(id);
                        if (dept != null) {
                            departNames.append(dept.getDeptName()).append(",");
                        }
                    }
                    data.put("provideDepartIds", deptIds);
                    if (provideIds.length > 0) {
                        data.put("provideDepartNames", departNames.deleteCharAt(departNames.length() - 1));
                    }
                }
            }
            return data;
        }
    
        @Transactional(readOnly = true)
        protected DataServiceInfo loadDataServiceInfo(String serviceId) throws Exception {
            DataServiceInfo dataServiceInfo = new DataServiceInfo();
            Map<String, Object> sourceSystemInfo = null;
            sourceSystemInfo = this.getSourceSystemInfo(serviceId);
            Map<String, String> serviceAndCatalogInfoMap = null;
            serviceAndCatalogInfoMap = serviceInfoMapper.getServiceInfoAndCatalogInfoById(serviceId);
            if (Tools.isNull(sourceSystemInfo) && Tools.isNull(serviceAndCatalogInfoMap)) {//通过 serviceId,均未查找到 数据服务信息
                String errorMsg = "根据所提供的数据服务编号,未能查找到数据服务信息!";
                LoggerUtil.error(LoggerUtil.DATASERVICE_MNG_CORE_LOGGER_INSTANCE, String.format(errorMsg + " [serviceId: %s]", serviceId));
                throw new BusinessException(errorMsg);
            }
            dataServiceInfo.setServiceId(serviceId);
            if (Tools.isNotNull(sourceSystemInfo)) {
                dataServiceInfo.setProvideDepartIds(Tools.isNotNull(sourceSystemInfo.get("provideDepartIds")) ? sourceSystemInfo.get("provideDepartIds").toString() : "");
                dataServiceInfo.setProvideDepartNames(Tools.isNotNull(sourceSystemInfo.get("provideDepartNames")) ? sourceSystemInfo.get("provideDepartNames").toString() : "");
                dataServiceInfo.setProvideSystemIds(Tools.isNotNull(sourceSystemInfo.get("provideSystemIds")) ? sourceSystemInfo.get("provideSystemIds").toString() : "");
                dataServiceInfo.setProvideSystemNames(Tools.isNotNull(sourceSystemInfo.get("provideSystemNames")) ? sourceSystemInfo.get("provideSystemNames").toString() : "");
            }
            if (Tools.isNotNull(serviceAndCatalogInfoMap)) {
                dataServiceInfo.setCatalogId(Tools.isNotNull(serviceAndCatalogInfoMap.get("catalogId")) ? serviceAndCatalogInfoMap.get("catalogId").toString() : "");
                dataServiceInfo.setCatalogName(Tools.isNotNull(serviceAndCatalogInfoMap.get("catalogName")) ? serviceAndCatalogInfoMap.get("catalogName").toString() : "");
                dataServiceInfo.setServiceName(Tools.isNotNull(serviceAndCatalogInfoMap.get("serviceName")) ? serviceAndCatalogInfoMap.get("serviceName").toString() : "");
                dataServiceInfo.setTableUnicode(Tools.isNotNull(serviceAndCatalogInfoMap.get("tableUnicode")) ? serviceAndCatalogInfoMap.get("tableUnicode").toString() : "");
            }
            return dataServiceInfo;
        }
    
        private RemovalListener<Object, Object> getRemovalListener() {
            return new RemovalListener<Object, Object>() {
                public void onRemoval(RemovalNotification<Object, Object> removalNotification) {
                    String removeLog = removalNotification.getKey() + " was removed, cause is " + removalNotification.getCause();
                    LoggerUtil.info(LoggerUtil.DATASERVICE_MNG_CORE_LOGGER_INSTANCE, removeLog);
                }
            };
        }
    
        private CacheLoader getCacheLoader() {
            return new CacheLoader<String, DataServiceInfo>() {
                @Override
                public DataServiceInfo load(String serviceId) throws Exception {// 处理缓存键不存在缓存值时的重新获取最新缓存值的处理逻辑
                    LoggerUtil.info(LoggerUtil.DATASERVICE_MNG_CORE_LOGGER_INSTANCE, "[dataServiceInfoCache] loading dataService is: " + serviceId);
                    return loadDataServiceInfo(serviceId);
                }
            };
        }
    
        @Override
        public DataServiceInfo get(String serviceId) throws Exception {
            DataServiceInfo dataServiceInfo = null;
            dataServiceInfo = DATA_SERVICE_INFO_CACHE.get(serviceId);
            if (Tools.isNull(dataServiceInfo)) {
                dataServiceInfo = loadDataServiceInfo(serviceId);
                if (Tools.isNotNull(dataServiceInfo)) {
                    DATA_SERVICE_INFO_CACHE.put(serviceId, dataServiceInfo);
                }
                return dataServiceInfo;
            }
            return dataServiceInfo;
        }
    
        /**
         * @Override public void put(String serviceId, DataServiceInfo dataServiceInfo) {
         * DATA_SERVICE_INFO_CACHE.put(serviceId, dataServiceInfo);
         * }
         * @Override public void putAll(Map<? extends String, ? extends DataServiceInfo> dataServiceInfoMap) {
         * DATA_SERVICE_INFO_CACHE.putAll(dataServiceInfoMap);
         * }
         */
        @Override
        public long size() {
            return DATA_SERVICE_INFO_CACHE.size();
        }
    
        @Override
        public void remove(String serviceId) {
            DATA_SERVICE_INFO_CACHE.invalidate(serviceId);
        }
    
        @Override
        public void removeAll(Iterable<Long> serviceIds) {
            DATA_SERVICE_INFO_CACHE.invalidateAll(serviceIds);
        }
    
        @Override
        public void removeAll() {
            DATA_SERVICE_INFO_CACHE.invalidateAll();
        }
    }
    

    X 参考与推荐文献

  • 相关阅读:
    线段树 hdu3255 Farming
    3.CCFadeOutTRTiles,部落格效果,跳动的方块特效,3D瓷砖晃动特效,破碎的3D瓷砖特效,瓷砖洗牌特效,分多行消失特效,分多列消失特效
    分析:新建短信,当我们接受人RecipientsEditor中输入+86的时候,系统会自己主动在+86后增加空格
    【POJ3377】Ferry Lanes 最短路
    Please ensure that adb is correctly located at &#39;D:Androidandroid-sdkplatform-toolsadb.exe&#39; and
    Objective-C
    分布式高维空间近邻搜索项目开发
    我的改进版2048(2)
    github关联域名,创建个人站点教程终结篇
    数据结构(6)二叉树
  • 原文地址:https://www.cnblogs.com/johnnyzen/p/13967285.html
Copyright © 2011-2022 走看看