Статья длиной в 10 000 слов объясняет блокировки в MongoDB.
Статья длиной в 10 000 слов объясняет блокировки в MongoDB.

👉Введение

MongoDB Это ведущая в мире база данных документов, которая пользуется большой популярностью среди разработчиков. MongoDB Блокировки в базе данных гарантируют высокую степень одновременного чтения и записи в базу данных. Эта статья начинается с MongoDB Введение в медленный журнал MongoDB Блокируется, представлено MongoDB Классификация ресурсов, классификация блокировок, структура блокировок, реализация блокировок, использование блокировок и методы запросов представлены в простой и понятной форме. MongoDB середина Замокиз Связанные технологии。Это длинная статья с полезной информацией. Рекомендуется поставить лайк и сохранить ее, прежде чем внимательно читать~.

👉Содержание

1 Введение: от MongoDB Введение в медленный журнал

2 Блокировка MongoDB и классификация ресурсов

3 Матрица блокировки MongoDB

4 Краткое введение: реализация блокировки в MongoDB

01. Введение: Введение из медленного журнала MongoDB.

В нашей повседневной работе с базой данных мы часто сталкиваемся с медленными журналами. При использовании MongoDB медленные журналы часто используются как один из показателей производительности чтения и записи MongoDB. Когда автор впервые приступил к работе, я тоже только начал сталкиваться с понятием медленных журналов в базе данных и часто задавал вопросы:

Что такое медленный журнал?

Полное название Slow Log — «Журнал медленных запросов». Как и его дословный перевод на английский язык, он представляет собой запись журнала запросов, которые возвращаются медленнее. Он начинается со статистики более медленных запросов, выполняемых в MySQL, и в основном используется для записи. время выполнения в MySQL-операторах, превышающее указанное время; запросив медленный журнал, мы можем узнать, какие операторы имеют низкую эффективность выполнения, и выполнить целевую оптимизацию запросов.

Концепция медленного журнала в MongoDB такая же, как и в MySQL. Запросы со временем выполнения более 100 мс обычно называются медленными запросами. Ядро подсчитывает время выполнения запроса во время выполнения и записывает информацию, связанную с запросом. время выполнения превышает 100 мс и распечатайте его в журнале работы ядра, он записывается как медленный журнал. Просматривая медленные журналы MongoDB, мы можем получить много информации о соответствующих запросах и текущем статусе работы ядра и соответствующим образом разработать соответствующие стратегии оптимизации.

MongoDB также может различными способами собирать и записывать информацию, связанную с медленными запросами.

Например, в MongoDB вы можете использовать следующие инструкции, чтобы настроить профилировщик базы данных для фильтрации и сбора запросов, а также для анализа медленных операций.

Язык кода:javascript
копировать
# Просмотр конфигурации профилера базы данных
db.getProfilingStatus()

# Настройте Databaseprofiler для сбора медленных запросов.
db.setProfilingLevel(<level>, <options>)

Среди них уровень представляет собой уровень профилирования, который имеет следующие три уровня:

  • 0: не включать профилировщик (не включен по умолчанию);
  • 1: включить профилировщик для сбора медленных запросов (сбор по умолчанию составляет более 100 мс);
  • 2: Включите profiler Соберите все из операций.

options содержит следующие параметры:

  • SlowMs: медленное принятие решения по запросу в миллисекундах, только больше, чем slowMs из Запрос будет profiler Отмечать и регистрировать медленные запросы, по умолчанию 100ms;
  • sampleRate: сбор медленных операций по частоте дискретизации;
  • фильтр: правила выборки и фильтрации.

После настройки профилировщика медленные запросы, соответствующие условиям, будут записываться в таблицу system.profile. Эта таблица представляет собой ограниченную коллекцию. Вы можете фильтровать и запрашивать записи медленных запросов с помощью db.system.profile.find(). пример пример:

Язык кода:javascript
копировать
>db.system.profile.find().pretty()
{
   "op" : "query", # Тип операции: может быть для команды, подсчета, отдельного, geoNear, getMore, группы, вставки, mapReduce, запроса, удаления, обновления.
   "ns" : "test.report", # Операции с таблицей библиотеки целевого пространства имен
   "command" : { # Операция по конкретной команде
      "find" : "report",
      ......
   },
   "cursorid" : 33629063128, # queryиgetMore использует курсор id
   "keysExamined" : 101, # для осуществления операции сканирования по номеру ключа индекса
   "docsExamined" : 101, # для осуществления операции сканирования данных документа
   "fromMultiPlanner" : true,
   "numYield" : 2, # изучить Позвольте другим операциям завершиться за раз во время работы
   "nreturned" : 101, # Вернуть количество документов
   "queryHash" : "811451DD", # Запрос ихеш
   "planCacheKey" : "759981BA", # план запроса из ключа
   "locks" : { # заблокировать информацию
      "Global" : { # глобальная блокировка
         "acquireCount" : {
            "r" : NumberLong(3),
            "w" : NumberLong(3)
         }
      },
      "Database" : { # Блокировка базы данных
         "acquireCount" : { "r" : NumberLong(3) },
         "acquireWaitCount" : { "r" : NumberLong(1) },
         "timeAcquiringMicros" : { "r" : NumberLong(69130694) }
      },
      "Collection" : { # Коллекционный замок
         "acquireCount" : { "r" : NumberLong(3) }
      }
   },
   "storage" : { # Ситуация с хранением
      "data" : {
         "bytesRead" : NumberLong(14736), # Чтение размера данных с диска середина
         "timeReadingMicros" : NumberLong(17) # Чтение данных с диска требует времени
      }
   },
   "responseLength" : 1305014, # размер ответа
   "protocol" : "op_msg", # сообщение из протокола
   "millis" : 69132,# осуществлятьвремя,milliseconds   "planSummary" : "IXSCAN { a: 1, _id: -1 }", # узнать план, вот руководство, иди индексный запрос
   "execStats" : { # Операция изучитьиз подробной пошаговой информации
      "stage" : "FETCH", # Тип операции, например COLLSCAN, IXSCAN, FETCH.
      "nReturned" : 101, # Вернуть количество документов
      ......
   },
   "ts" : ISODate("2019-01-14T16:57:33.450Z"), # Временная метка
   "client" : "127.0.0.1", # Информация о клиенте
   "appName" : "MongoDB Shell", # appName
   "allUsers" : [
      {
         "user" : "someuser",
         "db" : "admin"
      }
   ],
   "user" : "someuser@admin" # изучитьиз Информация о пользователе
}

Приведенная выше информация содержит некоторую подробную информацию о медленных операциях и может быть удобно использована для запроса и анализа медленных операций, например:

  • команда: записать содержимое запроса,Это может помочь нам проанализировать причины медленных запросов;
  • locks:проситьи Замокиз Сопутствующая информация,осуществлять Требуется запросполучатьиз Замок,а также Замок Ожидание в очереди、получать продолжительность и другую информацию;
  • writeConflicts (только запросы на запись): конфликты записи, которые вызывают некоторые конфликты при одновременной записи документа;
  • миллис: окончательное время выполнения медленного запроса;
  • planSummary: если выполнено COLLSCAN Сканирование всей таблицы будет быстрее, чем выполнение IXSCAN Индексные вызовы стоят дорожеизресурсивремя;
  • execStats:осуществлятьпланизспецифическийосуществлять Состояние,Удобно знать полную картину запроса изосуществовать.

Из анализа вышеуказанных аспектов мы можем примерно узнать статус выполнения запроса, который можно использовать для анализа причин медленных запросов. Вообще говоря, медленные запросы вызваны следующими причинами:

  • Высокая загрузка ЦП: частая проверка подлинности/установление соединения будет потреблять много ресурсов ЦП.,Заставляет запросы на изучение работать медленно;
  • ждать Замок/Замокконфликт:Некоторый Требуется запросполучать Замок,инравитьсяфруктыиметьдругойпроситьбратьприезжать Замокне выпущен,воля Заставляет запросы на изучение работать медленно;
  • Полное сканирование таблицы: запрос не проходит через индекс, что приводит к полному сканированию таблицы, что приводит к медленному выполнению запроса;
  • Сортировка памяти: Аналогично описанной выше ситуации.,Индекс не взятиз Состояние Симоучижитьсортировать Заставляет запросы на изучение работать медленно;
  • Но открой анализатор Profiler да требует некоторых затрат (например, влияет на производительность ядра),и обычно выключен по умолчанию,Так существуют при решении онлайн-проблем,Зачастую мы можем получить только запись ядра бревносередина из медленной бревно информации.

Типичный пример медленного журнала MongoDB выглядит следующим образом:

Язык кода:javascript
копировать
{
  "t": { # Временная метка
    "$date": "2020-05-20T20:10:08.731+00:00"
  },
  "s": "I", # уровень журнала, точкадля F, E, W, I соответственно для Fatal, Error, Предупреждение, Информация
  "c": "COMMAND", # изучить тип запроса, разделенный на ДОСТУП, КОМАНДА, КОНТРОЛЬ, ВЫБОРЫ, FTDC, ГЕО, ИНДЕКС и т.д.
  "id": 51803,# медленныйбревноизбревноid  "ctx": "conn281", # Ссылки и информация
  "msg": "Slow query", # Медленно бревно информация, медленный запрос президента.
  "attr": { # Детали параметра
    "type": "command", # Тип запроса
    "ns": "stocks.trades", # Пространство имен, запрос из таблицы базы данных
    "appName": "MongoDB Shell", # clientизapp name
    "command": { # Запросить подробности
      "aggregate": "trades",# проситьдляaggregate,исуществоватьtradesна столеосуществлять      ......
    },
    "planSummary": "COLLSCAN",# Запрос на изучение COLLSCAN
    «курсорид»: 1912190691485054700,
    "keysExamined": 0,
    "docsExamined": 1000001, # Запросить количество из документов, запросов больше из-за даCOLLSCAN
    "hasSortStage": true,
    "usedDisk": true,
    "numYields": 1002, # Проверьте количество других запросов на перевод.
    "nreturned": 101, # Количество возвращаемых документов намного меньше, чем у docsExamined, а полное сканирование таблицы занимает много времени.
    "reslen": 17738,
    "locks": { # заблокировать информацию
      "ReplicationStateTransition": {
        "acquireCount": {
          "w": 1119
        }
      },
      "Global": {
        "acquireCount": {
          "r": 1119
        }
      },
      "Database": {
        "acquireCount": {
          "r": 1119
        }
      },
      "Collection": {
        "acquireCount": {
          "r": 1119
        }
      },
      "Mutex": {
        "acquireCount": {
          "r": 117
        }
      }
    },
    "storage": {
      ......
    },
    "remote": "192.168.14.15:37666",
    "protocol": "op_msg",
    "durationMillis": 22427
  }
}

