기본 Java 튜토리얼이 열은 캐시와 데이터베이스 간의 이중 쓰기 일관성을 보장합니다.
머리를 들어주세요, 공주님. 그렇지 않으면 왕관이 떨어질 것입니다.
분산 캐시는 오늘날 많은 분산 애플리케이션에 없어서는 안 될 구성 요소이지만, 분산 캐시를 사용하면 캐시와 데이터베이스의 이중 저장 및 이중 쓰기가 발생할 수 있으므로 데이터 일관성 문제가 발생합니다. 일관성 문제를 해결하고 있나요?
캐시 따로 패턴
가장 고전적인 캐싱 + 데이터베이스 읽기 및 쓰기 모드는 캐시 따로 패턴입니다.
읽을 때는 캐시를 먼저 읽고 캐시가 없으면 데이터베이스를 읽은 후 데이터를 꺼내어 캐시에 넣고 동시에 응답을 반환합니다.
업데이트 시 데이터베이스를 먼저 업데이트한 후 캐시를 삭제하세요.
캐시를 업데이트하지 않고 왜 캐시를 삭제하나요?
이유는 매우 간단합니다. 많은 경우 복잡한 캐싱 시나리오에서 캐시는 데이터베이스에서 직접 가져온 값이 아닙니다.
예를 들어 특정 테이블의 필드가 업데이트되면 해당 캐시가 다른 두 테이블의 데이터를 쿼리하고 캐시의 최신 값을 계산하는 작업을 수행해야 합니다.
또한 캐시 업데이트 비용이 매우 높을 때도 있습니다. 데이터베이스가 수정될 때마다 해당 캐시도 업데이트되어야 한다는 뜻인가요? 일부 시나리오에서는 그럴 수 있지만 더 복잡한 캐시된 데이터 계산 시나리오에서는 그렇지 않습니다. 캐시와 관련된 여러 테이블을 자주 수정하는 경우 캐시도 자주 업데이트됩니다. 하지만 문제는 이 캐시에 자주 액세스할 것인가 하는 것입니다.
예를 들어, 캐시에 포함된 테이블의 필드가 1분에 20번 또는 100번 수정되면 캐시는 20번 또는 100번 업데이트되지만 이 캐시는 1분에 1번만 읽혀집니다. 콜드 데이터가 많아요. 실제로 캐시만 삭제하면 1분에 한 번만 캐시가 다시 계산되므로 오버헤드가 크게 줄어들게 됩니다.
사실 캐시를 업데이트하는 대신 캐시를 삭제하는 것은 게으른 계산 아이디어입니다. 사용 여부와 상관없이 매번 복잡한 계산을 다시 수행하지 말고, 필요할 때 다시 계산하도록 하세요. mybatis 및 hibernate와 마찬가지로 모두 지연 로딩에 대한 아이디어를 가지고 있습니다. 부서를 조회하면 부서에서 직원 목록을 가져온다. 부서를 조회할 때마다 그 안에 있는 직원 1,000명의 데이터도 동시에 검색된다는 것은 말할 필요도 없다. 80%의 경우 이 부서를 확인하려면 이 부서의 정보에만 액세스하면 됩니다. 먼저 부서를 확인하고 동시에 내부 직원에 접근해야 합니다. 그런 다음 내부 직원에 접근하려는 경우에만 직원 1,000명에 대한 데이터베이스를 쿼리합니다.
가장 기본적인 캐시 불일치 문제 및 해결 방법
문제: 데이터베이스를 먼저 수정한 후 캐시를 삭제하세요. 캐시 삭제에 실패하면 데이터베이스에는 새 데이터가, 캐시에는 이전 데이터가 생성되어 데이터 불일치가 발생합니다.
해결책: 먼저 캐시를 삭제한 다음 데이터베이스를 수정하세요. 데이터베이스 수정이 실패하면 데이터베이스에 오래된 데이터가 있고 캐시가 비어 있으므로 데이터가 불일치하지 않습니다. 읽을 때 캐시가 없기 때문에 데이터베이스의 오래된 데이터를 읽은 다음 캐시에 업데이트합니다.
더 복잡한 데이터 불일치 문제 분석
데이터가 변경되었습니다. 먼저 캐시를 삭제한 후 데이터베이스를 수정하세요. 요청이 들어오면 캐시를 읽어보니 캐시가 비어 있는 걸 발견하고, 데이터베이스에 질의를 해서 수정 전의 오래된 데이터를 찾아 캐시에 넣어두는 거죠. 그런 다음 데이터 변경 프로그램이 데이터베이스 수정을 완료합니다.
끝났습니다. 데이터베이스와 캐시의 데이터가 다릅니다. . .
수억 개의 트래픽이 발생하는 높은 동시성 시나리오에서 캐시에 이러한 문제가 발생하는 이유는 무엇입니까?
이 문제는 데이터 조각을 동시에 읽고 쓰는 경우에만 발생할 수 있습니다. 실제로 동시성이 매우 낮은 경우, 특히 읽기 동시성이 매우 낮은 경우(일일 방문 횟수가 10,000회에 불과한 경우) 드문 경우지만 위에서 설명한 일관되지 않은 시나리오가 발생합니다. 그런데 문제는 매일 수억 건의 트래픽이 발생하고 초당 수만 건의 동시 읽기가 발생하는 경우 매초마다 데이터 업데이트 요청이 있는 한 위에서 언급한 데이터베이스 + 캐시 불일치가 발생할 수 있다는 것입니다.
해결 방법은 다음과 같습니다.
데이터를 업데이트할 때 데이터의 고유 식별자를 기반으로 작업을 라우팅하고 이를 jvm 내부 대기열로 보냅니다. 데이터를 읽을 때 데이터가 캐시에 없는 것으로 확인되면 데이터를 다시 읽고 캐시를 업데이트합니다. 고유 식별자에 따라 라우팅한 후 동일한 jvm 내부 큐로 전송됩니다. .
큐는 작업자 스레드에 해당합니다. 각 작업자 스레드는 해당 작업을 순차적으로 가져온 다음 하나씩 실행합니다. 이 경우 데이터 변경 작업의 경우 캐시를 먼저 삭제한 후 데이터베이스를 업데이트하지만 아직 업데이트가 완료되지 않은 상태입니다. 이때 읽기 요청이 와서 빈 캐시를 읽으면 캐시 업데이트 요청을 먼저 큐에 보낼 수 있으며 이때 큐에 백로그된 후 캐시 업데이트를 기다릴 수 있습니다. 동기식으로 완료됩니다.
여기에 최적화 지점이 있습니다. 실제로 여러 업데이트 캐시 요청을 함께 묶는 것은 의미가 없습니다. 따라서 대기열에 캐시를 업데이트하라는 요청이 이미 있는 것으로 확인되면 필터링이 수행될 수 있습니다. , 그러면 다른 것을 넣을 필요가 없습니다. 업데이트 요청 작업이 들어왔으며 이전 업데이트 작업 요청이 완료될 때까지 기다리면 됩니다.
해당 큐에 해당하는 작업자 스레드는 이전 작업에 대한 데이터베이스 수정을 완료한 후 다음 작업인 캐시 업데이트 작업을 수행합니다. 이때 데이터베이스에서 최신 값을 읽은 다음 캐시에 기록되었습니다.
요청이 여전히 대기 시간 범위 내에 있고 지속적인 폴링을 통해 값을 얻을 수 있는 것으로 확인되면 요청이 특정 시간 이상 대기하면 직접 반환됩니다. 그러면 이번에는 현재 이전 값이 반환됩니다. 데이터베이스에서 직접 읽을 수 있습니다.
높은 동시성 시나리오에서 이 솔루션에 주의해야 할 문제는 다음과 같습니다.
읽기 요청이 약간 비동기적이므로 읽기 시간 초과에 주의해야 합니다. 문제가 발생했습니다. 각 읽기 요청은 제한 시간 내에 반환되어야 합니다.
이 솔루션의 가장 큰 위험 점은 데이터가 자주 업데이트되어 대기열에 대규모 업데이트 작업 백로그가 발생하고 읽기 요청에 대해 많은 시간 초과가 발생하여 결국 큰 폭의 오류가 발생한다는 것입니다. 데이터베이스로 직접 이동하는 요청 수입니다. 시뮬레이션된 실제 테스트를 실행하여 데이터가 얼마나 자주 업데이트되는지 확인하십시오.
또 다른 점은 대기열에 있는 여러 데이터 항목에 대한 업데이트 작업의 백로그가 있을 수 있으므로 자체 비즈니스 조건에 따라 테스트해야 할 수 있으며 여러 서비스를 배포해야 할 수 있으며 각 서비스는 일부 데이터 업데이트를 공유합니다. 운영. 메모리 큐가 실제로 100개 제품의 재고 수정 작업을 압축하고 각 재고 수정 작업을 완료하는 데 10ms가 걸리는 경우 마지막 제품에 대한 읽기 요청은 데이터를 얻을 수 있기 전에 10 * 100 = 1000ms = 1s 동안 기다릴 수 있습니다. 이로 인해 읽기 요청이 장기간 차단됩니다.
비즈니스 시스템의 실제 운영을 기반으로 몇 가지 스트레스 테스트를 수행하고 온라인 환경을 시뮬레이션하여 가장 바쁜 시간 동안 메모리 큐가 얼마나 많은 업데이트 작업을 압박할 수 있는지 확인하고, 이로 인해 마지막 업데이트 작업이 얼마나 오래 걸릴 수 있는지 확인하세요. 해당 읽기 요청이 200ms 내에 반환되면 가장 바쁜 시간에도 10개의 업데이트 작업 백로그가 있고 최대 대기 시간이 200ms라고 계산하면 괜찮습니다.
메모리 대기열에 백로그될 수 있는 업데이트 작업이 많은 경우 각 컴퓨터에 배포된 서비스 인스턴스가 더 적은 데이터를 처리하도록 더 많은 컴퓨터를 추가해야 합니다. 그러면 각 메모리 대기열의 업데이트 작업 백로그가 발생합니다. 소수가 됩니다.
사실 이전 프로젝트 경험에 따르면 일반적으로 데이터 쓰기 빈도가 매우 낮으므로 일반적으로 대기열의 업데이트 작업 백로그가 매우 작아야 합니다. 높은 읽기 동시성과 읽기 캐싱 아키텍처를 목표로 하는 이와 같은 프로젝트의 경우 일반적으로 초당 QPS가 수백에 도달할 수 있으면 쓰기 요청이 거의 없습니다.
대략적인 계산
초당 500개의 쓰기 작업이 있고 5개의 시간 조각으로 나누어지면 200ms마다 100개의 쓰기 작업이 있으며 각 메모리 큐는 20개에 배치됩니다. 5개의 쓰기 작업이 백로그되었습니다. 각 쓰기 작업 성능 테스트 후 일반적으로 약 20ms 내에 완료되므로 각 메모리 큐의 데이터에 대한 읽기 요청은 최대 잠시 동안만 중단되며 반드시 200ms 이내에 반환됩니다.
지금까지 간단한 계산을 통해 단일 머신에서 지원하는 쓰기 QPS는 수백 개에서도 문제가 없다는 것을 알 수 있습니다. 쓰기 QPS가 10배 증가하면 머신은 10배로 확장되고 각 머신은 대기열은 20개입니다.
위 상황이 발생하면 또 다른 위험이 있는지 확인하기 위해 여기에서 스트레스 테스트를 수행해야 합니다. 즉, 많은 수의 읽기 요청이 갑자기 중단될 수 있습니다. 수십 밀리초의 지연은 서비스가 이를 처리할 수 있는지 여부와 최대 극한 상황을 처리하는 데 필요한 시스템 수에 따라 다릅니다.
그러나 모든 데이터가 동시에 업데이트되는 것은 아니며 캐시가 동시에 만료되지 않기 때문에 소수의 데이터 캐시는 매번 유효하지 않을 수 있으며, 그런 다음 해당 데이터에 해당하는 읽기 요청은 동시성이 매우 클 것입니다.
이 서비스는 여러 인스턴스를 배포할 수 있으므로 데이터 업데이트 작업 및 캐시 업데이트 작업을 수행하는 요청이 Nginx 서버 상위를 통해 동일한 서비스 인스턴스로 라우팅되도록 해야 합니다.
예를 들어, 동일한 제품에 대한 모든 읽기 및 쓰기 요청은 동일한 시스템으로 라우팅됩니다. 특정 요청 매개변수에 따라 서비스 간 자체 해시 라우팅을 수행하거나 Nginx의 해시 라우팅 기능 등을 사용할 수 있습니다.
특정 제품의 읽기 및 쓰기 요청이 유난히 많고 모두 동일한 머신의 동일한 대기열로 전송되는 경우 제품에 과도한 부담이 발생할 수 있습니다. 특정 기계. 즉, 제품 데이터가 업데이트될 때만 캐시가 지워지고 읽기 및 쓰기 동시성이 발생하기 때문에 실제로 업데이트 빈도가 너무 높지 않으면 이 영향은 비즈니스 시스템에 따라 다릅니다. 문제는 특별히 크지는 않지만 일부 시스템의 부하가 더 높을 수 있는 것은 사실입니다.
위 내용은 Java 구현은 캐시와 데이터베이스 간의 이중 쓰기 일관성을 보장합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!