Redis 캐시 및 데이터베이스 이중 쓰기 일관성

데이터베이스 및 캐시(예: redis)의 이중 쓰기 데이터 일관성 문제는 개발 언어와 관련이 없는 공개 문제입니다. 특히 높은 동시성 시나리오에서 이 문제는 더욱 심각해집니다. 오늘의 기사에서는 얕은 것에서 깊은 것으로 이동하여 데이터베이스 및 캐시 이중 쓰기 데이터 일관성 문제에 대한 일반적인 솔루션, 이러한 솔루션에서 가능한 함정 및 최적의 솔루션에 대해 이야기할 것입니다.

이론적으로 캐시의 만료 시간을 설정하는 것은 궁극적인 일관성을 보장하는 솔루션입니다. 이 체계에서는 캐시에 저장된 데이터의 만료 시간을 설정할 수 있고 모든 쓰기 작업은 데이터베이스에 종속되며 캐시 작업에만 최선을 다합니다. 즉, 데이터베이스가 성공적으로 작성되고 캐시 업데이트가 실패하면 만료 시간에 도달하는 한 후속 읽기 요청은 자연스럽게 데이터베이스에서 새 값을 읽은 다음 캐시를 다시 채웁니다. 따라서 다음에 논의되는 아이디어는 캐시 만료 시간을 설정하는 방식에 의존하지 않습니다.

여기서는 네 가지 업데이트 전략에 대해 설명합니다.

  1. 먼저 캐시를 업데이트한 다음 데이터베이스를 업데이트하십시오.

  1. 먼저 데이터베이스를 업데이트한 다음 캐시를 업데이트하십시오.

  1. 먼저 캐시를 삭제한 다음 데이터베이스를 업데이트하십시오.

  1. 먼저 데이터베이스를 업데이트한 다음 캐시를 삭제하십시오.

1. 먼저 캐시를 업데이트한 다음 데이터베이스를 업데이트합니다.

이 업데이트 전략은 프로젝트에서 사용해서는 안 되며 기존 문제는 명백합니다.

위의 그림과 같이 특정 사용자의 각 쓰기 작업에 대해 캐시가 방금 쓰여졌다면 갑자기 네트워크에 이상이 발생하여 데이터베이스에 대한 쓰기가 실패합니다.

결과적으로 캐시는 최신 데이터로 업데이트되지만 데이터베이스는 그렇지 않은데 캐시의 데이터가 더티 데이터가 되지는 않나요 ? 이때 사용자의 쿼리 요청이 데이터를 읽기만 하면 데이터베이스에 데이터가 전혀 존재하지 않기 때문에 문제가 발생하며 이 문제는 매우 심각합니다.

우리 모두는 캐싱의 주요 목적이 데이터베이스의 데이터를 메모리에 임시로 저장하여 후속 쿼리에 편리하고 쿼리 속도를 향상시키는 것임을 알고 있습니다.

그러나 데이터 조각이 데이터베이스에 존재하지 않는 이러한 종류의 가짜 데이터를 캐싱하는 이유는 무엇입니까?

따라서 캐시를 먼저 업데이트한 후 데이터베이스를 업데이트하는 것은 바람직하지 않으며 실제 작업에서 많이 사용되지 않습니다.

2. 먼저 데이터베이스를 업데이트한 다음 캐시를 업데이트합니다.

이 계획은 일반적으로 모든 사람이 반대합니다. 두 가지 주요 이유가 있습니다.

원인 1:

스레드 안전의 관점에서 업데이트 작업에 대한 요청 A와 요청 B가 동시에 있는 경우 다음이 있습니다.

(1) 스레드 A가 데이터베이스를 업데이트합니다.
(2) 스레드 B가 데이터베이스를 업데이트합니다.
(3) 스레드 B가 캐시를 업데이트합니다.
(4) 스레드 A가 캐시를 업데이트합니다.

이것은 A에게 캐시 업데이트를 요청하는 것이 B에게 캐시 업데이트를 요청하는 것보다 먼저 요청해야 하지만 네트워크 및 기타 이유로 B가 A보다 먼저 캐시를 업데이트했음을 의미합니다. 이로 인해 더티 데이터가 발생하므로 고려되지 않습니다!

두 번째 이유:

비즈니스 시나리오의 관점에서 볼 때 두 가지 문제가 있습니다.