Из приведенных выше медленных запросов некоторая информация является относительно интуитивной. Например, если planSummary имеет значение COLLSCAN, это означает, что запрос прошел полное сканирование таблицы, а полное сканирование таблицы обычно означает плохую производительность, например, если docsExamied намного выше; чем nreturn, это означает более низкую эффективность выполнения запроса, более низкую производительность. Есть также некоторая информация, которая не так интуитивно понятна. Например, хотя locks представляет собой запрошенную информацию о блокировке, она разделена на множество подпроектов. На первый взгляд люди не могут не запутаться:

  • Что означают ReplicationStateTransation, Global, Database, Collection и Mutex соответственно?
  • Что означают W, R, w и r соответственно?
  • Замокколичествоизчто означает размер?
  • другой Замокмеждуда Какие отношения?Какая связь??
  • MongoDB серединаиз Замокданравиться Как реализоватьиз?структуранравитьсячто?
  • Задав вышеизложенные вопросы, мы начали постепенно понимать MongoDB серединаиз Замок.

02. Блокировка MongoDB и классификация ресурсов

MongoDB поддерживает одновременные операции чтения и записи, поэтому блокировки необходимы для обеспечения согласованности данных во время параллелизма. В MongoDB разные запросы будут выполнять запросы к разным ресурсам, поэтому операция блокировки также выполняется на разных ресурсах. Избегайте конфликтов параллелизма за счет многоуровневого и иерархического управления ресурсами, а также использования различных типов блокировок для реализации механизмов блокировки.

2.1 Классификация ресурсов

Ресурсы в MongoDB разделены иерархически. Lock_manager блокировки сам по себе не различает, какому ресурсу он принадлежит, и ресурсы на разных уровнях никогда не являются взаимоисключающими (не влияют друг на друга). Вообще говоря, ресурсы в MongoDB классифицируются по следующим типам с уменьшением приоритета сверху вниз.

До версии 4.4 (и включительно) ресурсы классифицировались следующим образом:

Язык кода:javascript
копировать
enum ResourceType {
    RESOURCE_INVALID = 0,

    RESOURCE_PBWM,  // Блокировка ресурсов одновременной пакетной записи

    RESOURCE_RSTL, // Блокировка перехода состояния члена набора реплик, состояния включают ЗАПУСК, ПЕРВИЧНЫЙ, ВТОРИЧНЫЙ, ВОССТАНОВЛЕНИЕ и т. д.

    RESOURCE_GLOBAL, // Глобальные операции и блокировки ресурсов, такие как reIndex, renameCollection, replSetResizeOplog и другие операции, будут получать global W Замок

    RESOURCE_DATABASE, // databaseоперации на уровнеизресурс Замок,нравитьсяcloneCollectionCapped、collMod、compact、convertToCappedждать Держатьделать Всевстречаacquire databsae W Замок
    RESOURCE_COLLECTION, // collectionоперации на уровнеизресурс Замок,нравитьсяcreateCollection、createIndexes、dropждать Держатьделать Всевстречаacquire collection W Замок
    RESOURCE_METADATA, // Связанные с метаданнымииз Замок

    RESOURCE_MUTEX, // Оставшийсяиз Нети Связанный с уровнем храненияиздругойресурсиз Замок

    ResourceTypesCount
};

После версии 4.4 ресурсы классифицируются следующим образом:

Язык кода:javascript
копировать
// Типы ресурсов примерно такие же, как и до версии 4.4, изменились только глобальные ресурсы.
enum ResourceType {
    RESOURCE_INVALID = 0,
    RESOURCE_GLOBAL,
    RESOURCE_DATABASE,
    RESOURCE_COLLECTION,
    RESOURCE_METADATA,
    RESOURCE_MUTEX,
    ResourceTypesCount
};

// Используйте перечисления для представления всех глобальных ресурсов.
enum class ResourceGlobalId : uint8_t {
    kParallelBatchWriterMode,
    kFeatureCompatibilityVersion,
    kReplicationStateTransitionLock,
    kGlobal,

    kNumIds
};

Начиная с версии 5.0, RESOURCE_PBWM, RESOURCE_RSTL и RESOURCE_GLOBAL классифицируются как RESOURCE_GLOBAL, и для их разделения используется перечисление. Начиная с версии 5.0, также был добавлен пакет операций чтения без блокировки. Эти операции не будут блокироваться, когда другие операции удерживают эксклюзивные блокировки записи в той же коллекции, например, поиск, подсчет, различение, агрегирование, listCollections, listIndexes и т. д. Если агрегат содержит операции записи в коллекцию, он будет удерживать монопольную блокировку коллекции.

Приоритет вышеуказанных уровней ресурсов уменьшается сверху вниз. Чтобы предотвратить взаимоблокировку, вообще говоря, при блокировке ресурса с низким приоритетом вам необходимо сначала добавить блокировку намерения к ресурсу с более высоким приоритетом. Например: перед добавлением монопольной блокировки к RESOURCE_COLLECTION вам необходимо добавить намеренные монопольные блокировки к RESOURCE_DATABASE и RESOURCE_GLOBAL.

Таким образом, ситуация получения блокировки для этой операции следующая:

Язык кода:javascript
копировать
{
  "locks": {
    "Global": {
      "acquireCount": {
        "w": 1
      }
    },
    "Database": {
      "acquireCount": {
        "w": 1
      }
    },
    "Collection": {
      "acquireCount": {
        "W": 1
      }
    },
  }
}
2.2 Классификация замков

MongoDB не только разделяет ресурсы иерархически, но также разделяет типы блокировок, такие как намеренные блокировки, монопольные блокировки, общие блокировки и т. д., упомянутые выше. В этом разделе описывается, как эффективно разрешать отношения параллелизма на одном уровне ресурсов путем разделения разных типов блокировок.

Чтобы повысить эффективность параллелизма, MongoDB предоставляет режимы, аналогичные блокировкам чтения и записи, а именно общие блокировки (Shared, S) (блокировки чтения) и монопольные блокировки (Exclusive, X) (блокировки записи). решить проблему многоуровневого ресурса. Взаимоисключающая связь между ними повышает эффективность запросов многоуровневых ресурсов, а также обеспечивает на этой основе Intent Lock. То есть замки можно разделить на 4 типа:

Язык кода:javascript
копировать
enum LockMode {
    MODE_NONE = 0,
    MODE_IS = 1, //Обмен намерениями Замок,Преднамеренное чтение Замок,r
    MODE_IX = 2, // Намеренная эксклюзивность Замок,Намерение написать Замок,w
    MODE_S = 3, // общий Замок,читать Замок,R
    MODE_X = 4, // эксклюзивный Замок,Писать Замок,W

    LockModesCount
};

Зачем нам снова разделять блокировки намерения при наличии блокировок чтения и записи? Мы знаем, что в процессе многоуровневой блокировки ресурсов при блокировке ресурсов низкого уровня также необходимо добавлять блокировки намерений к ресурсам высокого уровня. Поскольку ресурсы высокого уровня часто имеют инклюзивную связь с ресурсами низкого уровня, цель добавления блокировки намерения заключается в следующем: при блокировке ресурса низкого уровня он сообщает внешнему миру, что существует ресурс высокого уровня, добавляя намерение. блокировка ресурса верхнего уровня. К ресурсу нижнего уровня добавлена ​​блокировка.

Для чего нужна блокировка намерения? Объяснение в энциклопедии следующее:

Если другая задача пытается применить общую или монопольную блокировку на уровне таблицы, она блокируется блокировкой намерения на уровне таблицы, управляемой первой задачей. Второй задаче не требуется проверять блокировки отдельных страниц или строк перед блокировкой таблицы. Вместо этого просто проверьте наличие блокировок намерений в таблице.

Это немного запутанно, поэтому вот пример:

  • Если будет операция A заперт один коллекцияX, сейчас идет еще одна операция B нужно DB1 выполнять операции, и collectionX также принадлежит DB1,в это времясуществовать Держатьделать B Прежде чем добавить Замок,Для проверки необходимы следующие шаги:
  • исследовать DB1 из Библиотека Замокда Удерживается ли он другими операциями;
  • по очередиисследовать DB1 Далее все из сбор, подтвердите, есть ли у да какие-либо другие операции, а именно collection Замок;

Здесь вам необходимо просмотреть все блокировки вторичных ресурсов для принятия решения. Если в определенной БД имеется много коллекций, время прохождения и получения блокировок будет увеличиваться линейно, поэтому вводится концепция намеренных блокировок. После добавления намеренной блокировки операции A необходимо добавить намеренную блокировку к DB1 перед блокировкой коллекции X. Таким образом, прежде чем операция B заблокирует DB1, вышеуказанные шаги проверки будут выглядеть следующим образом:

  • исследовать DB1 из Библиотека Замокда Удерживается ли он другими операциями;
  • исследовать DB1 изнамерение Замокда Удерживается ли он другими операциями。

Таким образом, при запросе блокировки вышестоящего ресурса вам нужно только проверить, занята ли блокировка намерения вышестоящего ресурса. Если она занята, это означает, что блокировка вторичного ресурса занята. В этом нет необходимости. для обхода блокировки всех вторичных ресурсов. Сделайте решение о захвате блокировки более эффективным.

03. Матрица блокировки MongoDB

После классификации общих/эксклюзивных блокировок и намеренных блокировок блокировки MongoDB можно разделить на 4 категории: разные типы блокировок имеют разную эксклюзивность по отношению к другим типам блокировок. Благодаря этим исключительным отношениям можно добиться эффективности продвижения блокировок. . Эту особую эксклюзивность можно охарактеризовать как матрицу замков.

Матрица блокировок MongoDB выглядит следующим образом:

Язык кода:javascript
копировать
/**
 * MongoDBЗамок матрицы,может быть основано на Замок Быстрый запрос матрицы в настоящее время хочет добавитьиз Замоки Уже добавлено Замокизтипданетконфликт
 *
 * | Requested Mode |                      Granted Mode                     |
 * |----------------|:------------:|:-------:|:--------:|:------:|:--------:|
 * |                |  MODE_NONE   | MODE_IS |  MODE_IX | MODE_S |  MODE_X  |
 * | MODE_IS        |      +       |    +    |     +    |    +   |          |
 * | MODE_IX        |      +       |    +    |     +    |        |          |
 * | MODE_S         |      +       |    +    |          |    +   |          |
 * | MODE_X         |      +       |         |          |        |          |
 */

По матрице блокировок можно напрямую сделать вывод, можно ли сделать блокировку, исходя из типа блокировки, требуемого запросом, и типа блокировки ресурса, соответствующего текущему запросу. Вот еще один пример:

Например, если A запрашивается для выполнения операции чтения в коллекции 2, ему необходимо получить блокировку IS (намеренную блокировку сегмента) коллекции 2. На основании вышеуказанного соотношения приоритетов на уровне ресурсов необходимо получить блокировку IS для DB2 и блокировка IS Global по порядку вверх:

В это время запрос B должен выполнить операцию удаления DB2 и получить блокировку X (эксклюзивную блокировку) DB2. На основании вышеуказанного соотношения приоритетов на уровне ресурсов ему необходимо получить блокировку IX Global Up. к матрице блокировки MongoDB:

  • Global:IX Замоки IS Замоксовместимый,Таким образом, вы можете получить Global из IX Замок;
  • DB2:X Замоки IS Замок Нетсовместимый,Поэтому Нетспособныйполучатьприезжать DB2 из X Блокировка, нужно подождать DB2 из IS разблокировка замка;

Таким образом, операция удаления операции B в DB2 в этот момент не будет выполнена, поскольку блокировка не удалась и будет ждать.

В это время запросу C необходимо выполнить операцию записи в коллекции 2 и получить блокировку IX (намеренную эксклюзивную блокировку) коллекции 2. На основании приведенного выше соотношения приоритетов на уровне ресурсов ему необходимо получить блокировку IX DB2 и DB2. IX блокировка Global в соответствии с матрицей блокировок MongoDB:

● Global: блокировка IX совместима с блокировкой IS, поэтому вы можете получить блокировку Global IX;

● DB2: блокировка IX совместима с блокировкой IS, поэтому можно получить блокировку IX DB2l;

● Collection2: замок IX совместим с замком IS, поэтому можно получить замок IX Collection2l;

Таким образом, эта операция может получить блокировку и успешно выполниться.

Благодаря приведенной выше иерархической классификации ресурсов, а также использованию преднамеренных блокировок и блокировок чтения-записи, высокий уровень параллелизма может быть относительно эффективно достигнут с помощью блокировок.

Три уровня ресурсов: «Глобальный», «База данных» и «Коллекция» и соответствующие им блокировки упомянуты выше. Самый маленький детальный ресурс в MongoDB — это «Документ», а детальная блокировка документа использует блокировку в механизме WT. В MongoDB операции обычно являются оптимистичным параллелизмом. Элементы управления, такие как операции записи, сначала изменяют данные при условии отсутствия конфликта и блокируют их только тогда, когда данные действительно изменяются. При сбое блокировки документа возникает конфликт записи (WriteConflict), и когда возникает конфликт записи. MongoDB автоматически повторит попытку, поэтому мы не будем обсуждать это здесь.

04. Введение: реализация блокировки MongoDB

Поняв поведение типов ресурсов и соответствующих блокировок в MongoDB, мы анализируем реализацию и вызов блокировок MongoDB на уровне кода и реализации. Наиболее детальные блокировки ресурсов в MongoDB — это блокировки на уровне коллекций. Давайте посмотрим, как получить блокировки коллекций в коде MongoDB.

Определения, связанные с ресурсами, и реализации в стиле RAII находятся в файлеcatalog_raii.h. Сведения о стиле RAII см. в: wiki-RAII, что означает «Получение и инициализация ресурсов» и по сути является C++. В этом типе объектно-ориентированного стиля программирования один из способов жестко связать приобретение и жизненный цикл ресурсов с жизненным циклом объекта состоит в том, чтобы выразить это проще: объект представляет собой ресурс, который необходимо приобрести, а приобретение ресурса завершается при инициализации объекта (Конструктор), завершает высвобождение ресурсов (уничтожение) при разрушении объекта.

Вы можете видеть, что для ресурсов коллекции определены следующие классы:

Язык кода:javascript
копировать
//catalog_raii.h

/**
 * RAII-стильиз категории,существоватьполучатьcollectionЗамокчасвстреча Следуйте инструкциям нижематрицапо очередиполучать Дажевысокийресурс Иерархияиз Замок
 *
 * | modeColl | Global Lock Result | DB Lock Result | Collection Lock Result |
 * |----------+--------------------+----------------+------------------------|
 * | MODE_IX  | MODE_IX            | MODE_IX        | MODE_IX                |
 * | MODE_X   | MODE_IX            | MODE_IX        | MODE_X                 |
 * | MODE_IS  | MODE_IS            | MODE_IS        | MODE_IS                |
 * | MODE_S   | MODE_IS            | MODE_IS        | MODE_S                 |
 */
class AutoGetCollection {
    ...

public:
    // Конструктор, используемый для вызова получения ресурсов
    AutoGetCollection(......);

    ......

protected:
    boost::optional<AutoGetDb> _autoDb;
    std::vector<Lock::CollectionLock> _collLocks;
    ......
};

В реализации AutoGetCollection вы также можете увидеть, что вам нужно сначала получить global/RSTL и соответствующую блокировку базы данных, а затем получить блокировки коллекции:

Язык кода:javascript
копировать
// catalog_raii.cpp

AutoGetCollection::AutoGetCollection(......) {
    invariant(!opCtx->isLockFreeReadsOp());

    ......

    // получатьglobal/RSTLзаблокировать такжевсепереписыватьсяизDBЗамок
    _autoDb.emplace(opCtx,
                    !nsOrUUID.dbname().empty() ? nsOrUUID.dbname() : nsOrUUID.nss()->db(),
                    isSharedLockMode(modeColl) ? MODE_IS : MODE_IX,
                    deadline,
                    secondaryDbNames);
    ......

    // получатьcollectionЗамок
    if (secondaryDbNames.empty()) {
        uassertStatusOK(nsOrUUID.isNssValid());
        _collLocks.emplace_back(opCtx, nsOrUUID, modeColl, deadline);
    } else {
        acquireCollectionLocksInResourceIdOrder(
            opCtx, nsOrUUID, modeColl, deadline, secondaryNssOrUUIDs, &_collLocks);
    }
    ......
}

Как определено выше, AutoGetCollection Если вы хотите получить определенное mode из collection Замок,Необходимость получения верхнего ресурса из намерения можно увидеть. Замок существования середина;,есть одинstd::vector<CollectionLock>представлятьнуждатьсяполучатьиз collection переписыватьсяиз Замок,иboost::optional<AutoGetDb>нопредставлять Понятнопереписываться Начальствоиз Database ресурс. получать collection Замок До,будет основано наполучатьиз mode Конвертировать, если получатьизда S или IS,соответствующий Database из IS Замок;нравитьсяфруктыполучатьизда X или IX, соответствующий Database из IX Замок.

получать Collection Вам нужно получатьпереписыватьсяиз ресурсов Database Ресурсы имеют следующие определения:

Язык кода:javascript
копировать
// catalog_raii.h

// RAII-style class, МожетполучатьDBУровень изресурс Замок
class AutoGetDb {
    ......

public:
    // Конструктор, используемый для вызова получения ресурсов
    AutoGetDb(......);
    ......
    
private:
    std::string _dbName;
    Lock::DBLock _dbLock;
    Database* _db;
    std::vector<Lock::DBLock> _secondaryDbLocks;
};

в Lock::DBLock То есть Database держатьизресурс Замок,существоватьполучать Database Уровень из Замок До,Прежде всегополучатьв Включатьиз Global Уровень из Замок.

4.1 Классификация реализации замков

Вышеупомянутый процесссерединаописывать Понятнополучать Низкий Уровень изресурс Замоквпереднуждаться Первыйполучатьвысокий Уровень изресурс Замок,Включено соответственно:

  • GlobalLock: президент с глобальным ресурсом Замок;
  • DBLock: представляет Database ресурс Замок;
  • CollectionLock: представляет Collection Ресурсы Замок.

Вот и все Замокиз Определениясуществовать d_concurrency.h В файле мы рассмотрим его реализацию отдельно:

Язык кода:javascript
копировать
// d_concurrency.h

    /**
     * Collection Уровень из Замок
     * Должен Замок Поддержка следующих типовтип:
     *   MODE_IS: concurrent collection access, requiring read locks
     *   MODE_IX: concurrent collection access, requiring read or write locks
     *   MODE_S:  shared read access to the collection, blocking any writers
     *   MODE_X:  exclusive access to the collection, blocking all other readers and writers
     *
     * существоватьполучатьcollectionЗамок ДонуждатьсяполучатьпереписыватьсяизнамерениеDBЗамок
     */
    class CollectionLock {
        CollectionLock(const CollectionLock&) = delete;
        CollectionLock& operator=(const CollectionLock&) = delete;

    public:
        CollectionLock(OperationContext* opCtx,
                       const NamespaceStringOrUUID& nssOrUUID,
                       LockMode mode,
                       Date_t deadline = Date_t::max());

        CollectionLock(CollectionLock&&);
        ~CollectionLock();

    private:
        ResourceId _id;
        OperationContext* _opCtx;
    };

Как видно из записки середина, существуетполучать Collection Замок Донуждатьсяполучатьпереписыватьсяиз Database Замок.существовать private в домене ResourceId представлять Понятнонуждатьсядобавлять Замокиз Collection переписыватьсяиз идентификатор ресурса, в MongoDB Все ресурсы проводятся Resource логотип в то время как OperationContext серединаноиметьочень хочудобавлять Замокизсодержание,Можетследовать CollectionLock из Реализация конструкторасерединасмотретьприезжатьспецифическийиздобавлять Замокпроцесс:

Язык кода:javascript
копировать
// d_concurrency.cpp

Lock::CollectionLock::CollectionLock(......)
    : _opCtx(opCtx) {
    if (nssOrUUID.nss()) {
        auto& nss = *nssOrUUID.nss();
        _id = {RESOURCE_COLLECTION, nss.ns()};

        invariant(nss.coll().size(), str::stream() << "expected non-empty collection name:" << nss);
        dassert(_opCtx->lockState()->isDbLockedForMode(nss.db(),
                                                       isSharedLockMode(mode) ? MODE_IS : MODE_IX));

        _opCtx->lockState()->lock(_opCtx, _id, mode, deadline);
        return;
    }
    ......
    
    bool locked = false;
    NamespaceString prevResolvedNss;
    do {
        if (locked) {
            _opCtx->lockState()->unlock(_id);
        }

        _id = ResourceId(RESOURCE_COLLECTION, nss.ns());
        _opCtx->lockState()->lock(_opCtx, _id, mode, deadline);
        locked = true;

        prevResolvedNss = nss;
        nss = CollectionCatalog::get(opCtx)->resolveNamespaceStringOrUUID(opCtx, nssOrUUID);
    } while (nss != prevResolvedNss);
}

Вот и все,дапроходить_opCtx->lockStat()->lock()получатьпереписыватьсяиз Ресурсы Замок.и Замокизопределениесуществовать OperationContext серединада определяет это так:

Язык кода:javascript
копировать
// operation_context.h

class OperationContext : public Interruptible, public Decorable<OperationContext> {
    OperationContext(const OperationContext&) = delete;
    OperationContext& operator=(const OperationContext&) = delete;
public:
    ......

    // Interface for locking.  Caller DOES NOT own pointer.
    Locker* lockState() const {
        return _locker.get();
    }

    ......

private:
    ......

    std::unique_ptr<Locker> _locker;
    
    ......
};

в _locker для Locker изодин unique указатель, в Locker для виртуального класса как для Interface,определение Замокизструктура И идидля,определениесуществовать locker.h Файл середина, его общая реализация для LockerImpl, определенный в lock_state.h в файле. давай не будем это обсуждать сейчас Locker из Конкретная структура, наследование также реализация, вам нужно только сначала знать вызов дапроходить OperationConetext->lockState()->lock() интерфейсвыполнятьиз Замокизполучать。

получать Collection Замок Донуждаться Первыйполучатьпереписыватьсяиз Database Блокировка, способ реализации:

Язык кода:javascript
копировать
// d_concurrency.h
  
    /**
     * DatabaseРесурсы Замок.
     *
     * Должен Замок Поддержка следующих типовтип::
     *   MODE_IS: concurrent database access, requiring further collection read locks
     *   MODE_IX: concurrent database access, requiring further collection read or write locks
     *   MODE_S:  shared read access to the database, blocking any writers
     *   MODE_X:  exclusive access to the database, blocking all other readers and writers
     *
     * существоватьполучатьDBЗамоквпереднуждатьсяполучатьпереписыватьсятипизglobalЗамок
     */
    class DBLock {
    public:
        DBLock(OperationContext* opCtx,
               StringData db,
               LockMode mode,
               Date_t deadline = Date_t::max(),
               bool skipGlobalAndRSTLLocks = false);
        ......

    private:
        const ResourceId _id;
        OperationContext* const _opCtx;
        ......

        // Acquires the global lock on our behalf.
        boost::optional<GlobalLock> _globalLock;
    };

существовать DBLock из Определения середина, чтобы ResourceId для идентификатора ресурса также содержит OperationContext приходи в гости настоящийизресурс Замок,Но будьте осторожны,существовать DBLock из Определений середина много boost::optional<GlobalLock>,выражать global Уровень из Замок,Прямо сейчассуществоватьдлякаждый Database Базовый Замокдобавлять Замоквперед,Теоретически Всенуждаться Первыйдля Global Запирай и запирай, ты можешь видеть DBLock() Конструктор реализован следующим образом:

Язык кода:javascript
копировать
// concurrency.cpp

Lock::DBLock::DBLock(......)
    : _id(RESOURCE_DATABASE, db), _opCtx(opCtx), _result(LOCK_INVALID), _mode(mode) {

    if (!skipGlobalAndRSTLLocks) {
        _globalLock.emplace(opCtx,
                            isSharedLockMode(_mode) ? MODE_IS : MODE_IX,
                            deadline,
                            InterruptBehavior::kThrow);
    }
    massert(28539, "need a valid database name", !db.empty() && nsIsDbOnly(db));

    _opCtx->lockState()->lock(_opCtx, _id, _mode, deadline);
    _result = LOCK_OK;
}

Если установлено SkipGlobalAndRSLocks, вам не нужно получать Global Замок,нетно Всенуждатьсяполучатьпереписываться mode из Global Замок.в то же времяпроходить OperationContext->lockState()->lock() интерфейсполучатьпереписыватьсяиз Database Ресурсы Замок.

получать Database Замок Донуждаться Первыйполучать Global Уровень из Замок,Давайте продолжим видеть Global Замокиз реализуется следующим образом:

Язык кода:javascript
копировать
// d_concurrency.h

    // GlobalУровень из Ресурсы Замок.
    class GlobalLock {
    public:
        GlobalLock(OperationContext* opCtx, LockMode lockMode)
            : GlobalLock(opCtx, lockMode, Date_t::max(), InterruptBehavior::kThrow) {}

        // A GlobalLock with a deadline requires the interrupt behavior to be explicitly defined.
        GlobalLock(OperationContext* opCtx,
                   LockMode lockMode,
                   Date_t deadline,
                   InterruptBehavior behavior,
                   bool skipRSTLLock = false);
        ......

    private:
        ......
        OperationContext* const _opCtx;
        LockResult _result;
        ResourceLock _pbwm;
        ResourceLock _fcvLock;
        ......
    };
    
    // Global exclusive lock
    class GlobalWrite : public GlobalLock {};
    
    // Global shared lock
    class GlobalRead : public GlobalLock {};

Помимо GlobalLock, есть еще GlobalRead и GlobalWrite, которые здесь подробно рассматриваться не будут, только GlobalLock. Как видите, в GlobalLock определены два интерфейса:

  • _takeGlobalLockOnly(): брать только Global Замок;
  • _takeGlobalAndRSTLocks(): занимать одновременно RSTL и Global Замок;

Вы также можете увидеть, что есть _pbwm заблокировать также _fcv замок, оба замка ResourceLock объектизформальная реализация。

Язык кода:javascript
копировать
 // General purpose RAII wrapper for a resource managed by the lock manager
    class ResourceLock {
        ResourceLock(const ResourceLock&) = delete;
        ResourceLock& operator=(const ResourceLock&) = delete;

    public:
        ......
        // Acquires lock on this specified resource in the specified mode.
        void lock(OperationContext* opCtx, LockMode mode, Date_t deadline = Date_t::max());
        void unlock();
        bool isLocked() const;

    private:
        const ResourceId _rid;
        Locker* const _locker;
        LockResult _result;
    };

ResourceLock середина также имеет ресурс ResourceId,а такжепредставляетактуальный Замокиз Шкафчик. См. ниже GlobalLock из Конструкторизвыполнитьсерединаданравитьсячтополучать Замокиз:

Язык кода:javascript
копировать
Lock::GlobalLock::GlobalLock(OperationContext* opCtx,
                             LockMode lockMode,
                             Date_t deadline,
                             InterruptBehavior behavior,
                             bool skipRSTLLock)
    : _opCtx(opCtx),
      _result(LOCK_INVALID),
      _pbwm(opCtx->lockState(), resourceIdParallelBatchWriterMode),
      _fcvLock(opCtx->lockState(), resourceIdFeatureCompatibilityVersion),
      _interruptBehavior(behavior),
      _skipRSTLLock(skipRSTLLock),
      _isOutermostLock(!opCtx->lockState()->isLocked()) {
    _opCtx->lockState()->getFlowControlTicket(_opCtx, lockMode);

    try {
        ......
        _result = LOCK_INVALID;
        if (skipRSTLLock) {
            _takeGlobalLockOnly(lockMode, deadline);
        } else {
            _takeGlobalAndRSTLLocks(lockMode, deadline);
        }
        ......
    } catch (const ExceptionForCat<ErrorCategory::Interruption>&) {
        ......
    }
    ......
}

void Lock::GlobalLock::_takeGlobalLockOnly(LockMode lockMode, Date_t deadline) {
    _opCtx->lockState()->lockGlobal(_opCtx, lockMode, deadline);
}

void Lock::GlobalLock::_takeGlobalAndRSTLLocks(LockMode lockMode, Date_t deadline) {
    _opCtx->lockState()->lock(_opCtx, resourceIdReplicationStateTransitionLock, MODE_IX, deadline);
    ScopeGuard unlockRSTL(
        [this] { _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); });

    _opCtx->lockState()->lockGlobal(_opCtx, lockMode, deadline);

    unlockRSTL.dismiss();
}

Вы можете видеть, что это наконец прошло OperationContext->lockState()->lockGlobal() интерфейс Приходитьполучатьобщая ситуация Замок.

4.2 Конструкция замка

Как видно из вышеизложенного, либо CollectionLock、DBLock、GlobalLock все еще ResourceLock,Это финалдапроходитьLockerдобрыйизобъект Приходитьвыполнить Замокизполучатьивыпускать.Locker добрыйопределение Понятно Замокизполучать、выпускатьждать ХОРОШОдля,以один个虚добрыйизформасуществоватьделатьдля MongoDB середина lock концепция из Interface,Чтоопределениесуществовать locker.h документсередина,Есть больше категорий, таких как интерфейс,Давайте рассмотрим ключевые моменты:

Язык кода:javascript
копировать
// locker.h

// Interface for acquiring locks. One of those objects will have to be instantiated for each request (transaction).
class Locker {
    Locker(const Locker&) = delete;
    Locker& operator=(const Locker&) = delete;

    friend class UninterruptibleLockGuard;

public:
    virtual ~Locker() {}
    
    /**
     * This is what the lock modes on the global lock mean:
     * IX - Regular write operation
     * IS - Regular read operation
     * S  - Stops all *write* activity. Used for administrative operations (repl, etc).
     * X  - Stops all activity. Used for administrative operations (repl state changes,
     *          shutdown, etc).
     */
    virtual void lockGlobal(OperationContext* opCtx,
                            LockMode mode,
                            Date_t deadline = Date_t::max()) = 0;
    virtual bool unlockGlobal() = 0;
    
    /**
     * Requests the RSTL to be acquired in the requested mode (typically mode X) . This should only
     * be called inside ReplicationStateTransitionLockGuard.
     */
    virtual LockResult lockRSTLBegin(OperationContext* opCtx, LockMode mode) = 0; 
    virtual void lockRSTLComplete(OperationContext* opCtx, LockMode mode, Date_t deadline) = 0;                  
    
    /**
     * Acquires lock on the specified resource in the specified mode and returns the outcome
     * of the operation. See the details for LockResult for more information on what the
     * different results mean.
     */
    virtual void lock(OperationContext* opCtx,
                      ResourceId resId,
                      LockMode mode,
                      Date_t deadline = Date_t::max()) = 0;
    virtual void lock(ResourceId resId, LockMode mode, Date_t deadline = Date_t::max()) = 0;
    virtual bool unlock(ResourceId resId) = 0;
    
    void skipAcquireTicket();
    void setAcquireTicket();
    shouldAcquireTicket();
protected:
    ......
private:
    ......
};

в lockGlobal() и unlockGlobal() да Global Базовыйресурсизполучать/выпускать Замок,lockRSTLBegin() и lockRSTLComplete() дляверно RSTL Замокизполучать/выпускать,lock() и unlock() интерфейстогдадругие обозначения ResourceId переписыватьсяресурс Замокизполучать/выпускать.

Locker Определен только один набор interface,и Обычно мысуществовать MongoDB серединаиспользоватьиз Замок,Вседапроходить LockerImpl Поймите, его определение существует lock_state.h середина:

Язык кода:javascript
копировать
// lock_state.h

/**
 * Interface for acquiring locks. One of those objects will have to be instantiated for each request (transaction).
 */
class LockerImpl : public Locker {
public:
    ......
private:
     /**
     * Allows for lock requests to be requested in a non-blocking way. There can be only one
     * outstanding pending lock request per locker object.
     *
     * _lockBegin posts a request to the lock manager for the specified lock to be acquired,
     * which either immediately grants the lock, or puts the requestor on the conflict queue
     * and returns immediately with the result of the acquisition. The result can be one of:
     *
     * LOCK_OK - Nothing more needs to be done. The lock is granted.
     * LOCK_WAITING - The request has been queued up and will be granted as soon as the lock
     *      is free. If this result is returned, typically _lockComplete needs to be called in
     *      order to wait for the actual grant to occur. If the caller no longer needs to wait
     *      for the grant to happen, unlock needs to be called with the same resource passed
     *      to _lockBegin.
     */
    LockResult _lockBegin(OperationContext* opCtx, ResourceId resId, LockMode mode);
    
    void _lockComplete(OperationContext* opCtx, ResourceId resId, LockMode mode, Date_t deadline);
    
    /**
     * Acquires a ticket for the Locker under 'mode'.
     * Returns true   if a ticket is successfully acquired.
     *         false  if it cannot acquire a ticket within 'deadline'.
     * It may throw an exception when it is interrupted.
     */
    bool _acquireTicket(OperationContext* opCtx, LockMode mode, Date_t deadline);
    
    ......
};

LockerImpl Осуществленный Locker интерфейс и много нового private изинтерфейс,Выше середина выделены три интерфейса,Соответственно:

  • _lockBegin:проходить lockManager Приходитьполучать Замокили ВОЗсуществоватьочередьсерединаждатьобращаться Замок;
  • _lockComplete:ждатьобращаться Замокизполучать;
  • _acquireTicket:получать ticket。

для Зачем выбирать три из них отдельно? Взгляните на реализацию LockerImpl. Для двух важных интерфейсов lockGlobal() и lock() из:

Язык кода:javascript
копировать
// lock_state.cpp

void LockerImpl::lockGlobal(OperationContext* opCtx, LockMode mode, Date_t deadline) {
    dassert(isLocked() == (_modeForTicket != MODE_NONE));
    if (_modeForTicket == MODE_NONE) {
        if (_uninterruptibleLocksRequested) {
            // Ignore deadline and _maxLockTimeout.
            invariant(_acquireTicket(opCtx, mode, Date_t::max()));
        } else {
            auto beforeAcquire = Date_t::now();
            deadline = std::min(deadline,
                                _maxLockTimeout ? beforeAcquire + *_maxLockTimeout : Date_t::max());
            uassert(ErrorCodes::LockTimeout,
                    str::stream() << "Unable to acquire ticket with mode '" << mode
                                  << "' within a max lock request timeout of '"
                                  << Date_t::now() - beforeAcquire << "' milliseconds.",
                    _acquireTicket(opCtx, mode, deadline));
        }
        _modeForTicket = mode;
    }

    const LockResult result = _lockBegin(opCtx, resourceIdGlobal, mode);
    // Fast, uncontended path
    if (result == LOCK_OK)
        return;

    invariant(result == LOCK_WAITING);
    _lockComplete(opCtx, resourceIdGlobal, mode, deadline);
}

void LockerImpl::lock(OperationContext* opCtx, ResourceId resId, LockMode mode, Date_t deadline) {
    // `lockGlobal` must be called to lock `resourceIdGlobal`.
    invariant(resId != resourceIdGlobal);

    const LockResult result = _lockBegin(opCtx, resId, mode);

    // Fast, uncontended path
    if (result == LOCK_OK)
        return;

    invariant(result == LOCK_WAITING);
    _lockComplete(opCtx, resId, mode, deadline);
}

Это видно из приведенной выше реализации:

Или lockGlobal() все еще lock() Наконец, позвонив _lockBegin() и _lockComplete() проходить lockManager Приходитьполучатьпереписываться Уровень изресурс Замок;

Global Замоксуществоватьполучатьвпередвозвращатьсянуждатьсявызов _acquireTicket() Приходитьполучать Ticket。

Поэтому мы обсудили отдельно ticket и lockManager。

4.3 О билетах

MongoDB Существует два типа серединаиз Билет, своего рода да При включенном управлении потоком (FlowControl), существуетполучать Global Перед блокировкой необходимо позвонить getFlowContrlTicket() Приходитьполучатьконтроль потокаиз билет, по сути дапроходить дырявое ведро, токен-ведро и другие методы, изучают операцию ограничения тока. Другой - дасуществоватьполучить Global Замокчас,нуждаться Первыйпроходить _acquireTicket() интерфейсполучатьпереписыватьсяиз ticket,MongoDB проходить ticket Для управления параллелизмом запросов теоретически большинство запросов (если не установлено SkipAcquireTicket) необходимы для получения Global Замок(IX、IS、X),Поэтому для всех запросов требуется билет на получение.,_acquireTicket() из реализуется следующим образом:

Язык кода:javascript
копировать
// lock_state.cpp

bool LockerImpl::_acquireTicket(OperationContext* opCtx, LockMode mode, Date_t deadline) {
    const bool reader = isSharedLockMode(mode);
    auto holder = shouldAcquireTicket() ? _ticketHolders->getTicketHolder(mode) : nullptr;
    if (holder) {
        _clientState.store(reader ? kQueuedReader : kQueuedWriter);

        // If the ticket wait is interrupted, restore the state of the client.
        ScopeGuard restoreStateOnErrorGuard([&] { _clientState.store(kInactive); });

        // Acquiring a ticket is a potentially blocking operation.
        if (opCtx)
            invariant(!opCtx->recoveryUnit()->isTimestamped());

        auto waitMode = _uninterruptibleLocksRequested ? TicketHolder::WaitMode::kUninterruptible
                                                       : TicketHolder::WaitMode::kInterruptible;
        if (deadline == Date_t::max()) {
            _ticket = holder->waitForTicket(opCtx, &_admCtx, waitMode);
        } else if (auto ticket = holder->waitForTicketUntil(opCtx, &_admCtx, deadline, waitMode)) {
            _ticket = std::move(*ticket);
        } else {
            return false;
        }
        restoreStateOnErrorGuard.dismiss();
    }
    _clientState.store(reader ? kActiveReader : kActiveWriter);
    return true;
}

проходить _ticketHolders Приходите, чтобы получить один ticketHolder объект,Сновапроходить ticketHolder->waitForTicket() или ticketHolder->waitForTicketUntil() Приходитьполучать ticket。MongoDB проходить TicketHolders управлять TicketHolder,точка Непроходить _openWriteTransaction и _openReadTransaction управлять Писатьичитатьиз ticket。

Язык кода:javascript
копировать
// ticketHolders.h

class TicketHolders {
public:
    ......

    static TicketHolders& get(ServiceContext* svcCtx);
    static TicketHolders& get(ServiceContext& svcCtx);
    /**
     * Sets the TicketHolder implementation to use to obtain tickets from 'reading' (for MODE_S and
     * MODE_IS), and from 'writing' (for MODE_IX) in order to throttle database access. There is no
     * throttling for MODE_X, as there can only ever be a single locker using this mode. The
     * throttling is intended to defend against large drops in throughput under high load due to too
     * much concurrency.
     */
    void setGlobalThrottling(std::unique_ptr<TicketHolder> reading,
                             std::unique_ptr<TicketHolder> writing);

    TicketHolder* getTicketHolder(LockMode mode);

private:
    std::unique_ptr<TicketHolder> _openWriteTransaction;
    std::unique_ptr<TicketHolder> _openReadTransaction;
};

// ticketHolders.cpp

TicketHolder* TicketHolders::getTicketHolder(LockMode mode) {
    switch (mode) {
        case MODE_S:
        case MODE_IS:
            return _openReadTransaction.get();
        case MODE_IX:
            return _openWriteTransaction.get();
        default:
            return nullptr;
    }
}

существовать MongoDB Будет вызван при запуске InitializeStorageEngine(), исуществовать будет инициализировано. TicketHolder изтипа такжеколичество:

Язык кода:javascript
копировать
// storage_engine_init.cpp

StorageEngine::LastShutdownState initializeStorageEngine(OperationContext* opCtx,
                                                         const StorageEngineInitFlags initFlags) {
......

    // This should be set once during startup.
    if (storageGlobalParams.engine != "ephemeralForTest" &&
        (initFlags & StorageEngineInitFlags::kForRestart) == StorageEngineInitFlags{}) {
        auto readTransactions = gConcurrentReadTransactions.load();
        static constexpr auto DEFAULT_TICKETS_VALUE = 128;
        readTransactions = readTransactions == 0 ? DEFAULT_TICKETS_VALUE : readTransactions;
        auto writeTransactions = gConcurrentWriteTransactions.load();
        writeTransactions = writeTransactions == 0 ? DEFAULT_TICKETS_VALUE : writeTransactions;

        auto svcCtx = opCtx->getServiceContext();
        auto& ticketHolders = TicketHolders::get(svcCtx);
        if (feature_flags::gFeatureFlagExecutionControl.isEnabledAndIgnoreFCV()) {
            LOGV2_DEBUG(5190400, 1, "Enabling new ticketing policies");
            switch (gTicketQueueingPolicy) {
                case QueueingPolicyEnum::Semaphore:
                    LOGV2_DEBUG(6382201, 1, "Using Semaphore-based ticketing scheduler");
                    ticketHolders.setGlobalThrottling(
                        std::make_unique<SemaphoreTicketHolder>(readTransactions, svcCtx),
                        std::make_unique<SemaphoreTicketHolder>(writeTransactions, svcCtx));
                    break;
                case QueueingPolicyEnum::FifoQueue:
                    LOGV2_DEBUG(6382200, 1, "Using FIFO queue-based ticketing scheduler");
                    ticketHolders.setGlobalThrottling(
                        std::make_unique<FifoTicketHolder>(readTransactions, svcCtx),
                        std::make_unique<FifoTicketHolder>(writeTransactions, svcCtx));
                    break;
            }
        } else {
            ticketHolders.setGlobalThrottling(
                std::make_unique<SemaphoreTicketHolder>(readTransactions, svcCtx),
                std::make_unique<SemaphoreTicketHolder>(writeTransactions, svcCtx));
        }
    }

......
}

Как видно из кода середина, по умолчанию используется чтение и запись. ticket Номер для 128, и TicketHolder Есть два типа:

  • SemaphoreTicketHolder: одновременно удерживает управление семафором. ticket Номер изTicketHolder, при количестве запросов больше 128 не получается ticket изThread будет заблокирован из-за контроля семафора, когда Когда есть лишние ресурсы высвобождаются, семафор проводить середина прерывается для вызова ресурса получения;
  • FifoTicketHolder:проходить FIFO очередь Приходитьконтрольв то же времядержатьticketНомер изTicketHolder, при количестве запросов больше 128 не получается ticket изThreads войдет в очередь «первым пришел — первым вышел» из wait очередь подождите ticket выпускать.

два TicketHolder из waitForTicket() Суть интерфейсов в конечном итоге заключается в вызове waitForTicketUntil() Интерфейс, пожалуйста, смотрите подробности SemaphoreTicketHolder::waitForTicketUntil() и FifoTicketHolder::waitForTicketUntil() извыполнить,всесуществовать ticketHolder.cpp документсередина。

Получаем вывод: MongoDB существоватьполучать Global Замок До,Первыйполучать Ticket, Ticket да MongoDB Используется для управления параллелизмом инструмента, существует инициализация. StorageEngine будет инициализирован, когда TicketHolder и инициализировать чтение и запись. Ticket по 128 штук (значение по умолчанию, можно изменить), одновременно TicketHolder Существует два типа, соответственно да использует семафор середина для прерывания потока ожидания уведомления из SemaphoreTicketHolder,а такжепроходить FIFO в свою очередь создать ожидающий поток получения Ticket из FifoTicketHolder, два типа Holder Основное различие заключается в том, как уведомлять потоки, ожидающие получения ресурсов.

проходитьочередь/стоять Прямо сейчасполучатьнад Ticket назад,удобный Можетпроходить _lockBegin и _lockComplete получать Замок.

4.4 Замокочередь&Механизм борьбы с голоданием

Давайте обсудим это вместе _lockBegin() и _lockComplete() дваинтерфейса, первый взгляд _lockBegin():

Язык кода:javascript
копировать
// lock_state.cpp

MONGO_TSAN_IGNORE
LockResult LockerImpl::_lockBegin(OperationContext* opCtx, ResourceId resId, LockMode mode) {
    dassert(!getWaitingResource().isValid());
    ......

    // Making this call here will record lock re-acquisitions and conversions as well.
    globalStats.recordAcquisition(_id, resId, mode);
    _stats.recordAcquisition(resId, mode);

    // Give priority to the full modes for Global, PBWM, and RSTL resources so we don't stall global
    // operations such as shutdown or stepdown.
    const ResourceType resType = resId.getType();
    if (resType == RESOURCE_GLOBAL) {
        if (mode == MODE_S || mode == MODE_X) {
            request->enqueueAtFront = true;
            request->compatibleFirst = true;
        }
    } else if (resType != RESOURCE_MUTEX) {
        // This is all sanity checks that the global locks are always be acquired
        // before any other lock has been acquired and they must be in sync with the nesting.
        if (kDebugBuild) {
            const LockRequestsMap::Iterator itGlobal = _requests.find(resourceIdGlobal);
            invariant(itGlobal->recursiveCount > 0);
            invariant(itGlobal->mode != MODE_NONE);
        };
    }

    // The notification object must be cleared before we invoke the lock manager, because
    // otherwise we might reset state if the lock becomes granted very fast.
    _notify.clear();

    LockResult result = isNew ? getGlobalLockManager()->lock(resId, request, mode)
                              : getGlobalLockManager()->convert(resId, request, mode);

    ......

    return result;
}

существовать _lockBegin() середина,встречапроходить globalStats и _stats записать ResId переписыватьсяизресурспроситьа также Замокиз в то же время на нем также будет основан режим; resType а также mode из типа, чтобы установить очередь из приоритета для реализации механизма предотвращения голодания. существованиеполучать Конец ticket назад,существоватьполучать Замокизчас Погода все ещевстречаруководитьочередь,здесьпроходитьдля S и X из Global Замокнастраивать Понятно Дажевысокийизочередьприоритет,проходить request->enqueueAtFront=true и request->compatibleFirst=true гарантировать Global Уровень из S и X Замоксуществовать Замокочередьчасиметь Дажевысокийизприоритет,чтобы обеспечитьнравиться shutdown или stepdown Такие запросы не будут блокироваться долгое время. Здесь у нас сначала возникает впечатление, т. е. MongoDB существоватьполучать Замокчасвсе ещеесть одиночередьочередьиз Состояние,Я подробно объясню, как встать в очередь позже.,а также Как предотвратить голодание.

наконец,проходить getGlobalLockManager()->lock() и getGlobalManager()->convert() интерфейс Приходитьверно Замокруководитьполучать,и возвращает получениеиз результатов:

Язык кода:javascript
копировать
// lock_manager_defs.h

/**
 * Return values for the locking functions of the lock manager.
 */
enum LockResult {

    // успехполучатьприезжать Замок
    LOCK_OK,

    // житьсуществовать Замокконфликт,ждатьобращатьсяполучать Замок
    LOCK_WAITING,

    // получать Замокдогонятьчас
    LOCK_TIMEOUT,

    // Значение инициализации, не следует использовать
    LOCK_INVALID
};

Видно, что результат LOCK_OK нопредставлятьуспехполучать Понятнопереписыватьсяресурсиз Замок,и Узелфрукты LOCK_WAITING нопредставлять Понятнокогдавпереднуждатьсяполучатьиз Замок已одеялодругой Держатьделатьзаниматьиметь,Входить Замокконфликтждатьобращатьсяочередь;LOCK_TIMEOUT Это значит, что время ожидания существования истекло.

Продолжить чтение здесь _lockComplete() извыполнить:

Язык кода:javascript
копировать
// lock_state.cpp

void LockerImpl::_lockComplete(OperationContext* opCtx,
                               ResourceId resId,
                               LockMode mode,
                               Date_t deadline) {
    ......

    while (true) {
        if (opCtx && _uninterruptibleLocksRequested == 0) {
            result = _notify.wait(opCtx, waitTime);
        } else {
            result = _notify.wait(waitTime);
        }

        // Account for the time spent waiting on the notification object
        const uint64_t curTimeMicros = curTimeMicros64();
        const uint64_t elapsedTimeMicros = curTimeMicros - startOfCurrentWaitTime;
        startOfCurrentWaitTime = curTimeMicros;

        globalStats.recordWaitTime(_id, resId, mode, elapsedTimeMicros);
        _stats.recordWaitTime(resId, mode, elapsedTimeMicros);

        if (result == LOCK_OK)
            break;

        // If infinite timeout was requested, just keep waiting
        if (timeout == Milliseconds::max()) {
            continue;
        }

        ......
    }

    invariant(result == LOCK_OK);
    unlockOnErrorGuard.dismiss();
    _setWaitingResource(ResourceId());
}

Как и в приведенном выше коде, существуют только вызовы _lockBegin() Возвращенный результат не да LOCK_OK буду продолжать звонить _lockComplete() Ожидание, существование _lockComplete() середина、Волявстречаосуществлятьждатьобращаться,нравитьсяфруктыждатьобращатьсяиз Замокодеяловыпускатьиуспехбратьприезжать,воляпроходить _notifyWait() Уведомить и в то же время _lockComplete() серединавозвращатьсявстречастатистикаждать Замокизвремяждатьинформация。

上述文章описывать Понятноданравитьсячтождатьобращатьсяиполучать Замокиз,финальный Вседапроходить LockManager->lock() Приходитьосуществлятьиз Замокполучатьи Ожидание в очереди,Ниже мыточкаанализировать LockManager изструктура такжеданравитьсячтопредотвращать Замокголодатьиз。

Структура LockManager следующая:

Язык кода:javascript
копировать
// lock_manager.h

/**
 * Entry point for the lock manager scheduling functionality. Don't use it directly, but
 * instead go through the Locker interface.
 */
