시나리오
Mutex는
홈페이지의 상위 10위와 같이 동시 액세스 수가 많고 캐시 만료가 많은 상황에서 주로 사용되며 n에 대한 데이터베이스에서 Memcache 캐시로 로드됩니다. 분
Weibo 캐시에 연예인의 콘텐츠가 존재하지 않으면 대량의 요청이 데이터베이스에 도달하여 로드할 수 없습니다.
여러 IO 작업으로 생성된 데이터를 캐시에 저장해야 하며, DB를 여러 번 쿼리하는 등
문제
대규모 동시 상황에서 캐시가 실패하는 경우 많은 동시 사용자가 동시에 캐시에 접근할 수 없을 때 DB에 접근한 후 캐시를 재설정하게 됩니다. 동시에 시스템에 잠재적인 과부하 위험이 발생할 수 있습니다. 우리는 온라인 시스템에서도 비슷한 실패를 경험했습니다.
해결 방법
방법 1
db를 로드하기 전에 뮤텍스 키를 추가하세요. 뮤텍스 키 추가가 성공한 후 db를 로드하세요. 추가가 실패하면 잠자기 상태에서 원본 캐시 데이터를 다시 읽어보세요. 교착 상태를 방지하려면 뮤텍스 키에도 만료 시간을 설정해야 합니다. 의사 코드는 다음과 같습니다
(참고: 아래 의사 코드는 아이디어 이해를 위한 것일 뿐, 버그가 있을 수 있으니 자유롭게 지적해 주세요.)
Java 코드
if (memcache.get(key) == null) { // 3 min timeout to avoid mutex holder crash if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { value = db.get(key); memcache.set(key, value); memcache.delete(key_mutex); } else { sleep(50); retry(); } }
방법 2
값 내부에 타임아웃 값(timeout1)을 설정하는데, timeout1은 실제 Memcache 타임아웃(timeout2)보다 작습니다. 캐시에서 timeout1을 읽고 만료된 것으로 확인되면 timeout1이 즉시 연장되고 캐시로 재설정됩니다. 그런 다음 데이터베이스에서 데이터를 로드하고 캐시에 설정합니다. 의사 코드는 다음과 같습니다
Java 코드
v = memcache.get(key); if (v == null) { if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { value = db.get(key); memcache.set(key, value); memcache.delete(key_mutex); } else { sleep(50); retry(); } } else { if (v.timeout <= now()) { if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { // extend the timeout for other threads v.timeout += 3 * 60 * 1000; memcache.set(key, v, KEY_TIMEOUT * 2); // load the latest value from db v = db.get(key); v.timeout = KEY_TIMEOUT; memcache.set(key, value, KEY_TIMEOUT * 2); memcache.delete(key_mutex); } else { sleep(50); retry(); } } }
옵션 1과 비교
장점: 큰 값을 얻지 못함 캐시가 뮤텍스 및 절전 모드에 실패할 때 요청 수
단점: 코드 복잡성이 증가하므로 일반적인 상황에서는 해결 방법 1이면 충분합니다.
옵션 2도 Memcached FAQ에 어떻게 클로버링 업데이트, 스탬핑 요청을 방지하는지 자세히 소개되어 있으며, Brad는 자신이 가장 좋아하는 도구 중 하나인 Gearman을 사용하여 단일 인스턴스 캐시 설정을 구현하는 방법도 소개했습니다. , 캐시 미스 스탬피드(Cache miss stampedes)를 참조하세요. 그러나 Gearman을 사용하여 이 문제를 해결하는 것은 약간 까다롭게 느껴집니다.