ハンドMO手 - グアバのキャッシュを使用してSpringbootを学ぶあなたがホットストリップ私のパックを失う構築するために学ぶことができません

概要はじめに

  1. キャッシュは、多くの場合、技術的手段の日々の開発に適用され、キャッシュの使用の合理化が大幅にアプリケーションのパフォーマンスを向上させることができます。
  2. キャッシュ接続のグアバ公式説明
  3. キャッシュは、ユースケースの多様で有用です。計算または非常に高価な値を取得する場合たとえば、あなたがキャッシュを使用することを検討すべきである、と入力の値に複数回、それを必要とします。
  4. ConcurrentMapキャッシュは小さいが、同一ではないです。最も基本的な違いは、ConcurrentMapが、彼らは明示的に削除された要素になるまで、すべてがそれに追加することを主張していることです。
  5. 一方、キャッシュは、一般に、自動的にメモリフットプリントを制限するエントリを引き出すように構成されています。
  6. それは自動的にキャッシュをロードするため、いくつかのケースでは、あなたがLoadingCacheエントリを追い出すことができない場合でも、それは便利です。

適用性

  1. あなたは速度を上げるために、いくつかのメモリを費やすことをいといません。あなたはスピードを向上させるために、いくつかのメモリを費やすことをいといません。
  2. キーはあなたが照会したいことを何度も、時にはそれ以上です。あなたは、キーが時々回以上照会れますことを期待しています。
  3. あなたは、適切な他の何よりも多くのデータを格納するキャッシュする必要はありません。(グアバキャッシュは、ローカルアプリケーションを実行しています)。あなたのキャッシュはinRAMに合うものよりも多くのデータを保存する必要はありません。(グアバのキャッシュは、アプリケーションの単一の実行にローカルです。
  4. 彼らは外部のサーバに保存されていない、ファイル内のデータを格納しないでください。これがニーズに合わない場合は、その上のmemcached、Redisのなどのツールを検討してください。

引用回復に基づいて、

(参考ベースエビクション)、(強力な)強い軟質(ソフト)、弱い(弱)、バーチャル(仮想)参照-参照:キー、または参照弱い値、またはソフト参照GuavaCache缶を使用して参照弱の値キャッシュは、ガベージコレクションを許可するように設定されています。

  1. CacheBuilder.weakKeys():弱い結合を用いて基準メモリ。(ソフトまたは強い)、他のキーが参照していない場合にはキャッシュエントリがごみを収集することができます。ガベージコレクションのため==比較キーに等しい代わりにキャッシュを参照して弱い結合を使用して、唯一のアイデンティティ(==)に依存します。
  2. CacheBuilder.weakValues():使用弱参照格納された値。(強いまたはソフト)さらなる値は参照しない場合、キャッシュ・エントリは、ごみを収集することができます。ガベージコレクションが唯一のアイデンティティ(==)に依存するので、比較値と弱参照を使用するキャッシュされた値が代わりに==等しいです。
  3. CacheBuilder.softValuesは():ソフト参照に格納された値を使用してください。唯一の最低使用のために、世界経済の回復の前にメモリの需要に応じて、ソフト参照。ソフト参照を使用した場合のパフォーマンスへの影響を考えると、我々は一般的に(回復の容量に基づいて、上記参照)より予測可能なパフォーマンスキャッシュのサイズ制限を使用することをお勧めします。==同じ緩衝液で軟らかい基準値を使用するのではなく、比較値に等しいです。

キャッシュの読み込み

ロジックモデルと一緒に、二つの異なるキャッシュローディングのCacheLoader、呼び出し可能に建設グアバキャッシュを使用してCacheBuilderクラスをベースとしているビルダーは、キー値をロードされます。差があまりにも一般のCacheLoaderの定義は、キャッシュ全体のために定義することで、あなたは時に指定したロード方法をで取得することができ、キー値の負荷値、およびより柔軟な呼び出し可能な方法に従って、統一された方法とみなすことができます。次のコードを見てください:

Cache<String,Object> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.SECONDS).maximumSize(500).build();
 
         cache.get("key", new Callable<Object>() { //Callable 加载
            @Override
            public Object call() throws Exception {
                return "value";
            }
        });
 
        LoadingCache<String, Object> loadingCache = CacheBuilder.newBuilder()
                .expireAfterAccess(30, TimeUnit.SECONDS).maximumSize(5)
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) throws Exception {
                        return "value";
                    }
                });
复制代码

