• LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。
  • LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。

jodd.cache.LRUCache本身使用很简单,

static LRUCache<String, String> cache = new LRUCache(500000);

直接new 一个LRUCache, 并设置缓存大小即可, 来一条数据get一下, 没有就继续走, 有就返回, 并且每条最后都再put一次.  实测LRUCache 和LFUCache缓存命中率极其接近, 但LRUCache效率稍高(其实效率都很高, 相较于qps 700左右的redisCache).

为什么不用redisCache, 实测redisCache的qps在700左右, 不能满足线上需求, 而LRUCache/LFUCache 10w缓存qps103w/128w, 100w缓存qps52w/73w.

为什么不设置缓存过期时间, 实测不管是本地缓存还是redis缓存, 加了过期时间后性能都下降厉害, 如LRUCache 设置缓存10w, 1h过期时间, qps只有3000, 且设置过期时间后命中率相差不大.

为什么用jodd.cache.LRUCache, 因为先采用的org.redisson.cache.LRUCacheMap被坑了, 也同样有验证的并发bug. org.redisson.cache.LRUCacheMap 在多线程, 缓存满时, 会导致cpu占用100%! 并且被阻塞.

jodd.cache.LRUCache有坑吗, 有, jodd.cache.LRUCache也有严重的并发bug, 在多线程中对LRUCache.put时, LRUCache.size会超过初始化定义的size! 然后内存占用缓慢增加直到100%,  见下面测试代码跑出来的日志和内存信息.

import jodd.cache.LRUCache;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.File;
import java.util.List;
import java.util.concurrent.*;public class LruStressTest {private final static Logger logger = LoggerFactory.getLogger(LruStressTest.class);public static void main(String[] args) throws Exception {logger.info("test start...");test2();}private final static Semaphore semaphore = new Semaphore(30);private static long total = 0L;private static long hitCount = 0L;private static LRUCache<String, String> cache = new LRUCache(500000);private static ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10000);static void test2() throws Exception {LruStressTest lruStressTest = new LruStressTest();//start queue threadExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();singleThreadExecutor.submit(() -> {try {readToQueue();} catch (Exception e) {e.printStackTrace();}});//consumer threadlong startMs = System.currentTimeMillis();ExecutorService taskThreadExecutor = Executors.newFixedThreadPool(30);for (; ; ) {semaphore.acquire();total++;String text = queue.take();taskThreadExecutor.submit(() -> {try {String v = lruStressTest.getCacheValue(text);if (StringUtils.isNotBlank(v)) {hitCount++;} else {v = String.valueOf(System.currentTimeMillis());TimeUnit.MILLISECONDS.sleep(1);}lruStressTest.putCacheValue(text, v);} catch (Exception e) {e.printStackTrace();throw new IllegalStateException("handle error");} finally {semaphore.release();}});if (total % 10000 == 0){logger.info("total: {}, hits: {}, queue.size: {} spendMs: {}", total, hitCount, queue.size(), System.currentTimeMillis() - startMs);logger.info("cache.size: {}, getHitCount: {}, getMissCount: {}, isFull: {}",cache.size(), cache.getHitCount(), cache.getMissCount(), cache.isFull() );}}}public String getCacheValue(String key){return cache.get(key);}//must add synchronized here !public void putCacheValue(String key, String value){cache.put(key, value);}//read local text file to queue, mock prd envprivate static void readToQueue() throws Exception {File dir = new File("E:\\Projects\\text\\");File[] files = dir.listFiles();int i = 0;for (;;){for (File file : files) {List<String> lines = FileUtils.readLines(file);for (String line : lines) {queue.add(line);i++;if (i>=100){if (queue.size()>0) TimeUnit.MILLISECONDS.sleep(10);i = 0;}}}}}}
14:16:22.105 [main] INFO LruStressTest - total: 23700000, hits: 13556179, queue.size: 0 spendMs: 3248056
14:16:22.105 [main] INFO LruStressTest - cache.size: 9734776, getHitCount: 13045869, getMissCount: 9985785, isFull: true
14:16:29.920 [main] INFO LruStressTest - total: 23710000, hits: 13566116, queue.size: 0 spendMs: 3255871
14:16:29.920 [main] INFO LruStressTest - cache.size: 9734776, getHitCount: 13055325, getMissCount: 9985785, isFull: true

跑出来的日志信息 cache.size: 9734776! 内存一直飙高! 所以 cache.put上一定要加个锁, 实测加锁后正常, 且性能无差异.

    public synchronized String getCacheValue(String key){return cache.get(key);}public synchronized void putCacheValue(String key, String value){cache.put(key, value);}
16:06:59.832 [main] INFO LruStressTest - total: 43490000, hits: 7916914, queue.size: 0 spendMs: 5100450
16:06:59.832 [main] INFO LruStressTest - cache.size: 500000, getHitCount: 7917744, getMissCount: 35572254, isFull: true
16:07:01.045 [main] INFO LruStressTest - total: 43500000, hits: 7918649, queue.size: 0 spendMs: 5101663
16:07:01.045 [main] INFO LruStressTest - cache.size: 500000, getHitCount: 7919478, getMissCount: 35580522, isFull: true

加锁后cache.size正常, 内存最高占用2G且后续回收正常了.

jodd.cache.LRUCache: 小巧的本地缓存, 及其并发bug相关推荐

  1. 小巧的本地缓存Jodd

    小巧的本地缓存Jodd 说到缓存,大家容易想到memcached和redis,它们大名鼎鼎,但都是远程缓存,需要通过TCP网络访问. 这些缓存服务器本身性能很好,但不管性能再怎么好,也要通过网络访问, ...

  2. 重新认识下JVM级别的本地缓存框架Guava Cache——优秀从何而来

    Guava Cache初识 Guava是Google提供的一套JAVA的工具包,而Guava Cache则是该工具包中提供的一套完善的JVM级别的高并发缓存框架.其实现机制类似ConcurrentHa ...

  3. 干掉 GuavaCache:Caffeine 才是本地缓存的王

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 话说,中间件的选择上,Spring(SpringBoot ...

  4. ehcache缓存原理_干掉GuavaCache:Caffeine才是本地缓存的王

    话说,中间件的选择上,Spring(SpringBoot)一直是业界的风向标.比如Spring一直使用「Jackson」,而没有使用Gson和fastjson.SpringBoot2.0默认数据库连接 ...

  5. Redis与本地缓存组合食用,味道更佳!

    点击下方"IT牧场",选择"设为星标" 来源:juejin.cn/post/7000263632151904293 前言 设计示例 Redis结合本地缓存 后记 ...

  6. 干掉GuavaCache:Caffeine才是本地缓存的王

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群",加入新技术 话说,中间件的选择上,Spring(SpringBoot)一直是业 ...

  7. guava_使用Google Guava Cache进行本地缓存

    guava 很多时候,我们将不得不从数据库或另一个Web服务获取数据或从文件系统加载数据. 在涉及网络呼叫的情况下,将存在固有的网络延迟,网络带宽限制. 解决此问题的方法之一是在应用程序本地拥有一个缓 ...

  8. Caffeine Cache~高性能 Java 本地缓存之王

    前面刚说到Guava Cache,他的优点是封装了get,put操作:提供线程安全的缓存操作:提供过期策略:提供回收策略:缓存监控.当缓存的数据超过最大值时,使用LRU算法替换.这一篇我们将要谈到一个 ...

  9. 使用Google Guava Cache进行本地缓存

    很多时候,我们将不得不从数据库或另一个Web服务获取数据或从文件系统加载数据. 在涉及网络呼叫的情况下,将存在固有的网络等待时间,网络带宽限制. 解决此问题的方法之一是在应用程序本地拥有一个缓存. 如 ...

最新文章

  1. 成绩查看_托福网考免费寄送成绩单,掌握这些知识能帮你省不少钱!
  2. linux 内核模块加载错误 disagree about version of symbol module_layout
  3. 【JAVA】大整数数据量排序
  4. Ansible常用模块详解
  5. filenotfoundexception是什么异常_Java异常处理:给程序罩一层保险
  6. 怀念西电北门的石凳(转载)
  7. linux修改文件句柄数生效_linux修改句柄数
  8. 由几个月前写的(验证码利用ashx一般处理程序来做),修改为不用以一般处理程序...
  9. mysql查询当前时间和用户_MySQL学习笔记(3) - 查询服务器版本,当前时间,当前用户...
  10. Python 面向对象一(转载)
  11. 西门子STEP7 MICROWIN V4 SP5 下载
  12. setValue和setObject的区别
  13. 5线上模式刷2亿bug_GTA5还想冲销量?玩家利用BUG刷钱,遭受比封号更严厉惩罚
  14. Maix Bit(K210) 裸机开发教程(六)摄像头使用
  15. tornado 异步
  16. 学习网络安全应该具备哪些专业技能
  17. Linux下普通用户使用强制位获取root权限
  18. VSPD虚拟串口工具——使用完一定要删除串口
  19. 转自g+,一个有意思的故事
  20. 斗罗大陆CSS_204687

热门文章

  1. Android studio的下载安装教程
  2. Midjourney从入门到精通
  3. 2017 IAI国际创享节第二日:创享不止,精彩继续
  4. shell脚本中单引号和双引号的区别
  5. mercury怎么设置虚拟服务器,XAMPP邮件服务器Mercury的设置方法
  6. python毕业设计作品基于django框架 疫苗预约系统毕设成品(6)开题答辩PPT
  7. 《天堂向左,深圳往右》
  8. Nginx服务器搭建与个人博客部署
  9. SpringMvc 与 Struts2 区别总结
  10. Qt QString详解