IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> jodd.cache.LRUCache: 小巧的本地缓存 及其并发bug -> 正文阅读

[Java知识库]jodd.cache.LRUCache: 小巧的本地缓存 及其并发bug

  • 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 thread
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.submit(() -> {
            try {
                readToQueue();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        //consumer thread
        long 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 env
    private 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且后续回收正常了.

?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-08 22:14:58  更:2022-03-08 22:17:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:02:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码