キャッシュの削除

グアバの方法は、データがデータグアバの除去を除去する2種類のパッシブとアクティブの除去に分割したときにキャッシュを削除することができません。

  • データを削除するパッシブな方法は、グアバのデフォルトは、以下の3つの方法が用意されています。
  1. 削除の大きさに基づいて:文字通り、指定したサイズに達するのに約場合は、削除するキャッシュのサイズに合わせて知っている見、それは一般的に使用されていないキーと値のペアをキャッシュから削除されます。

再定義された一般CacheBuilder.maximumSize(長い)ようにして、通行の方法考慮すべき権利があり、個人的に私はそれが実際の使用で使用されていないと思います。いくつかのポイントがあることをこのノートのビューの共通点、

  • まず、サイズは、キャッシュ内のエントリの数ではなく、メモリサイズや他のことをいいます。
  • 第二に、ない完全に指定したサイズのシステムに使用頻度の低いデータを削除し始めましたが、システムが削除アクションを行うために開始されます場合は、このサイズに近いです。
  • cachebuildはCacheLoaderの道を使用している場合、これはしていない場合は、キーと値のペアがキャッシュから削除された場合には第三に、あなたは再びアクセス時間を要求し、それはまだ、まだ、その後、CacheLoaderをから値を取得します例外がスローされます
  1. 除去時間に基づいて:グアバは2時間ベースの除去方法を提供しています
  • expireAfterAccess(TimeUnitで、長い)このメソッドは、特定のキーに基づいて、最後のアクセスからの時間の多くを削除することです
  • expireAfterWriteは(長い、TimeUnitで)このメソッドのは、作成された後の時間の量を除去するか、または値がキーに従って交換します
  1. 参照ベースを削除します。   ガベージコレクションメカニズムを削除するには、この方法では、主にJavaのに基づいており、キーや値に応じて参照関係を削除することにしました
  • イニシアティブは、次の3つの方法を削除するためのイニシアチブをとる、データモードを削除するには:
  • アローンCache.invalidateで除去した(キー)
  • Cache.invalidateAllによって除去バッチ(キー)
  • )(Cache.invalidateAllですべてを削除

あなたには、いくつかのアクションが必要な場合は、データを消去する場合、あなたはまた、削除のリスナーを定義することができますが、少しはデフォルトの削除リスナーの挙動と除去のアクションを同期的に実行されることに留意すべきでフォームを変更する必要がある場合は、非同期的に、RemovalListenersを使用することを検討してください。非同期(RemovalListener、エグゼキュータ)

実践的な演習

  1. Mavenの依存性
 <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
 </dependency>
复制代码
  1. GuavaAbstractLoadingCacheローディングバッファーと基本クラスを(私はCacheBuilderを使用)を使用して基本的な性質
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
* @ClassName: GuavaAbstractLoadingCache
* @author LiJing
* @date 2019/07/02 11:09 
*
*/

public abstract class GuavaAbstractLoadingCache <K, V> {
   protected final Logger logger = LoggerFactory.getLogger(this.getClass());

   //用于初始化cache的参数及其缺省值
   private int maximumSize = 1000;                 //最大缓存条数,子类在构造方法中调用setMaximumSize(int size)来更改
   private int expireAfterWriteDuration = 60;      //数据存在时长,子类在构造方法中调用setExpireAfterWriteDuration(int duration)来更改
   private TimeUnit timeUnit = TimeUnit.MINUTES;   //时间单位(分钟)

   private Date resetTime;     //Cache初始化或被重置的时间
   private long highestSize=0; //历史最高记录数
   private Date highestTime;   //创造历史记录的时间

   private LoadingCache<K, V> cache;

   /**
    * 通过调用getCache().get(key)来获取数据
    * @return cache
    */
   public LoadingCache<K, V> getCache() {
       if(cache == null){  //使用双重校验锁保证只有一个cache实例
           synchronized (this) {
               if(cache == null){
                   cache = CacheBuilder.newBuilder().maximumSize(maximumSize)      //缓存数据的最大条目,也可以使用.maximumWeight(weight)代替
                           .expireAfterWrite(expireAfterWriteDuration, timeUnit)   //数据被创建多久后被移除
                           .recordStats()                                          //启用统计
                           .build(new CacheLoader<K, V>() {
                               @Override
                               public V load(K key) throws Exception {
                                   return fetchData(key);
                               }
                           });
                   this.resetTime = new Date();
                   this.highestTime = new Date();
                   logger.debug("本地缓存{}初始化成功", this.getClass().getSimpleName());
               }
           }
       }

       return cache;
   }

   /**
    * 根据key从数据库或其他数据源中获取一个value,并被自动保存到缓存中。
    * @param key
    * @return value,连同key一起被加载到缓存中的。
    */
   protected abstract V fetchData(K key) throws Exception;

   /**
    * 从缓存中获取数据(第一次自动调用fetchData从外部获取数据),并处理异常
    * @param key
    * @return Value
    * @throws ExecutionException
    */
   protected V getValue(K key) throws ExecutionException {
       V result = getCache().get(key);
       if(getCache().size() > highestSize){
           highestSize = getCache().size();
           highestTime = new Date();
       }

       return result;
   }

   public long getHighestSize() {
       return highestSize;
   }

   public Date getHighestTime() {
       return highestTime;
   }

   public Date getResetTime() {
       return resetTime;
   }

   public void setResetTime(Date resetTime) {
       this.resetTime = resetTime;
   }

   public int getMaximumSize() {
       return maximumSize;
   }

   public int getExpireAfterWriteDuration() {
       return expireAfterWriteDuration;
   }

   public TimeUnit getTimeUnit() {
       return timeUnit;
   }

   /**
    * 设置最大缓存条数
    * @param maximumSize
    */
   public void setMaximumSize(int maximumSize) {
       this.maximumSize = maximumSize;
   }

   /**
    * 设置数据存在时长(分钟)
    * @param expireAfterWriteDuration
    */
   public void setExpireAfterWriteDuration(int expireAfterWriteDuration) {
       this.expireAfterWriteDuration = expireAfterWriteDuration;
   }
}
复制代码
  1. ILocalCacheキャッシュが呼び出されるインタフェース(ビジネス・オペレーションのクラスとインタフェースモード)
public interface ILocalCache <K, V> {

    /**
     * 从缓存中获取数据
     * @param key
     * @return value
     */
    public V get(K key);
}
复制代码
  1. キャッシュ・インスタンスを取得する実装キャッシュ
import com.cn.xxx.xxx.bean.area.Area;
import com.cn.xxx.xxx.mapper.area.AreaMapper;
import com.cn.xxx.xxx.service.area.AreaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


/**
 * @author LiJing
 * @ClassName: LCAreaIdToArea
 * @date 2019/07/02 11:12
 */

@Component
public class AreaCache extends GuavaAbstractLoadingCache<Long, Area> implements ILocalCache<Long, Area> {

    @Autowired
    private AreaService areaService;

    //由Spring来维持单例模式
    private AreaCache() {
        setMaximumSize(4000); //最大缓存条数
    }

    @Override
    public Area get(Long key) {
        try {
            Area value = getValue(key);
            return value;
        } catch (Exception e) {
            logger.error("无法根据baseDataKey={}获取Area,可能是数据库中无该记录。", key, e);
            return null;
        }
    }

    /**
     * 从数据库中获取数据
     */
    @Override
    protected Area fetchData(Long key) {
        logger.debug("测试:正在从数据库中获取area,area id={}", key);
        return areaService.getAreaInfo(key);
    }
}
复制代码

だから、使用する準備ができて、キャッシュを構築するためにシンプルな、はるかに、より完全な。原理はキャッシュクエリを既存のデータベース・クエリは、あなたが設定したプロパティに基づいて、キャッシュを維持するために行く、キャッシュにのみ統合を行っていないということですキャッシュは、上記拡張することができるインターフェースは栗の場合............キャッシュされている実装します

キャッシュ管理

  1. 再びキャッシュ管理を書き、ここでキャッシュ管理を一元管理するために、コントローラに戻るには、統一されたキャッシュ管理であります
import com.cn.xxx.common.core.page.PageParams;
import com.cn.xxx.common.core.page.PageResult;
import com.cn.xxx.common.core.util.SpringContextUtil;
import com.google.common.cache.CacheStats;

import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.ConcurrentMap;

/**
 * @ClassName: GuavaCacheManager
 * @author LiJing
 * @date 2019/07/02 11:17 
 *
 */
public class GuavaCacheManager {

    //保存一个Map: cacheName -> cache Object,以便根据cacheName获取Guava cache对象
    private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> cacheNameToObjectMap = null;

    /**
     * 获取所有GuavaAbstractLoadingCache子类的实例,即所有的Guava Cache对象
     * @return
     */

    @SuppressWarnings("unchecked")
    private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> getCacheMap(){
        if(cacheNameToObjectMap==null){
            cacheNameToObjectMap = (Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>>) SpringContextUtil.getBeanOfType(GuavaAbstractLoadingCache.class);
        }
        return cacheNameToObjectMap;

    }

    /**
     *  根据cacheName获取cache对象
     * @param cacheName
     * @return
     */
    private static GuavaAbstractLoadingCache<Object, Object> getCacheByName(String cacheName){
        return (GuavaAbstractLoadingCache<Object, Object>) getCacheMap().get(cacheName);
    }

    /**
     * 获取所有缓存的名字(即缓存实现类的名称)
     * @return
     */
    public static Set<String> getCacheNames() {
        return getCacheMap().keySet();
    }

    /**
     * 返回所有缓存的统计数据
     * @return List<Map<统计指标,统计数据>>
     */
    public static ArrayList<Map<String, Object>> getAllCacheStats() {

        Map<String, ? extends Object> cacheMap = getCacheMap();
        List<String> cacheNameList = new ArrayList<>(cacheMap.keySet());
        Collections.sort(cacheNameList);//按照字母排序

        //遍历所有缓存,获取统计数据
        ArrayList<Map<String, Object>> list = new ArrayList<>();
        for(String cacheName : cacheNameList){
            list.add(getCacheStatsToMap(cacheName));
        }

        return list;
    }

    /**
     * 返回一个缓存的统计数据
     * @param cacheName
     * @return Map<统计指标,统计数据>
     */
    private static Map<String, Object> getCacheStatsToMap(String cacheName) {
        Map<String, Object> map =  new LinkedHashMap<>();
        GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
        CacheStats cs = cache.getCache().stats();
        NumberFormat percent = NumberFormat.getPercentInstance(); // 建立百分比格式化用
        percent.setMaximumFractionDigits(1); // 百分比小数点后的位数
        map.put("cacheName", cacheName);//Cache名称
        map.put("size", cache.getCache().size());//当前数据量
        map.put("maximumSize", cache.getMaximumSize());//最大缓存条数
        map.put("survivalDuration", cache.getExpireAfterWriteDuration());//过期时间
        map.put("hitCount", cs.hitCount());//命中次数
        map.put("hitRate", percent.format(cs.hitRate()));//命中比例
        map.put("missRate", percent.format(cs.missRate()));//读库比例
        map.put("loadSuccessCount", cs.loadSuccessCount());//成功加载数
        map.put("loadExceptionCount", cs.loadExceptionCount());//成功加载数
        map.put("totalLoadTime", cs.totalLoadTime()/1000000);       //总加载毫秒ms
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if(cache.getResetTime()!=null){
            map.put("resetTime", df.format(cache.getResetTime()));//重置时间
            LocalDateTime localDateTime = LocalDateTime.ofInstant(cache.getResetTime().toInstant(), ZoneId.systemDefault()).plusMinutes(cache.getTimeUnit().toMinutes(cache.getExpireAfterWriteDuration()));
            map.put("survivalTime", df.format(Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())));//失效时间
        }
        map.put("highestSize", cache.getHighestSize());//历史最高数据量
        if(cache.getHighestTime()!=null){
            map.put("highestTime", df.format(cache.getHighestTime()));//最高数据量时间
        }

        return map;
    }

    /**
     * 根据cacheName清空缓存数据
     * @param cacheName
     */
    public static void resetCache(String cacheName){
        GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
        cache.getCache().invalidateAll();
        cache.setResetTime(new Date());
    }

    /**
     * 分页获得缓存中的数据
     * @param pageParams
     * @return
     */
    public static PageResult<Object> queryDataByPage(PageParams<Object> pageParams) {
        PageResult<Object> data = new PageResult<>(pageParams);

        GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName((String) pageParams.getParams().get("cacheName"));
        ConcurrentMap<Object, Object> cacheMap = cache.getCache().asMap();
        data.setTotalRecord(cacheMap.size());
        data.setTotalPage((cacheMap.size()-1)/pageParams.getPageSize()+1);

        //遍历
        Iterator<Map.Entry<Object, Object>> entries = cacheMap.entrySet().iterator();
        int startPos = pageParams.getStartPos()-1;
        int endPos = pageParams.getEndPos()-1;
        int i=0;
        Map<Object, Object> resultMap = new LinkedHashMap<>();
        while (entries.hasNext()) {
            Map.Entry<Object, Object> entry = entries.next();
            if(i>endPos){
                break;
            }

            if(i>=startPos){
                resultMap.put(entry.getKey(), entry.getValue());
            }

            i++;
        }
        List<Object> resultList = new ArrayList<>();
        resultList.add(resultMap);
        data.setResults(resultList);
        return data;
    }
}
复制代码
  1. キャッシュサービス:
import com.alibaba.dubbo.config.annotation.Service;
import com.cn.xxx.xxx.cache.GuavaCacheManager;
import com.cn.xxx.xxx.service.cache.CacheService;

import java.util.ArrayList;
import java.util.Map;

/**
 * @ClassName: CacheServiceImpl
 * @author lijing
 * @date 2019.07.06 下午 5:29
 *
 */
@Service(version = "1.0.0")
public class CacheServiceImpl implements CacheService {

    @Override
    public ArrayList<Map<String, Object>> getAllCacheStats() {
        return GuavaCacheManager.getAllCacheStats();
    }

    @Override
    public void resetCache(String cacheName) {
        GuavaCacheManager.resetCache(cacheName);
    }
}
复制代码
import com.alibaba.dubbo.config.annotation.Reference;
import com.cn.xxx.common.core.page.JsonResult;
import com.cn.xxx.xxx.service.cache.CacheService;
import com.github.pagehelper.PageInfo;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @ClassName: CacheAdminController
 * @author LiJing
 * @date 2018/07/06 10:10 
 *
 */
@Controller
@RequestMapping("/cache")
public class CacheAdminController {

    @Reference(version = "1.0.0")
    private CacheService cacheService;

    @GetMapping("")
    @RequiresPermissions("cache:view")
    public String index() {
        return "admin/system/cache/cacheList";
    }

