RocksDB는 오픈 소스 스토리지 엔진으로서 트랜잭션의 ACID 특성을 지원합니다. ACID에서 I(격리)를 지원하려면 동시성 제어가 필수적입니다. 이 기사에서는 주로 RocksDB의 잠금 메커니즘 구현에 대해 설명합니다. 이 글을 통해 독자들이 RocksDB 동시성 제어의 원리를 심도 있게 이해할 수 있기를 바랍니다. 이 기사는 주로 다음 네 가지 측면에서 시작됩니다. 먼저 RocksDB 잠금의 기본 구조를 소개하고 RocksDB 행 잠금 데이터 구조의 잠금 공간 오버헤드를 소개합니다. 그런 다음 몇 가지 일반적인 잠금 프로세스를 소개합니다. 마지막으로 메커니즘의 필수 교착 상태 감지 메커니즘을 소개하겠습니다.
1. 행 잠금 데이터 구조
RocksDB의 최소 잠금 단위는 행입니다. KV 저장소의 경우 잠금 개체가 키이고 각 키는 LockInfo 구조에 해당합니다. 모든 키는 해시 테이블을 통해 관리되며, 잠금을 찾을 때 해시 테이블을 통해 직접 찾아 키가 잠겨 있는지 확인할 수 있습니다. 그러나 전역적으로 해시 테이블이 하나만 있는 경우 이 해시 테이블에 액세스할 때 많은 충돌이 발생하여 동시성 성능에 영향을 미칩니다. RocksDB는 먼저 Columnfamily로 분할됩니다. 각 Columnfamily의 잠금은 LockMap으로 관리되며 각 LockMap은 LockMapStripe로 분할되고 해시 테이블(std::unordered_map2eac983ba28aeb69c5ad6bcec0f35ca8acquire_snapshot(false)을 사용하여 스냅샷 획득을 지연합니다(잠금 후 스냅샷 찍기)
5). get_for_update를 다시 시도하세요. 현재 키가 잠겨 있으므로 다시 시도하면 성공할 수 있습니다.
6) 업데이트 작업을 수행합니다
7). 1로 점프하여 기본 키가 조건을 충족하지 않을 때까지 계속 실행한 후 종료됩니다.
3.3. 보조 인덱스 기반 업데이트
이 시나리오는 보조 인덱스에서 기본 키를 찾는 프로세스에 한 단계가 더 있다는 점을 제외하면 3.2와 유사합니다.
1) 스냅샷을 생성하고 반복자를 기반으로 보조 인덱스를 스캔합니다.
2) 보조 인덱스에 따라 기본 키를 반대로 찾습니다. 실제로 이 프로세스도 호출됩니다. 키
3 ) 보조 인덱스에 따라 계속해서 다음 기본 키를 탐색하고 잠금을 시도합니다.
4) 반환된 보조 인덱스가 조건을 충족하지 않으면 종료됩니다
3.4 InnoDB 잠금 사이
RocksDB와 InnoDB의 차이점에 대해 말하자면, 업데이트 시나리오의 경우 RocksDB는 여전히 스냅샷을 읽는 반면 InnoDB는 현재 버전을 읽으므로 동작에 차이가 있습니다. 예를 들어 RC 격리 수준의 범위 업데이트 시나리오에서 트랜잭션은 1,000개의 레코드를 업데이트해야 하므로 잠금이 검색되고 잠겨 있으므로 999번째 레코드를 검색하면 이 키의 시퀀스를 찾을 수 있습니다. 스캔된 스냅샷(다른 트랜잭션에 의해 업데이트된 이 키)보다 크면 이번에는 스냅샷 다시 획득이 트리거되고 이 스냅샷을 기반으로 최신 키 값을 가져옵니다. InnoDB에는 이러한 문제가 없습니다. 현재 읽기 및 스캔 프로세스를 통해 999번째 레코드가 업데이트되면 InnoDB는 최신 레코드를 직접 볼 수 있습니다. 이 경우 RocksDB와 InnoDB에서 본 결과는 동일합니다. 또 다른 경우에는 새로 삽입된 키도 스캔 범위에 있다고 가정하면 이 키의 시퀀스는 의심할 여지없이 스캔된 스냅샷보다 크므로 이 키는 스캔 프로세스 중에 필터링되어 존재하지 않습니다. 충돌 감지라고 불리는 이 키는 발견되지 않습니다. 업데이트 과정에서 ID가 1과 900인 두 개의 레코드가 삽입되었습니다. 마지막으로 900번째 레코드가 보이지 않아 업데이트할 수 없었습니다. InnoDB의 경우 현재 읽고 있기 때문에 새로 삽입된 ID 900의 레코드를 보고 업데이트할 수 있다는 점이 InnoDB와의 차이점이다.
업데이트가 스냅샷을 기반으로 한다는 점 외에도 RocksDB는 잠금이 더 간결합니다. 특히 업데이트 프로세스 중에 열 업데이트에 고유 제약 조건이 포함되면 기본 키만 잠깁니다. , 잠금이 필요합니다. 일반 보조 인덱스는 잠글 필요가 없습니다. 이 목적은 고유 제약 조건 충돌을 방지하는 것입니다. 여기서 고유 제약 조건(기본 키 또는 고유 인덱스)이 업데이트되면 잠금이 필요합니다. InnoDB는 각 인덱스를 잠가야 합니다. 예를 들어 업데이트가 보조 인덱스를 기반으로 배치되면 보조 인덱스도 잠가야 합니다. 이러한 차이가 발생하는 이유는 InnoDB가 RR 격리 수준을 구현하기 때문입니다. 여기서 격리 수준에 대해 조금 이야기해 보겠습니다. 실제로 MySQL에서 정의하는 RR 격리 수준은 SQL 표준에서 정의하는 격리 수준과 다소 다릅니다. SQL 표준은 반복 불가능한 읽기 문제를 해결하기 위해 RR 격리 수준을 정의하고 팬텀 읽기 문제를 해결하기 위해 직렬화 가능 격리 수준을 정의합니다. 반복 불가능 읽기는 동일한 레코드의 값이 수정되지 않는다는 점을 강조하는 반면, 팬텀 읽기는 두 번의 읽기에 의해 반환되는 레코드 수가 고정되어 있으며 레코드 수를 늘리거나 줄이지 않는다는 점을 강조합니다. MySQL은 반복 불가능한 읽기 및 팬텀 읽기 문제를 해결하기 위해 RR 격리 수준을 정의하는 반면, InnoDB의 RR 격리 수준 구현은 GAP 잠금에 의존합니다. RocksDB는 스냅샷 기반 메커니즘이 새로 삽입된 레코드를 효과적으로 필터링할 수 있기 때문에 GAP 잠금을 지원하지 않습니다(고유한 제약 조건 검사만 지원하고 존재하지 않는 키는 잠급니다). 반면 InnoDB는 현재 읽기로 인해 간격 잠금을 통한 다른 삽입을 금지해야 합니다. 주로 잠금 간격 때문에 보조 인덱스도 잠궈야 합니다. 그렇지 않으면 현재 두 읽기의 결과가 다를 수 있습니다. 물론 RC 분할 수준의 경우 InnoDB 일반 보조 인덱스를 잠글 필요가 없습니다.
4.死锁检测算法
死锁检测采用DFS((Depth First Search,深度优先算法),基本思路根据加入等待关系,继续查找被等待者的等待关系,如果发现成环,则认为发生了死锁,当然在大并发系统下,锁等待关系非常复杂,为了将死锁检测带来的资源消耗控制在一定范围,可以通过设置deadlock_detect_depth来控制死锁检测搜索的深度,或者在特定业务场景下,认为一定不会发生死锁,则关闭死锁检测,这样在一定程度上有利于系统并发的提升。需要说明的是,如果关闭死锁,最好配套将锁等待超时时间设置较小,避免系统真发生死锁时,事务长时间hang住。死锁检测基本流程如下:
1.定位到具体某个分片,获取mutex
2.调用AcquireLocked尝试加锁
3.若上锁失败,则触发进行死锁检测
4.调用IncrementWaiters增加一个等待者
5.如果等待者不在被等待者map里面,则肯定不会存在死锁,返回
6.对于被等待者,沿着wait_txn_map_向下检查等待关系,看看是否成环
7.若发现成环,则将调用DecrementWaitersImpl将新加入的等待关系解除,并报死锁错误。
相关的数据结构:
class TransactionLockMgr { // Must be held when modifying wait_txn_map_ and rev_wait_txn_map_. std::mutex wait_txn_map_mutex_; // Maps from waitee -> number of waiters. HashMap<TransactionID, int> rev_wait_txn_map_; // Maps from waiter -> waitee. HashMap<TransactionID, autovector<TransactionID>> wait_txn_map_; DecrementWaiters // IncrementWaiters // } struct TransactionOptions { bool deadlock_detect = false; //是否检测死锁 int64_t deadlock_detect_depth = 50; //死锁检测的深度 int64_t lock_timeout = -1; //等待锁时间,线上一般设置为5s int64_t expiration = -1; //持有锁时间, }
위 내용은 RocksDB 잠금 메커니즘의 예에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!