Maison  >  Article  >  base de données  >  Explication détaillée d'exemples de mécanisme de verrouillage RocksDB

Explication détaillée d'exemples de mécanisme de verrouillage RocksDB

零下一度
零下一度original
2017-07-03 09:31:442394parcourir

En tant que moteur de stockage open source, RocksDB prend en charge les caractéristiques ACID des transactions. Pour prendre en charge I (isolement) dans ACID, cet article traite principalement de l'implémentation du mécanisme de verrouillage de RocksDB, et les détails le seront. Pour l'analyse du code source, j'espère que grâce à cet article, les lecteurs pourront acquérir une compréhension plus approfondie des principes de contrôle de concurrence de RocksDB. L'article commence principalement par les quatre aspects suivants : Tout d'abord, je présenterai la structure de base du verrouillage RocksDB. Ensuite, je présenterai la surcharge de l'espace de verrouillage sous la conception de la structure de données de verrouillage de ligne RocksDB. Ensuite, je présenterai le processus de verrouillage de plusieurs types. scénarios.Enfin, je présenterai le verrou, un mécanisme essentiel de détection de blocage dans le mécanisme.

1. Structure de données de verrouillage de ligne
La granularité minimale du verrouillage de RocksDB est la ligne. Pour le stockage KV, l'objet de verrouillage est la clé et chaque clé. correspond à une structure LockInfo. Toutes les clés sont gérées via la table de hachage Lorsque vous recherchez un verrou, vous pouvez le localiser directement via la table de hachage pour déterminer si la clé a été verrouillée. Mais s'il n'y a qu'une seule table de hachage au niveau mondial, cela entraînera de nombreux conflits lors de l'accès à cette table de hachage, affectant les performances de concurrence. RocksDB est d'abord divisé par Columnfamily. Les verrous de chaque Columnfamily sont gérés par un LockMap, et chaque LockMap est divisé en plusieurs fragments. Chaque fragment est géré par LockMapStripe et la table de hachage (std::unordered_map8a542d0d361839b94a43059fe0150833acquire_snapshot(false), delay Obtenez un instantané (après le verrouillage, prenez un autre instantané)
5). Appelez à nouveau get_for_update puisque la clé a été verrouillée à ce moment-là. une fois, réessayer réussira certainement.
6). Effectuez l'opération de mise à jour
7). Passez à 1 et continuez l'exécution jusqu'à ce que la clé primaire ne remplisse pas les conditions, puis elle se termine.

3.3. Mise à jour basée sur l'index secondaire
Ce scénario est similaire à 3.2, sauf qu'il y a une étape supplémentaire dans le processus de localisation de la clé primaire à partir de l’index secondaire.
1). Créez un instantané et scannez l'index secondaire en fonction de l'itérateur
2). Trouvez la clé primaire à l'envers en fonction de l'index secondaire, qui en fait. appelle get_row_by_rowid. Ce processus tentera de verrouiller la clé
3). Continuez à parcourir la clé primaire suivante en fonction de l'index secondaire et essayez de verrouiller
4. ). Lorsque le secondaire est renvoyé Lorsque l'index ne remplit pas les conditions, il se termine

3.4 La différence entre le verrouillage et InnoDB
Nous avons mentionné plus tôt que le La différence entre RocksDB et InnoDB est que pour les mises à jour. Dans ce scénario, RocksDB est toujours une lecture instantanée, tandis qu'InnoDB est une lecture actuelle, ce qui entraîne des différences de comportement. Par exemple, dans le scénario de mise à jour de plage sous le niveau d'isolement RC, par exemple, une transaction doit mettre à jour 1 000 enregistrements. Puisqu'elle est verrouillée lors de l'analyse, lorsque le 999ème enregistrement est analysé, il peut s'avérer que la séquence de cette clé est. supérieur à l'instantané analysé (cette clé mise à jour par d'autres transactions), cette fois déclenchera la réacquisition de l'instantané, puis obtiendra la dernière valeur de clé basée sur cet instantané. InnoDB n'a pas ce problème. Grâce au processus de lecture et d'analyse actuel, si le 999ème enregistrement est mis à jour, InnoDB peut voir directement le dernier enregistrement. Dans ce cas, les résultats vus par RocksDB et InnoDB sont les mêmes. Dans un autre cas, en supposant qu'une nouvelle clé soit insérée dans la plage de numérisation, la séquence de cette clé sera sans aucun doute plus grande que l'instantané numérisé, donc cette clé sera filtrée pendant le processus de numérisation et n'existera pas. détection de conflit, cette clé ne sera pas trouvée. Lors du processus de mise à jour, deux enregistrements portant les ID 1 et 900 ont été insérés. Enfin, le 900ème enregistrement n'a pas pu être mis à jour car il était invisible. Pour InnoDB, puisqu'il est en cours de lecture, l'enregistrement nouvellement inséré avec l'identifiant 900 peut être vu et mis à jour, c'est donc la différence avec InnoDB.
En plus de la différence selon laquelle les mises à jour sont basées sur des instantanés, RocksDB est également plus concis dans le verrouillage. Tous les verrous n'impliquent que des index uniques. Plus précisément, pendant le processus de mise à jour, seule la clé primaire est verrouillée ; update Lorsqu'une colonne implique une contrainte d'unicité, elle doit être verrouillée. Cependant, les index secondaires ordinaires n'ont pas besoin d'être verrouillés. Cet objectif est d'éviter les conflits de contraintes d'unicité. Ici, si la contrainte unique (clé primaire ou index unique) est mise à jour, un verrou est requis. InnoDB doit verrouiller chaque index. Par exemple, si les mises à jour sont positionnées en fonction de l'index secondaire, l'index secondaire doit également être verrouillé. La raison de cette différence est qu'InnoDB implémente le niveau d'isolation RR. Parlons ici un peu du niveau d'isolement. En fait, le niveau d'isolement RR défini dans MySQL est quelque peu différent du niveau d'isolement défini par le standard SQL. La norme SQL définit le niveau d'isolement RR pour résoudre le problème des lectures non répétables, et le niveau d'isolement sérialisable pour résoudre le problème des lectures fantômes. La lecture non répétable souligne que la valeur du même enregistrement ne sera pas modifiée ; tandis que la lecture fantôme souligne que le nombre d'enregistrements renvoyés par deux lectures est fixe et n'augmentera ni ne diminuera le nombre d'enregistrements. MySQL définit le niveau d'isolement RR pour résoudre les problèmes de lectures non répétables et de lectures fantômes, tandis que l'implémentation du niveau d'isolement RR dans InnoDB repose sur les verrous GAP. RocksDB ne prend pas en charge les verrous GAP (prend uniquement en charge les vérifications de contraintes uniques et verrouille les clés inexistantes) car le mécanisme basé sur les instantanés peut filtrer efficacement les enregistrements nouvellement insérés, tandis qu'InnoDB doit interdire d'autres insertions via des verrous gap en raison des lectures en cours. l'index secondaire doit également être verrouillé, principalement pour l'espace de verrouillage, sinon les résultats des deux lectures actuelles peuvent être différents. Bien entendu, pour le niveau divisé RC, les index secondaires ordinaires InnoDB n'ont pas besoin d'être verrouillés.

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; //持有锁时间,
}




Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn