Hand mo hand - learning Springboot in using Guava cache can not learn to build you lose a pack of hot strip me

Overview Introduction

  1. The cache is often applied to the daily development of a technical means, rational use of caching can greatly improve the performance of applications.
  2. Guava official description of the Cache connection
  3. Cache is useful in a variety of use cases. For example, when calculating or retrieving values ​​very expensive, you should consider using caching, and need it more than once in the value of an input.
  4. ConcurrentMap cache is smaller, but not identical. The most fundamental difference is that a ConcurrentMap insist that all added to it until they are explicitly deleted elements.
  5. On the other hand, the cache is generally configured to automatically withdraw an entry to limit its memory footprint.
  6. In some cases, even if you can not expel a LoadingCache entry it is useful because it automatically loads the cache.

applicability

  1. You are willing to spend some memory to increase speed. You are willing to spend some memory to improve speed.
  2. Key is sometimes more than once that you want to be queried. You expect that keys will sometimes get queried more than once.
  3. You do not need to cache to store more data than anything else suitable. (Guava cache is run local applications). Your cache will not need to store more data than what would fit inRAM. (Guava caches are local to a single run of your application.
  4. They do not store data in a file, not stored on an external server. If this does not suit your needs, consider a tool like memcached, redis and so on.

Based on the recovery cited

(Reference-based Eviction) strong (strong), soft (Soft), weak (weak), virtual (Phantom) references - Reference: value of the weak referenced by using a key, or weak values referenced, or soft referenced GuavaCache can cache is set to allow garbage collection:

  1. CacheBuilder.weakKeys (): reference memory using a weak bond. When no other key (soft or strong) references the cache entry may be garbage collected. Since garbage collection depends only Identities (==), using a weak bond with reference to the cache instead == Comparison equals key.
  2. CacheBuilder.weakValues ​​(): Use weak references stored value. When no further value (strong or soft) references the cache entry may be garbage collected. Since garbage collection depends only Identities (==), the use of weak references the cached value with a comparison value equals == instead.
  3. CacheBuilder.softValues ​​(): Use soft references stored value. Soft reference only in response to memory demand before the global recovery in the order of least recently used. Considering the performance impact of using soft references, we generally recommend the use of more predictable performance cache size limit (see above, based on the capacity of recovery). Use a soft reference value with the same buffer == rather than equals the comparison value.

Cache loading

CacheBuilder class using the Guava cache constructed in two different cache loading CacheLoader, Callable, together with the logic model are based builder is loaded key value. The difference is that the definition of CacheLoader too general to be defined for the entire cache, can be considered a uniform method according to key value load value, and Callable way more flexible, allowing you to get in when the specified load method. Look at the following code:

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";
                    }
                });
复制代码

Cache remove

guava way to do remove the cache when the data is divided into passive and active removal of two kinds of removing the removal of data guava.

  • Passive way to remove data, guava default provides three ways:
  1. Based on the size of the removed: see literally know is in accordance with the size of the cache to remove, if about to reach the specified size, it is not commonly used key-value pairs will be removed from the cache.

Re-defined manner generally CacheBuilder.maximumSize (long), there is a right to be considered a way method, personally I think that is not used in actual use. Common point of view on this note that there are several points,

  • First, the size refers to the number of entries in the cache, not the memory size or other;
  • Second, not entirely to the specified size system began removing infrequently used data, but close to this size when the system will start to do the removal actions;
  • Third, if a key-value pair has been removed from the cache, you again requesting access time, if cachebuild using cacheloader way, it still will still then take a value from cacheloader, if this has not, throws an exception
  1. Based on the removal time: the Guava offers two time-based removal method
  • expireAfterAccess (long, TimeUnit) This method is to remove much of the time since the last access based on certain key
  • expireAfterWrite (long, TimeUnit) of this method is to remove the amount of time after being created or a value to be replaced according to a key
  1. Remove the reference-based:   This way to remove the garbage collection mechanism is mainly based on java, decided to remove the reference relationship according to key or value
  • Initiative to remove the data mode, take the initiative to remove three ways:
  • Alone was removed with Cache.invalidate (key)
  • Batch removed by Cache.invalidateAll (keys)
  • Remove all with Cache.invalidateAll ()

If you need some action you can also define Removal Listener when erasing data, but little should be noted that the default Removal Listener Behavior and removal action is executed synchronously, asynchronously If you need to change the form, consider using RemovalListeners. asynchronous (RemovalListener, Executor)

Practical exercise

  1. maven dependence
 <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
 </dependency>
复制代码
  1. GuavaAbstractLoadingCache loading buffer and basic properties using the base class (I use 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 cache gets called interfaces (interface mode with a class of business operations)
public interface ILocalCache <K, V> {

    /**
     * 从缓存中获取数据
     * @param key
     * @return value
     */
    public V get(K key);
}
复制代码
  1. Implementation cache obtaining cache instance
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);
    }
}
复制代码

So far, more complete, simpler to build the cache, ready for use. The principle is that existing cache query the database query did not go into the cache, go to maintain cache, based on the properties you set, only integration cache implements the interface can be extended above is cached ............ For chestnuts

Cache Management

  1. Again write cache management, cache management here is a unified cache management to return to the Controller to unified management
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. Cache service:
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);
    }*/
}
复制代码

Conclusion

These are the gauva cache in the background, we can restart and clear the cache, each cache management and viewing cache statistics, cache frequently used data that do not change often poorly written, welcome criticism - The following is a background page display :

  

Guess you like

Origin juejin.im/post/5d3561bf518825165444687f