(1) 데이터베이스가 더 많이 쓰고 더 적게 읽는 비즈니스 시나리오인 경우 이 솔루션을 채택하면 데이터를 읽기도 전에 캐시가 자주 업데이트되어 성능이 낭비됩니다.

(2) 데이터베이스에 쓴 값이 캐시에 직접 쓰여지는 것이 아니라 일련의 복잡한 계산을 거쳐 캐시에 쓰여지는 경우. 그런 다음 데이터베이스에 쓸 때마다 캐시에 쓴 값을 다시 계산하는데, 이는 의심할 여지 없이 성능 낭비입니다.

분명히 캐시를 삭제하는 것이 더 적합합니다. 다음 논의는 가장 논란이 많은 것으로 캐시를 먼저 삭제한 다음 데이터베이스를 업데이트하거나 데이터베이스를 먼저 업데이트한 다음 캐시를 삭제하는 것입니다.

3. 먼저 캐시를 삭제한 다음 데이터베이스를 업데이트합니다.

이 체계가 불일치를 일으키는 이유는 동시에 업데이트 작업에 대한 요청 A와 쿼리 작업에 대한 또 다른 요청 B가 있기 때문입니다. 그러면 다음과 같은 상황이 나타납니다.

(1) A에게 쓰기 작업을 수행하도록 요청하기 전에 캐시를 삭제합니다.
(2) B에게 쿼리하여 캐시가 존재하지 않는지 확인하도록 요청합니다.
(3) B에게 이전 값을 가져오기 위해 데이터베이스를 쿼리하도록 요청합니다.
(4) B에게 쓰기를 요청합니다. 캐시에 이전 값
(5) 데이터베이스에 새 값 쓰기 요청

캐시 만료 시간 설정 정책이 여기에서 채택되지 않으면 데이터는 항상 더티 데이터가 됩니다! ! !

솔루션: 지연된 이중 삭제 전략:

(1) 먼저 캐시를 삭제합니다.
(2) 데이터베이스에 다시 쓰기를 합니다.
(3) 일정 시간(예: 1초) 동안 절전 모드로 전환한 후 다시 캐시를 삭제합니다.

이렇게 하는 목적은 sleep 시간 동안 생성된 더티 캐시 데이터를 삭제하기 위함입니다(이 sleep 시간은 프로젝트의 시간 소모적인 비즈니스 로직에 따라 지정해야 함).

MySQL의 읽기-쓰기 분리 아키텍처라면 어떨까요?

이 경우 데이터 불일치의 원인은 다음과 같으며, 여전히 두 개의 요청이 있는데, 하나는 A에게 업데이트 작업을 수행하도록 요청하고 다른 하나는 B에게 쿼리 작업을 수행하도록 요청합니다.

(1) A에게 쓰기 작업을 수행하고 캐시를 삭제하도록 요청,
(2) A에게 데이터베이스에 데이터를 쓰도록 요청,
(3) B에게 캐시를 쿼리하여 캐시에 값이 없음을 확인하도록 요청,
(4) B에게 요청 데이터베이스에서 쿼리하려면 이때 마스터-슬레이브 동기화가 완료되지 않았으므로 이전 값을 쿼리합니다.
(5) B에게 이전 값을 캐시에 쓰도록 요청합니다.
(6) 데이터베이스가 마스터- 슬레이브 동기화 및 슬레이브 라이브러리가 새로운 값이 됩니다.

위와 같은 상황이 데이터 불일치의 원인입니다. 여전히 이중 삭제 지연 전략을 사용합니다. 그러나 슬립 시간은 마스터-슬레이브 동기화 지연 시간에 수백 ms를 추가하도록 수정됩니다.

지연된 이중 삭제 전략으로 인해 처리량이 감소하면 어떻게 해야 합니까?

두 번째 삭제 작업을 비동기적으로 수행하기 위해 다른 스레드를 시작할 수 있으므로 쓰기 요청이 반환되기 전에 일정 시간 동안 휴면할 필요가 없으므로 처리량이 증가합니다.

다음으로 또 다른 질문이 있습니다. 캐시를 두 번째로 삭제할 때 삭제에 실패하면 어떻게 해야 합니까?

4. 먼저 데이터베이스를 업데이트한 다음 캐시를 삭제합니다.