    @PostMapping("/findPage")
    @ResponseBody
    @RequiresPermissions("cache:view")
    public PageInfo findPage() {
        return new PageInfo<>(cacheService.getAllCacheStats());
    }

    /**
     * 清空缓存数据、并返回清空后的统计信息
     * @param cacheName
     * @return
     */
    @RequestMapping(value = "/reset", method = RequestMethod.POST)
    @ResponseBody
    @RequiresPermissions("cache:reset")
    public JsonResult cacheReset(String cacheName) {
        JsonResult jsonResult = new JsonResult();

        cacheService.resetCache(cacheName);
        jsonResult.setMessage("已经成功重置了" + cacheName + "!");

        return jsonResult;
    }

    /**
     * 查询cache统计信息
     * @param cacheName
     * @return cache统计信息
     */
    /*@RequestMapping(value = "/stats", method = RequestMethod.POST)
    @ResponseBody
    public JsonResult cacheStats(String cacheName) {
        JsonResult jsonResult = new JsonResult();

        //暂时只支持获取全部

        switch (cacheName) {
            case "*":
                jsonResult.setData(GuavaCacheManager.getAllCacheStats());
                jsonResult.setMessage("成功获取了所有的cache!");
                break;

            default:
                break;
        }

        return jsonResult;
    }*/

