Java后端程序员面经(2-1. 缓存之本地缓存)

来源:互联网 发布:泰瑞克埃文斯生涯数据 编辑:程序博客网 时间:2024/05/29 16:41

说明

本文主要基于面试中碰到的问题进行总结分析,可能不全。

应用场景

1. 对数据一致性要求不强,主要是由于现在web服务一般是多机房部署2. 访问频繁,且更新不频繁的数据,前者体现了缓存的作用减少对DB的压力,后者说明了缓存毕竟是对DB数据的副本,如果经常不一致是肯定不行的3. 缓存的数据量不宜太大,毕竟单机内存还要分配来提供服务,不能太多给你当缓存使用了

技术思想

1. 需要一个装数据的容器,一般来说就是map2. 需要实现数据的读取和加载(将DB结果存入缓存)3. 有一定的策略对缓存数据进行失效(否则会导致长时间数据不一致性)4. 容器需要设置一个上限,并在达到上限时有一定的策略进行删除(见应用场景第三条)

具体实现

1. 自己实现    1-1. 申请一个ConcurrentHashMap(满了的策略不好实现)、或者LinkedHashMap(LRU好实现满了的策略,不过要自己加锁作线程安全)用作存储    1-2. 申请一个Timer()用作定时器,以实现定时对数据进行失效2. guava localCache    2-1. 引用    <dependency>        <groupId>com.google.guava</groupId>        <artifactId>guava</artifactId>        <version>23.0</version>    </dependency>    2-2.使用    private LoadingCache<String, Book> CACHE = CacheBuilder.newBuilder()            .maximumSize(10)            .refreshAfterWrite(10, TimeUnit.SECONDS) // 当缓存数据过期的时候,真正去加载数据的线程会阻塞一段时间,其余线程立马返回过期的值            .expireAfterAccess(15, TimeUnit.SECONDS) // 访问之后多久过期(删除此key的数据)            .build(                    new CacheLoader<String, Book>() {                        @Override                        public Book load(String name) throws Exception {                            return bookDao.getByName(name); // 从DB中加载数据                        }                    }            );    2-3. 调用    public Book getByName(String name) {        return CACHE.getUnchecked(name);    }    2-4. 参数refreshAfterWrite测试    private LoadingCache<String, Book> CACHE = CacheBuilder.newBuilder()            .maximumSize(10)            .refreshAfterWrite(10, TimeUnit.SECONDS)            .expireAfterAccess(60, TimeUnit.SECONDS)            .build(                    new CacheLoader<String, Book>() {                        @Override                        public Book load(String name) throws Exception {                            Book book = bookDao.getByName(name);                            System.out.println("c:\t" + LocalTime.now().getSecond() + book);                            return book;                        }                    }            );       public Book getByNameWithCallable(String name) {        Book result = null;        try {            result = CACHE.get(name, () -> {                Book book = bookDao.getByName(name);                System.out.println("a:\t" + LocalTime.now().getSecond() + book);                try {                    Thread.sleep(5000);                } catch (Exception ex) {                }                return book;            });            System.out.println("b:\t" + LocalTime.now().getSecond() + result);        } catch (ExecutionException e) {            e.printStackTrace();        }        return result;    }    当多条线程调用时,第一条进行加载,其它的返回旧值,而且10s之后被访问就会触发刷新,即输出a那行。    第一个线程调用输出    {    "id":5,    "name":"wdmyong",    "date":{        "year":2017,        "month":"OCTOBER",        "chronology":{            "id":"ISO",            "calendarType":"iso8601"        },        "era":"CE",        "dayOfYear":290,        "dayOfWeek":"TUESDAY",        "leapYear":false,        "dayOfMonth":17,        "monthValue":10    }}第二条线程的输出{    "id": 4,    "name": "wdmyong",    "date": {        "year": 2017,        "month": "OCTOBER",        "chronology": {            "id": "ISO",            "calendarType": "iso8601"        },        "era": "CE",        "dayOfYear": 290,        "dayOfWeek": "TUESDAY",        "leapYear": false,        "dayOfMonth": 17,        "monthValue": 10    }}我的id用了自增,可以看出在第一条线程计算的时候返回的是原来的id为4,等自己计算完之后,第一条线程返回的是id为5的数据。2-4. 上述过程应该还可以完全异步,即第一条计算线程也返回原值,让它异步计算自己计算去,后续待研究。3. 
原创粉丝点击