在搜索系统中,如何缓存搜索最频繁的1000个搜索结果?自定制的精准短文本搜索服务项目代码
本文利用了ConcurrentHashMap和AtomicLong实现了线程安全且支持高并发的最频繁访问驻留缓存算法,除了缓存功能,还提供了缓存状态查询接口,非常实用。
比如,在搜索管理界面可看到如下缓存状态:
缓存状态
最大缓存数量: 1000
当前缓存数量: 11
驱逐缓存次数: 0
命中缓存次数: 6
未命中缓存次数: 11
缓存命中比例: 35.294117 %
搜索缓存命中情况(11)
1 | L | 3 |
2 | LYB | 2 |
3 | LY | 1 |
4 | LANGYB | 0 |
5 | X | 0 |
6 | LANG | 0 |
7 | XL | 0 |
8 | LAN | 0 |
9 | XLF | 0 |
10 | LANGY | 0 |
11 | XLFD | 0 |
实现代码如下:
import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** * 最频繁访问驻留缓存算法 * Created by ysc on 7/18/16. */ public class ConcurrentLRUCache<K, V> { private int maxCacheSize; private Map<K, CacheItem<V>> cache = new ConcurrentHashMap<>(); private AtomicLong totalEvictCount = new AtomicLong(); private AtomicLong hitCount = new AtomicLong(); private AtomicLong notHitCount = new AtomicLong(); public ConcurrentLRUCache(int maxCacheSize) { cache = new ConcurrentHashMap<>(maxCacheSize, 1, 10); this.maxCacheSize = maxCacheSize; } public String getStatus(){ StringBuilder status = new StringBuilder(); long total = hitCount.get()+notHitCount.get(); status.append("最大缓存数量: ").append(maxCacheSize).append("\n") .append("当前缓存数量: ").append(getActualCacheSize()).append("\n") .append("驱逐缓存次数: ").append(totalEvictCount.get()).append("\n") .append("命中缓存次数: ").append(hitCount.get()).append("\n") .append("未命中缓存次数: ").append(notHitCount.get()).append("\n") .append("缓存命中比例: ").append(total == 0 ? 0 : hitCount.get()/(float)total*100).append(" %\n"); return status.toString(); } public String getKeyAndHitCount(){ StringBuilder status = new StringBuilder(); AtomicInteger i = new AtomicInteger(); cache.entrySet().stream().sorted((a,b)->b.getValue().getCount()-a.getValue().getCount()).forEach(entry->status.append(i.incrementAndGet()).append("\t").append(entry.getKey()).append("\t").append(entry.getValue().getCount()).append("\n")); return status.toString(); } public int getMaxCacheSize() { return maxCacheSize; } public int getActualCacheSize() { return cache.size(); } public Map<K, CacheItem<V>> getCache() { return Collections.unmodifiableMap(cache); } public AtomicLong getTotalEvictCount() { return totalEvictCount; } public long getHitCount() { return hitCount.longValue(); } public long getNotHitCount() { return notHitCount.longValue(); } public void put(K key, V value){ if(cache.size() >= maxCacheSize){ // evict count int evictCount = (int)(maxCacheSize*0.1); if(evictCount < 1){ evictCount = 1; } totalEvictCount.addAndGet(evictCount); cache.entrySet().stream().sorted((a,b)->a.getValue().getCount()-b.getValue().getCount()).limit(evictCount).forEach(entry->cache.remove(entry.getKey())); return; } cache.put(key, new CacheItem<V>(value, new AtomicInteger())); } public V get(K key){ CacheItem<V> item = cache.get(key); if(item != null){ item.hit(); hitCount.incrementAndGet(); return item.getValue(); } notHitCount.incrementAndGet(); return null; } private static class CacheItem<V>{ private V value; private AtomicInteger count; public CacheItem(V value, AtomicInteger count) { this.value = value; this.count = count; } public void hit(){ this.count.incrementAndGet(); } public V getValue() { return value; } public int getCount() { return count.get(); } } public static void main(String[] args) { ConcurrentLRUCache<Integer, Integer> cache = new ConcurrentLRUCache<>(5); for(int i=0; i<9; i++){ cache.put(i, i); if(i%2==0){ for(int j=0; j<i+2; j++){ cache.get(i); } } } System.out.println(cache.getStatus()); System.out.println(cache.getKeyAndHitCount()); } }
相关推荐
这些算法处理的主要问题是缓存(或称为页面)不足时,如何选择应该被替换出内存的页面。本项目实现了一个模拟器,用于演示和比较四种不同的页面置换算法:FIFO(先进先出)、LFU(最不常用)、LRU(最近最少使用)...
LFU在处理短期爆发性访问的页面时表现良好,但对长期低频但突然大量使用的页面可能会造成误判,这些页面可能会因为早期的低频使用而在内存中长期驻留。 3. **DBL (2Q or Double-Queue)** DBL是LRU和LFU的结合,它...
内存缓存是快速访问图片的关键,尤其是在频繁滚动列表时。Android中可以使用LRU(Least Recently Used)算法实现,通过维护一个最大容量的HashMap,当内存不足时,优先移除最近最少使用的图片。这样可以确保常用...
通常,一旦缓存页被访问,就会将其移动到LRU链表头部,以延长其在内存中驻留的时间。但是,如果经常访问的缓存页频繁移动,会导致链表节点的大量移动,影响性能。因此,MySQL的优化措施是,只有热数据区域的后3/4...
- 使用缓存替换策略,如LRU(最近最少使用)算法,确保最常使用的数据驻留在缓存中。 3. **I/O复杂性的分析与优化**: - 分析数据结构操作所需的磁盘读写次数,作为评估其效率的关键指标。 - 设计算法时,优先...
1. **性能提升**:通过将频繁访问的数据放在内存,Alluxio减少了数据读取的延迟,加速了节点相似性算法的计算过程。 2. **资源优化**:通过智能缓存策略,Alluxio避免了重复计算,减少了不必要的资源消耗。 3. **...
这种算法简单但可能导致频繁的替换。 4. **最近最不常用(NM, Not Recently Used)**:与LRU相反,选择最近最不常用的页,但它需要更多的硬件支持来记录每个页的使用情况。 5. **Clock算法**:一种简单而有效的算法...
通过缓存分层(主缓存和副缓存),策略能够有效地识别和保护流行内容,延长其在缓存中的驻留时间。 4. **主缓存(Primary Cache, PC)与副缓存(Secondary Cache, SC)**:主缓存用于识别流行内容,而副缓存则用于...
如果数据库的工作集(即频繁访问的数据页集合)超过了Buffer Pool的大小,那么即使使用LRU,也无法保证所有常用的数据页都驻留在内存中。这会导致频繁的页面替换,增加了不必要的I/O操作,从而降低了系统性能。 ...
操作系统中的存储管理是确保系统高效运行的关键组成部分,LRU(Least Recently Used)是最常见的页面替换算法之一,用于解决内存不足时的数据处理问题。LRU算法的核心思想是:最近最少使用的页面优先被淘汰。当物理...
虚拟存储器系统通常与缓存紧密结合,缓存中存储的是频繁访问的页面,以减少对主存的访问。 8. **虚拟存储器的扩展性**: 通过虚拟地址空间,操作系统可以支持多进程并发,每个进程都有独立的地址空间,避免了地址...
优化代码的目标之一就是尽量让数据驻留在L1缓存中,减少访问更慢的内存层次。 1. **循环展开**:循环展开是缓存优化的一个常见技巧,通过增大循环体,可以一次性加载更多的数据到缓存中。例如,如果你知道一个数组...
1. **减轻数据库压力**:将频繁访问的数据缓存到Memcached,减少对数据库的读写操作。 2. **session共享**:在分布式系统中,通过Memcached共享用户session,提高用户体验。 3. **页面缓存**:对静态页面或动态生成...
7. **缓存管理**:为了提高性能,操作系统还会利用CPU缓存(L1、L2、L3)来存放频繁访问的数据。缓存管理涉及到缓存替换策略和缓存一致性协议。 8. **内存池**:在某些情况下,操作系统或应用程序会使用内存池技术...
8.8 FIFO(先进先出)页替换算法按照页面进入主存的顺序替换,而Clock算法类似,但会忽略已被访问过的页面,以减少近期频繁访问页面的替换。 8.9 页缓冲通过保留被替换出但近期可能再次访问的页,减少磁盘I/O,同时...
3. 最近最久未使用(LRU)算法:替换最近最长时间未被访问的页面。 4. Clock置换算法:一种近似LRU的算法,使用位标记简化实现。 5. 最少使用算法:选择在内存中驻留时间最长的页面进行替换。 6. 页面缓冲算法:结合...
例如,对于一个4块的全相联Cache,通过访问序列的不同,可以看到FIFO可能会导致频繁替换,而LRU则更能保持常用块的驻留。 接下来,讨论的是Cache的更新策略。在CPU执行写操作时,数据只写入Cache而不立即写入主存,...
9. **工作集(Working sets)**:工作集是进程最近使用的页面集合,用于优化页面替换算法,确保常用页面驻留在内存中。 10. **Inode**:在Unix-like系统中,Inode是用于存储文件元数据的数据结构,包含文件大小、...
1. **缓存优化**:利用CPU缓存的局部性原理,通过调整矩阵的存储顺序和计算顺序,使得频繁访问的数据能更好地驻留在高速缓存中,减少主内存访问,从而提高速度。 2. **并行计算**:将矩阵乘法任务分解为多个子任务...