문제

자주 변경되지 않는 일부 참조 데이터에 대해 DAO를 구현해야 하는 경우가 많습니다.가끔 DAO의 컬렉션 필드에 이를 캐시하여 한 번만 로드하고 필요할 때 명시적으로 업데이트합니다.

그러나 이로 인해 많은 동시성 문제가 발생합니다. 데이터가 로드되거나 업데이트되는 동안 다른 스레드가 데이터에 액세스하려고 시도하면 어떻게 될까요?

분명히 이는 데이터의 getter와 setter를 모두 동기화하여 처리할 수 있지만 대규모 웹 애플리케이션의 경우 이는 상당한 오버헤드입니다.

나는 밀짚모자로서 나에게 필요한 사소한 결함이 있는 예를 포함시켰습니다.이를 구현하는 다른 방법을 제안하십시오.

public class LocationDAOImpl implements LocationDAO {

private List<Location> locations = null;

public List<Location> getAllLocations() {
    if(locations == null) {
        loadAllLocations();
    }
    return locations;
}

더 많은 정보를 얻기 위해 나는 Hibernate와 Spring을 사용하고 있지만 이 요구 사항은 많은 기술에 걸쳐 적용됩니다.

몇 가지 추가 생각:

이것은 코드에서 전혀 처리되지 않아야 하며 대신 ehcache 또는 이와 유사한 것이 처리되도록 해야 합니까?내가 놓친 일반적인 패턴이 있습니까?이를 달성할 수 있는 방법은 분명히 많지만 간단하고 유지 관리가 가능한 패턴을 찾지 못했습니다.

미리 감사드립니다!

도움이 되었습니까?

해결책

나만의 캐싱 솔루션을 빠르게 구현하고 싶다면 다음을 살펴보세요. 이것 책에 대한 리뷰인 JavaSpecialist에 대한 기사 실제 Java 동시성 ~에 의해 브라이언 괴츠.

다음을 사용하여 기본 스레드 안전 캐시를 구현하는 방법에 대해 설명합니다. 미래태스크 그리고 동시해시맵.

이 방법을 사용하면 단 하나의 동시 스레드만 장기 실행 계산을 트리거합니다(귀하의 경우 데이터베이스는 DAO에서 호출합니다).

필요한 경우 캐시 만료를 추가하려면 이 솔루션을 수정해야 합니다.

직접 캐싱하는 것에 대한 또 다른 생각은 가비지 수집입니다.캐시에 WeakHashMap을 사용하지 않으면 GC는 필요한 경우 캐시에서 사용하는 메모리를 해제할 수 없습니다.자주 액세스하지 않는 데이터(그러나 계산하기 어렵기 때문에 여전히 캐싱할 가치가 있는 데이터)를 캐싱하는 경우 WeakHashMap을 사용하여 메모리가 부족할 때 가비지 수집기를 도울 수 있습니다.

다른 팁

가장 간단하고 안전한 방법은 Ehcache 라이브러리 프로젝트에서 이를 사용하여 캐시를 설정합니다.이 사람들은 여러분이 직면할 수 있는 모든 문제를 해결했으며 가능한 한 빨리 라이브러리를 만들었습니다.

내 자신의 참조 데이터 캐시를 롤링한 상황에서 나는 일반적으로 다음을 사용했습니다. ReadWriteLock 스레드 경합을 줄이기 위해.각 접근자는 다음 형식을 취합니다.

public PersistedUser getUser(String userName) throws MissingReferenceDataException {
    PersistedUser ret;

    rwLock.readLock().lock();
    try {
        ret = usersByName.get(userName);

        if (ret == null) {
            throw new MissingReferenceDataException(String.format("Invalid user name: %s.", userName));
        }
    } finally {
        rwLock.readLock().unlock();
    }

    return ret;
}

쓰기 잠금을 해제하는 유일한 방법은 다음과 같습니다. refresh(), 저는 일반적으로 MBean을 통해 노출합니다.

public void refresh() {
    logger.info("Refreshing reference data.");
    rwLock.writeLock().lock();
    try {
        usersById.clear();
        usersByName.clear();

        // Refresh data from underlying data source.

    } finally {
        rwLock.writeLock().unlock();
    }
}

덧붙여서, 나는 다음과 같은 이유로 나만의 캐시를 구현하기로 결정했습니다.

  • 내 참조 데이터 컬렉션은 작기 때문에 항상 모든 것을 메모리에 저장할 수 있습니다.
  • 내 앱은 간단하고 빨라야 합니다.외부 라이브러리에 대한 의존성을 최대한 줄이고 싶습니다.
  • 데이터는 거의 업데이트되지 않으며 새로 고침() 호출이 상당히 빠릅니다.따라서 나는 캐시를 열심히 초기화합니다(밀짚모자 예제와 달리). 이는 접근자가 쓰기 잠금을 해제할 필요가 없음을 의미합니다.

참조 데이터가 변경 불가능한 경우 최대 절전 모드의 두 번째 수준 캐시가 합리적인 솔루션이 될 수 있습니다.

분명히 이는 데이터의 getter와 setter를 모두 동기화하여 처리할 수 있지만 대규모 웹 애플리케이션의 경우 이는 상당한 오버헤드입니다.

나는 밀짚모자로서 나에게 필요한 사소한 결함이 있는 예를 포함시켰습니다.이를 구현하는 다른 방법을 제안하십시오.

이것이 어느 정도 사실일 수도 있지만, 지연 로드 시 동시성 문제를 피하기 위해 제공한 샘플 코드를 동기화해야 한다는 점에 유의해야 합니다. locations.해당 접근자가 동기화되지 않은 경우 다음이 발생합니다.

  • 여러 스레드가 액세스 loadAllLocations() 방법을 동시에
  • 일부 스레드가 들어갈 수 있습니다. loadAllLocations() 다른 스레드가 메서드를 완료하고 결과를 할당한 후에도 locations - Java 메모리 모델에서는 다른 스레드가 동기화 없이 변수의 변경 사항을 볼 것이라는 보장이 없습니다.

지연 로딩/초기화를 사용할 때는 주의하세요. 이는 단순한 성능 향상처럼 보이지만 많은 불쾌한 스레딩 문제를 일으킬 수 있습니다.

제 생각에는 스스로 하지 않는 것이 최선이라고 생각합니다. 왜냐하면 올바르게 하는 것은 매우 어려운 일이기 때문입니다.Hibernate 및 Spring과 함께 EhCache 또는 OSCache를 사용하는 것이 훨씬 더 나은 아이디어입니다.

게다가 DAO를 상태 저장 상태로 만들어 문제가 될 수 있습니다.Spring이 관리하는 연결, 팩토리 또는 템플릿 객체 외에는 상태가 전혀 없어야 합니다.

업데이트:참조 데이터가 너무 크지 않고 실제로 변경되지 않는 경우 대체 디자인은 열거형을 만들고 데이터베이스를 완전히 없애는 것일 수 있습니다.캐시도 없고, 최대 절전 모드도 없으며, 걱정하지 마세요.아마도 oxbow_lakes의 요점은 고려해 볼 가치가 있습니다.아마도 그것은 매우 간단한 시스템일 수도 있다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top