class LockManager {
    ......

public:
    ......

    /**
     * Acquires lock on the specified resource in the specified mode and returns the outcome
     * of the operation. See the details for LockResult for more information on what the
     * different results mean.
     */
    LockResult lock(ResourceId resId, LockRequest* request, LockMode mode);
    LockResult convert(ResourceId resId, LockRequest* request, LockMode newMode);
    bool unlock(LockRequest* request);
    ......

private:
    // The lockheads need access to the partitions
    friend struct LockHead;

    // These types describe the locks hash table
    struct LockBucket {
        SimpleMutex mutex;
        typedef stdx::unordered_map<ResourceId, LockHead*> Map;
        Map data;
        LockHead* findOrInsert(ResourceId resId);
    };

    struct Partition {
        PartitionedLockHead* find(ResourceId resId);
        PartitionedLockHead* findOrInsert(ResourceId resId);
        typedef stdx::unordered_map<ResourceId, PartitionedLockHead*> Map;
        SimpleMutex mutex;
        Map data;
    };

    LockBucket* _getBucket(ResourceId resId) const;
    Partition* _getPartition(LockRequest* request) const;
    ......
    static const unsigned _numLockBuckets;
    LockBucket* _lockBuckets;

    static const unsigned _numPartitions;
    Partition* _partitions;
};

существоватьLockManagerсередина,Более важный внешний интерфейс, а именно lock(), Convert(), unlock().,а такжев Некоторые из наиболее важных из структуробъектов:

  • LockHead:структуратело по-настоящемуизполучать Замок,включает предоставленный список и ожидание из conflictList;
  • LockBucket: структура используется для определения ResourceId-->LockHead изHashповерхность;
  • _lockBuckets:для одного Зависит от _numLockBuckets Определить длину из LockBucket множество;
  • _numLockBuckets: определено LockBucket Из длины код середина определяется как 128.

Структуру LockManager можно выразить следующим образом:

Обсуждается отдельно ниже: Сначала LockManager серединажитьсуществоватьдлинадля128из BucketArray,Что Может Ни с чем Замокдоступ,существует каждая операция lock(), середина сначала применяет resId % 128, чтобы найти переписатьсяиз ведро, использованное здесь ResourceId не имеет ничего общего друг с другом.,Улучшенный параллелизм.

Язык кода:javascript
копировать
// lock_manager.cpp

LockResult LockManager::lock(ResourceId resId, LockRequest* request, LockMode mode) {
    ......
    
    // Use regular LockHead, maybe start partitioning
    LockBucket* bucket = _getBucket(resId);
    ......
    LockHead* lock = bucket->findOrInsert(resId);

    // Start a partitioned lock if possible
    if (request->partitioned && !(lock->grantedModes & (~intentModes)) && !lock->conflictModes) {
        Partition* partition = _getPartition(request);
        stdx::lock_guard<SimpleMutex> scopedLock(partition->mutex);
        PartitionedLockHead* partitionedLock = partition->findOrInsert(resId);
        invariant(partitionedLock);
        lock->partitions.push_back(partition);
        partitionedLock->newRequest(request);
        return LOCK_OK;
    }

    ......
}

// Have more buckets than CPUs to reduce contention on lock and caches
const unsigned LockManager::_numLockBuckets(128);

LockManager::LockBucket* LockManager::_getBucket(ResourceId resId) const {
    return &_lockBuckets[resId % _numLockBuckets];
}

каждый bucket середина сохранила <ResourceId,LockHead> изHash карта, удобна для быстрого позиционирования, а хеш-таблица подчиняется bucket серединаиз mutex Замок Защищать。когдаполучать ResourceId переписыватьсяиз LockHead час,Первыйпроходить stdx::lock_guard<SimpleMutex> получать Замок,Сновапроходить LockHead* lock = bucket->findOrInsert(resId) получатьпереписыватьсяиз LockHead。

Язык кода:javascript
копировать
// lock_manager.h

struct LockBucket {
    SimpleMutex mutex;
    typedef stdx::unordered_map<ResourceId, LockHead*> Map;
    Map data;
    LockHead* findOrInsert(ResourceId resId);
};

LockHead да MongoDB середина Замокизвоплощениеобъект,Его краткая структура такова:

Язык кода:javascript
копировать
// lock_manager.cpp

/**
 * There is one of these objects for each resource that has a lock request. Empty objects (i.e.
 * LockHead with no requests) are allowed to exist on the lock manager's hash table.
 */
struct LockHead {
    ......
  
    /**
     * Finish creation of request and put it on the LockHead's conflict or granted queues. Returns
     * LOCK_WAITING for conflict case and LOCK_OK otherwise.
     */
    LockResult newRequest(LockRequest* request) {
        invariant(!request->partitionedLock);
        request->lock = this;

        // New lock request. Queue after all granted modes and after any already requested conflicting modes
        if (conflicts(request->mode, grantedModes) ||
            (!compatibleFirstCount && conflicts(request->mode, conflictModes))) {
            request->status = LockRequest::STATUS_WAITING;

            // Put it on the conflict queue. Conflicts are granted front to back.
            if (request->enqueueAtFront) {
                conflictList.push_front(request);
            } else {
                conflictList.push_back(request);
            }

            incConflictModeCount(request->mode);

            return LOCK_WAITING;
        }

        // No conflict, new request
        request->status = LockRequest::STATUS_GRANTED;

        grantedList.push_back(request);
        incGrantedModeCount(request->mode);

        if (request->compatibleFirst) {
            compatibleFirstCount++;
        }

        return LOCK_OK;
    }

    // Id of the resource which is protected by this lock. Initialized at construction time and does not change.
    ResourceId resourceId;

    // Granted queue
    LockRequestList grantedList;
    uint32_t grantedCounts[LockModesCount];
    uint32_t grantedModes;

    // Conflict queue
    LockRequestList conflictList;
    uint32_t conflictCounts[LockModesCount];
    uint32_t conflictModes;

    ......
};

LockHead да соответствует определенному ResourceIdиз Замокобъект,поддерживать Свсеверно Должен ResourceId из Замокпросить。LockHead середина имеет два важных компонента: ConflictList и GrantList длядва Двусторонняя цепьповерхность,точка Непредставлять СЗамокизждатьобращатьсяочередьтекущий Замокиздержатьочередь,в ConflictList для одного FIFO изочередь, а также ConflictCounts и GrantCounts Приходите, чтобы поддерживать очередь ожидания и удерживает очередь из длины, GrantedModes и ConflictModes ноделатьдля bit-mask Приходитьлоготипкогдавпередочередьсерединажитьсуществовать Замокиз mode тип. для Почему ты это делаешь? Просто представьте, когда есть При поступлении нового запроса необходимо пройти все GrantList элемент серединаиз для обнаружения запроса серединаиз mode иочередьсерединада Конфликта нет, временная сложность при этом невелика. O(n), не эффективно.

Язык кода:javascript
копировать
// псевдокод

def lock(newNode):
  foreach node in GrandList:
    if conflict(node.mode, newNode.mode):
      return ConflictList.add(newNode);
  return GrantList.add(newNode);

для Чтобы решить эту проблему, MongoDB для ConflictList и GrantList Увеличено количество ссылок из существующего массива, добавлен объект в GrantList посередине, в то же время GrantCounts[mode] Накопить, если GrantCounts[mode] да меняется с 0 на 1из, нужно поменять GrantModes переписываться mode из bitMask Набор для1. от GrantList серединаудалитьобъектчасдареверсизверно称Держатьделать。так,GrantCounts[mode] выражает каждый mode переписыватьсяизсуществовать GrantList серединаизколичество,и GrantModes означает текущий GrantList серединаданетжитьсуществоватьпереписываться mode из Замокдержать。Зависит отэтот,существоватьсудить о чем-то mode да Неттока GrantList Когда середина уже конфликтует с объектом, вам нужно только добавить узел из mode и GrantModes серединапереписыватьсяиз bitMask Для сравнения, временная сложность колеблется от O(n) уменьшено до 0(1)。

Язык кода:javascript
копировать
// lock_manager.cpp

uint_32 conflictCounts[LockModesCount];
uint_32 conflictModes;

// Methods to maintain the conflict queue
void incConflictModeCount(LockMode mode) {
    invariant(conflictCounts[mode] >= 0);
    if (++conflictCounts[mode] == 1) {
        invariant((conflictModes & modeMask(mode)) == 0);
        conflictModes |= modeMask(mode); // Вычислить режим из бит-карты, а затем выполнить побитовое присваивание
    }
}

void decConflictModeCount(LockMode mode) {
    invariant(conflictCounts[mode] >= 1);
    if (--conflictCounts[mode] == 0) {
        invariant((conflictModes & modeMask(mode)) == modeMask(mode));
        conflictModes &= ~modeMask(mode); // Вычислите режим из бит-карты, а затем поразрядно инвертируйте и отмените присваивание.
    }
}
    
enum LockMode {
    MODE_NONE = 0,
    MODE_IS = 1,
    MODE_IX = 2,
    MODE_S = 3,
    MODE_X = 4,

    LockModesCount
};
    
uint32_t modeMask(LockMode mode) {
    return 1 << mode; // верно1, режим сдвига влевопредставляет битовую битовую маску
}
    
// Helper functions for the lock modes
bool conflicts(LockMode newMode, uint32_t existingModesMask) {
    return (LockConflictsTable[newMode] & existingModesMask) != 0;
}
    
// Map of conflicts.
static const int LockConflictsTable[] = {
    0, // MODE_NONE
    (1 << MODE_X), // MODE_IS
    (1 << MODE_S) | (1 << MODE_X), // MODE_IX
    (1 << MODE_IX) | (1 << MODE_X), // MODE_S
    (1 << MODE_S) | (1 << MODE_X) | (1 << MODE_IS) | (1 << MODE_IX), // MODE_X
};