이 전략에 동시성 문제가 없는 것은 아닙니다. 다음과 같은 상황이 발생하면 여전히 더티 데이터가 생성됩니다. 쿼리 작업에 대한 요청 A와 업데이트 작업에 대한 요청 B의 두 가지 요청이 있다고 가정하면 다음과 같은 상황이 발생합니다.

(1) 캐시가 방금 실패했습니다.
(2) A에게 데이터베이스를 쿼리하고 이전 값을 가져오도록 요청했습니다.
(3) B에게 새 값을 데이터베이스에 쓰도록 요청했습니다.
(4) B에게 캐시를 삭제하도록 요청했습니다.
(5) A에게 쓰기를 요청했습니다. 캐시에서 찾은 이전 값

그러나 이러한 상황은 여전히 ​​상대적으로 드물며 다음 조건이 동시에 충족되어야 합니다.

1. 캐시가 자동으로 만료됩니다.

2. A에게 데이터베이스에서 이전 값을 찾아 캐시를 업데이트하도록 요청하는 것은 B에게 데이터베이스에 쓰고 캐시를 삭제하도록 요청하는 것보다 시간이 오래 걸립니다.

우리 모두는 데이터베이스를 쓴 후 캐시를 삭제하는 것은 말할 것도 없고 데이터베이스를 쿼리하는 속도가 일반적으로 데이터베이스를 쓰는 것보다 빠르다는 것을 알고 있습니다. 따라서 대부분의 경우 데이터 요청을 쓰는 것이 데이터를 읽는 것보다 오래 걸립니다.

시스템이 위의 두 가지 조건을 동시에 만족할 확률은 매우 작음을 알 수 있다.

데이터베이스에 먼저 쓴 다음 캐시를 삭제하는 솔루션을 사용하는 것이 좋습니다 데이터 불일치 문제를 100% 피할 수는 없지만 이 문제의 가능성은 다른 솔루션에 비해 가장 적습니다.

하지만 이 시나리오에서 캐시 삭제에 실패하면 어떻게 될까요?

답변: 재시도 메커니즘을 추가해야 합니다.

인터페이스에서 데이터베이스 업데이트는 성공했지만 캐시 업데이트가 실패하면 즉시 3번 재시도할 수 있습니다. 그 중 하나라도 성공하면 성공이 직접 반환됩니다. 세 번 모두 실패하면 후속 처리를 위해 데이터베이스에 기록됩니다.

물론 인터페이스에서 동기적으로 직접 재시도한다면 인터페이스의 동시성이 상대적으로 높을 때 인터페이스의 성능에 약간의 영향을 미칠 수 있다.

이때 비동기식 재시도로 변경해야 합니다.

다음과 같이 비동기식으로 재시도하는 방법에는 여러 가지가 있습니다.

1. 매번 별도의 스레드가 시작되며 이 스레드는 재시도 전용입니다. 그러나 동시성이 높은 시나리오라면 너무 많은 스레드가 생성되어 시스템 OOM 문제가 발생할 수 있으므로 권장하지 않습니다.

2. 재시도한 작업을 쓰레드 풀에 넘기는데, 서버가 재시작되면 일부 데이터가 유실될 수 있습니다.

3. 재시도 데이터를 테이블에 쓴 다음 탄력적 작업 및 기타 예약된 작업을 사용하여 재시도합니다.

4. 재시도된 요청을 mq와 같은 메시지 미들웨어에 작성하고 mq 소비자에서 처리합니다.

그러나 위의 방법들은 모두 비즈니스 코드 라인에 많은 침범을 초래한다는 단점이 있습니다. 방법 5가 있습니다.

mysql의 binlog를 구독한다 구독자 중 데이터 업데이트 요청이 발견되면 해당 캐시를 삭제한다.

알리는 이미 기성품 미들웨어 채널이 있으니 관심 있으신 분들은 직접 배워보세요~

같은 방식으로 canel을 사용하더라도 삭제 실패 문제는 여전히 존재하며 앞서 설명한 재시도 메커니즘을 추가해야 합니다.

운하 클라이언트(즉, 가입자)가 다시 캐시 삭제에 실패하면 mq에 쓰고 mq가 자동으로 재시도하도록 하는 것이 좋습니다.

아래 그림과 같이:

Supongo que te gusta

Origin blog.csdn.net/cj_eryue/article/details/129737398
Recomendado
Clasificación