캐싱, 모두가 익숙할 거라 생각합니다. 프로젝트에서 캐싱은 꼭 필요합니다. Redis, Guava Cache 또는 EHcache와 같은 많은 캐싱 도구가 시중에 나와 있습니다.
이러한 도구는 모두가 매우 익숙할 것이라고 생각하므로 오늘은 로컬 캐싱 구현 방법에 대해 이야기하지 않겠습니다. 위의 도구를 참조하여 더 나은 로컬 캐시를 달성하려면 Pingtou 형제는 다음 세 가지 측면에서 시작해야 한다고 믿습니다.
1. 저장소 컬렉션 선택
로컬 캐싱을 구현하려면 저장소 컨테이너가 키/값 형식의 데이터 구조여야 합니다. Java에서는 일반적으로 사용되는 맵 컬렉션입니다. Map에는 HashMap, Hashtable, ConcurrentHashMap이 있는데, 높은 동시성에서 데이터 보안 문제를 고려하지 않는다면 HashMap과 Hashtable 중 하나를 선택할 수 있습니다. ConcurrentHashMap.Collection이지만 ConcurrentHashMap의 성능이 Hashtable보다 우수하기 때문에 ConcurrentHashMap을 선호합니다.
2. 만료된 캐시 처리
캐시는 메모리에 직접 저장되기 때문에 만료된 캐시를 처리하지 않으면 메모리는 우리가 원하는 것과는 다른 수많은 유효하지 않은 캐시에 의해 점유될 것입니다. 따라서 잘못된 캐시를 정리해야 합니다. 만료된 캐시 처리는 Redis 전략을 참조하여 구현할 수 있습니다. Redis는 일반 삭제 + 지연 제거 전략을 채택합니다.
주기적 삭제 전략
주기적 삭제 전략은 만료된 캐시를 정기적으로 감지하여 삭제하는 것입니다. 이 전략의 장점은 만료된 캐시가 삭제된다는 것입니다. 또한 만료된 캐시는 시간 내에 삭제되지 않을 수 있습니다. 이는 우리가 설정한 타이밍 빈도와 관련이 있습니다. 또 다른 단점은 캐시된 데이터가 많으면 각 감지가 많은 압력을 가한다는 것입니다. .
지연 제거 전략
지연 제거 전략은 캐시 사용 시 캐시가 만료되었는지 먼저 확인하고, 만료되면 삭제하고 빈 상태로 반환하는 것입니다. 이 전략의 장점은 검색 시 만료 여부만 확인할 수 있어 CUP에 미치는 영향이 적다는 것입니다. 동시에 이 전략에는 치명적인 단점이 있습니다. 많은 수의 캐시가 저장되면 해당 캐시는 사용되지 않고 만료되며 유효하지 않은 캐시가 많은 양의 메모리 공간을 차지하게 됩니다. 결국 서버 메모리가 오버플로될 수 있습니다.
Redis의 두 가지 만료 캐시 처리 전략을 간략하게 살펴보았습니다. 각 전략에는 고유한 장점과 단점이 있습니다. 따라서 사용 중에 두 가지 전략을 결합할 수 있으며 결합된 효과는 여전히 매우 이상적입니다.
3. 캐시 제거 전략
캐시 제거는 만료된 캐시 처리와 구별되어야 합니다. 캐시 제거는 캐시 수가 우리가 지정한 캐시 수에 도달하는 경우를 의미하므로 결국 우리의 메모리는 무제한이 아닙니다. 캐시를 계속 추가해야 하는 경우 새로 추가된 캐시를 위한 공간을 확보하기 위해 특정 전략에 따라 기존 캐시에서 일부 캐시를 제거해야 합니다. 일반적으로 사용되는 몇 가지 캐시 제거 전략에 대해 알아 보겠습니다.
선입선출 정책
캐시 공간이 부족하여 새 데이터를 수용할 새 공간을 확보할 수 없을 때 캐시에 처음 들어간 데이터가 먼저 지워집니다. 이 전략은 주로 캐시된 요소의 생성 시간을 비교합니다. 상대적으로 높은 데이터 효율성이 필요한 일부 시나리오에서는 최신 데이터를 사용할 수 있도록 보장하는 데 우선순위를 두는 전략으로 이러한 유형의 전략을 고려할 수 있습니다.
최소 사용 전략
만료 여부에 관계없이 요소 사용 횟수에 따라 덜 자주 사용된 요소를 지워 공간을 확보합니다. 이 전략은 주로 요소의 hitCount(적중 횟수)를 비교합니다. 이러한 유형의 전략은 고주파 데이터의 유효성이 보장되는 시나리오에서 선택할 수 있습니다.
최근에 가장 적게 사용된 전략
만료 여부에 관계없이 요소의 마지막으로 사용된 타임스탬프를 기준으로 가장 멀리 사용된 타임스탬프가 있는 요소를 지워 공간을 확보합니다. 이 전략은 주로 캐시가 get에 의해 마지막으로 사용된 시간을 비교합니다. 이는 핫 데이터 시나리오에 더 적합하며, 핫 데이터의 유효성을 보장하는 데 우선순위가 부여됩니다.
무작위 제거 전략
캐시 만료 여부에 관계없이 캐시를 무작위로 제거합니다. 캐시 데이터에 대한 요구 사항이 없다면 이 전략을 사용해 볼 수 있습니다.
비제거 전략
캐시가 지정된 값에 도달하면 캐시는 제거되지 않지만, 캐시가 제거될 때까지 더 이상 캐시를 추가할 수 없습니다.
위 내용은 로컬 캐시를 구현하기 위해 고려해야 할 세 가지 사항입니다. 이 내용을 읽고 나면 로컬 캐시를 구현하는 방법을 함께 알아볼까요?
로컬 캐싱 구현
이 데모에서는 ConcurrentHashMap을 스토리지 컬렉션으로 사용하므로 동시성이 높은 상황에서도 캐시의 안전성을 보장할 수 있습니다. 만료된 캐시 처리의 경우 여기서는 예약 삭제 전략만 사용하고 예약 삭제 + 지연 제거 전략은 사용하지 않았습니다. 만료된 캐시 처리에는 이 두 가지 전략을 직접 사용해 볼 수 있습니다. 캐시 제거 측면에서 여기서는 최소 사용 전략을 사용하겠습니다. 자, 이제 기술적인 선택을 알았으니 코드 구현을 살펴보겠습니다.
캐시 객체 클래스
public class Cache implements Comparable<Cache>{ // 键 private Object key; // 缓存值 private Object value; // 最后一次访问时间 private long accessTime; // 创建时间 private long writeTime; // 存活时间 private long expireTime; // 命中次数 private Integer hitCount; ...getter/setter()...
캐시 추가
/** * 添加缓存 * * @param key * @param value */ public void put(K key, V value,long expire) { checkNotNull(key); checkNotNull(value); // 当缓存存在时,更新缓存 if (concurrentHashMap.containsKey(key)){ Cache cache = concurrentHashMap.get(key); cache.setHitCount(cache.getHitCount()+1); cache.setWriteTime(System.currentTimeMillis()); cache.setAccessTime(System.currentTimeMillis()); cache.setExpireTime(expire); cache.setValue(value); return; } // 已经达到最大缓存 if (isFull()) { Object kickedKey = getKickedKey(); if (kickedKey !=null){ // 移除最少使用的缓存 concurrentHashMap.remove(kickedKey); }else { return; } } Cache cache = new Cache(); cache.setKey(key); cache.setValue(value); cache.setWriteTime(System.currentTimeMillis()); cache.setAccessTime(System.currentTimeMillis()); cache.setHitCount(1); cache.setExpireTime(expire); concurrentHashMap.put(key, cache); }
캐시 가져오기
/** * 获取缓存 * * @param key * @return */ public Object get(K key) { checkNotNull(key); if (concurrentHashMap.isEmpty()) return null; if (!concurrentHashMap.containsKey(key)) return null; Cache cache = concurrentHashMap.get(key); if (cache == null) return null; cache.setHitCount(cache.getHitCount()+1); cache.setAccessTime(System.currentTimeMillis()); return cache.getValue(); }
최소 사용 캐시 가져오기
/** * 获取最少使用的缓存 * @return */ private Object getKickedKey() { Cache min = Collections.min(concurrentHashMap.values()); return min.getKey(); }
만료된 캐시 감지 방법
/** * 处理过期缓存 */ class TimeoutTimerThread implements Runnable { public void run() { while (true) { try { TimeUnit.SECONDS.sleep(60); expireCache(); } catch (Exception e) { e.printStackTrace(); } } } /** * 创建多久后,缓存失效 * * @throws Exception */ private void expireCache() throws Exception { System.out.println("检测缓存是否过期缓存"); for (Object key : concurrentHashMap.keySet()) { Cache cache = concurrentHashMap.get(key); long timoutTime = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - cache.getWriteTime()); if (cache.getExpireTime() > timoutTime) { continue; } System.out.println(" 清除过期缓存 : " + key); //清除过期缓存 concurrentHashMap.remove(key); } } }
위 내용은 Java 로컬 캐시를 구현하려면 다음 지점부터 시작하십시오.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!