Приведенный выше код решает Понятнонамерение Замоксерединаполучатьи Ожидание в очередиизвопрос,и упомянутьвысокий Понятнополучать Замокизэффективность。вернов одном Замокпросить,нравитьсяфруктытекущий GrantList серединаиз Тип запросаниктоконфликт,Сразу Воля Чтодобавлятьдобавлятьприезжать GrantList серединадобавлять Замокуспех,В противном случае добавьте его в ConflictList середина. и подожди grantedModes При изменении из ConflictList середина Выберите партию grantedModes совместимыйиздобавлять Замокпросить Входить Список Грантов. Но есть проблема с вышеуказанной стратегией:

  • Представьте себе следующий сценарий: если ConflictList серединаиметь X Замоксуществоватьждатьобращаться,и GrantList серединаиз IS/IX Замокпросить Источник Нетперерывиз Входить Приходить,Так X Блокировка никогда не будет запланирована, то есть блокировка будет истощена.

для Понятноизбегай этогоэксклюзивный Замокодеялообщий Замокголодатьиз Состояние,существовать ConflictList из FIFO На основе очереди вводится понятие приоритета очереди. Монго БД проходитьдобавлятьдобавлять enqueueAtFront и compatibleFirst этотдвапараметр Приходитьрешатьэксклюзивный Замокголодатьизвопрос。который расположенсуществоватьполучать lock При прохождении из LockRequest середина:

Язык кода:javascript
копировать
// lock_manager_defs.h

/**
 * There is one of those entries per each request for a lock. They hang on a linked list off
 * the LockHead or off a PartitionedLockHead and also are in a map for each Locker. This
 * structure is not thread-safe.
 */
struct LockRequest {
    enum Status {
        STATUS_NEW,
        STATUS_GRANTED,
        STATUS_WAITING,
        STATUS_CONVERTING,

        // Counts the rest. Always insert new status types above this entry.
        StatusCount
    };

    ......

    // If the request cannot be granted right away, whether to put it at the front or at the end of
    // the queue. By default, requests are put at the back. If a request is requested to be put at
    // the front, this effectively bypasses fairness. Default is FALSE.
    bool enqueueAtFront;

    // When this request is granted and as long as it is on the granted queue, the particular
    // resource's policy will be changed to "compatibleFirst". This means that even if there are
    // pending requests on the conflict queue, if a compatible request comes in it will be granted
    // immediately. This effectively turns off fairness.
    bool compatibleFirst;

    ......
};

нравиться,существовать_lockBegin()середина,Сразувстреча Воля Global Уровень из X Настройка блокировки с высоким приоритетом:

Язык кода:javascript
копировать
// lock_state.cpp

MONGO_TSAN_IGNORE
LockResult LockerImpl::_lockBegin(OperationContext* opCtx, ResourceId resId, LockMode mode) {
  ......
  
    // Give priority to the full modes for Global, PBWM, and RSTL resources so we don't stall global
    // operations such as shutdown or stepdown.
    const ResourceType resType = resId.getType();
    if (resType == RESOURCE_GLOBAL) {
        if (mode == MODE_S || mode == MODE_X) {
            request->enqueueAtFront = true;
            request->compatibleFirst = true;
        }
    } else if (resType != RESOURCE_MUTEX) {
        // This is all sanity checks that the global locks are always be acquired
        // before any other lock has been acquired and they must be in sync with the nesting.
        if (kDebugBuild) {
            const LockRequestsMap::Iterator itGlobal = _requests.find(resourceIdGlobal);
            invariant(itGlobal->recursiveCount > 0);
            invariant(itGlobal->mode != MODE_NONE);
        };
    }
    
    ......
}

в enqueueAtFront Параметр определяет, когда сделан текущий запрос. mode В случае конфликта да запросит вставку ConflictList на самом фронтевсе еще в конце установлено enqueueAtFront Параметр запроса будет существовать в ожидании очереди в начале:

Язык кода:javascript
копировать
// lock_manager.cpp

    LockResult newRequest(LockRequest* request) {
        ......

        // New lock request. Queue after all granted modes and after any already requested conflicting modes
        if (conflicts(request->mode, grantedModes) ||
            (!compatibleFirstCount && conflicts(request->mode, conflictModes))) {
            request->status = LockRequest::STATUS_WAITING;

            // Put it on the conflict queue. Conflicts are granted front to back.
            if (request->enqueueAtFront) {
                conflictList.push_front(request);
            } else {
                conflictList.push_back(request);
            }

            incConflictModeCount(request->mode);

            return LOCK_WAITING;
        }
        
        // No conflict, new request
        request->status = LockRequest::STATUS_GRANTED;

        grantedList.push_back(request);
        incGrantedModeCount(request->mode);

        if (request->compatibleFirst) {
            compatibleFirstCount++;
        }
        ......
    }

и compatibleFrist Параметр дафит enqueueAtFront Работайте вместе, чтобы предотвратить истощение эксклюзивных блокировок, как показано в приведенном выше коде:

  • нравитьсяфрукты Замокпроситьтекущий GrantedModes Если есть конфликт, введите ConflictList, чтобы подождать, и на основе enqueueAtFront Чтобы оценить, нужно ли вставить очередь ожидания в начало/хвост очереди;
  • нравитьсяфрукты Замокпроситьтекущий GrantedModes Если конфликта нет, блокировка может оказаться неудачной. Вам все равно необходимо определить ток. GrantList держать Замокизресурссерединаиз совместимыйcFristCount,Прямо сейчас:GrantList середина compatibleFist=true из Замокпроситьизчисло,нравитьсяфрукты GrantList серединаникто complatibleFirst из Замокпросить,ипроситьиз Замок mode текущий ConflictList Если режим запроса серединаиз конфликтует, новый запрос все равно необходимо добавить. ConflictList Ожидание очереди ждет, Когда здесь гарантировано естьэксклюзивный Замоксуществовать ConflictList серединаждатьобращатьсячас,новыйизобщий Замок Нетвстреча Нетперерывиз Входить GrantList получать Замокипривести кэксклюзивный Замокголодать。
  • нравитьсяфруктыполучать Замокуспех,но Воля Замокпроситьдобавлятьвходить GrantList середина,И будет совместимоFristCount++;
  • Теперь существуют, давайте проанализируем Global из X Замок Входитьочередьиз Состояние:
  • Когда есть Global из X Когда запрашивается блокировка, MongoDB Будет ли для текущих настроек запроса enqueueAtFirst=true а также compatibleFirst=true;
  • в это время GrantList серединаиз Замокпросить mode вседля IX/IS тип;
  • Когда запрос поступает, из-за mode и GrantList Конфликт, Глобальный из X Добавлен запрос на блокировку ConflictList очередь подожди, и потому что enqueueAtFirst=true, запрос добавляется непосредственно в ConflictList излидер;
  • Опять что-то новое IX/IS Когда запрос поступает, из-зав это время совместимыйFristCount==0 и запросить IX/IS тип Замоки ConflictList серединаиз Global из X Замоктипконфликт,привести кновыйиз IX/IS Запросы на блокировку все еще приходят ConflictList Конец очереди ждет.
  • Поскольку новые запросы продолжают поступать ConflictList подожди, и Global из X Запрос на блокировку находится по адресу ConflictList из FIFO очередьпервое место,предотвращать Понятноэксклюзивный Замокодеяло Источник Нетперерывизобщий Замокголодать。

Эта статья в основном начинается с MongoDB Введение в медленный журнал, для того, чтобы вы его подробно разобрали MongoDB из Замоки Связанныйвыполнитьвопрос。существовать Следующая статьясередина,мы будемверно MongoDB из Держатьделатьи Замокиспользоватьруководить深входитьиз Разрабатывать,Следите за обновлениями.

-End-

Автор оригинала|Хе Ян

boy illustration
IP-адрес Получить
boy illustration
【Java】Решено: org.springframework.web.HttpRequestMethodNotSupportedException
boy illustration
Native js отправляет запрос на публикацию_javascript отправляет запрос на публикацию
boy illustration
.net PDF в Word_pdf в Word
boy illustration
[Пул потоков] Как Springboot использует пул потоков
boy illustration
Подробное объяснение в одной статье: Как работают пулы потоков
boy illustration
Серия SpringCloud (6) | Поговорим о балансировке нагрузки
boy illustration
IDEA Maven может упаковать все импортное полностью красное решение — универсальное решение.
boy illustration
Последний выпуск 2023 года, самое полное руководство по обучению Spring Boot во всей сети (с интеллект-картой).
boy illustration
[Решено — Практическая работа] SaTokenException: запрос не может быть получен в контексте, отличном от Интернета. Решение проблем — Практическая работа.
boy illustration
HikariPool-1 - Connection is not available, request timed out after 30000ms
boy illustration
Power Query: автоматическое суммирование ежемесячных данных с обновлением одним щелчком мыши.
boy illustration
установка Ubuntu в среде npm
boy illustration
3 Бесплатные системы управления складом (WMS) .NET с открытым исходным кодом
boy illustration
Глубокое погружение в библиотеку Python Lassie: мощный инструмент для автоматизации извлечения метаданных
boy illustration
Объяснение прослушивателя серии Activiti7 последней версии 2023 года
boy illustration
API-интерфейс Jitu Express для электронных счетов-Express Bird [просто для понимания]
boy illustration
Каковы архитектуры микросервисов Java. Серверная часть плавающей области обслуживания
boy illustration
Описание трех режимов жизненного цикла службы внедрения зависимостей Asp.net Core.
boy illustration
Java реализует пользовательские аннотации для доступа к интерфейсу без проверки токена.
boy illustration
Серверная часть Unity добавляет поддержку .net 8. Я еще думал об этом два дня назад, и это сбылось.
boy illustration
Проект с открытым исходным кодом | Самый элегантный метод подписки на публичные аккаунты WeChat на данный момент
boy illustration
Разрешения роли пользователя Gitlab Гость, Репортер, Разработчик, Мастер, Владелец
boy illustration
Spring Security 6.x подробно объясняет механизм управления аутентификацией сеанса в этой статье.
boy illustration
[Основные знания ASP.NET] — Аутентификация и авторизация — Использование удостоверений для аутентификации.
boy illustration
Соединение JDBC с базой данных MySQL в jsp [легко понять]
boy illustration
[Уровень няни] Полный процесс развертывания проекта Python (веб-страницы Flask) в Docker.
boy illustration
6 способов чтения файлов свойств, рекомендуем собрать!
boy illustration
Графическое объяснение этапа строительства проекта IDEA 2021 Spring Cloud (базовая версия)
boy illustration
Подробное объяснение технологии междоменного запроса данных JSONP.