    /**
     * 返回所有的本地缓存统计信息
     * @return
     */
    /*@RequestMapping(value = "/stats/all", method = RequestMethod.POST)
    @ResponseBody
    public JsonResult cacheStatsAll() {
        return cacheStats("*");
    }*/

    /**
     * 分页查询数据详情
     * @param params
     * @return
     */
    /*@RequestMapping(value = "/queryDataByPage", method = RequestMethod.POST)
    @ResponseBody
    public PageResult<Object> queryDataByPage(@RequestParam Map<String, String> params){
        int pageSize = Integer.valueOf(params.get("pageSize"));
        int pageNo = Integer.valueOf(params.get("pageNo"));
        String cacheName = params.get("cacheName");

        PageParams<Object> page = new PageParams<>();
        page.setPageSize(pageSize);
        page.setPageNo(pageNo);
        Map<String, Object> param = new HashMap<>();
        param.put("cacheName", cacheName);
        page.setParams(param);

        return GuavaCacheManager.queryDataByPage(page);
    }*/
}
复制代码

結論

これらは我々が再起動し、キャッシュのクリアを、各キャッシュ管理およびキャッシュの統計情報を表示することができ、バックグラウンドでgauvaキャッシュされ、キャッシュ下手に書かれた多くの場合、変更しない頻繁に使用されるデータ、歓迎批判 - 次は背景ページ表示です:

  

おすすめ

転載: juejin.im/post/5d3561bf518825165444687f