Примечание куратора: содержание этой статьи изначально было написано Майклом Нитчингером здесь: http://nitschinger.at/
мотивация
Этот пост предназначен для очень подробной и информативной статьи для тех, кто уже использовал Couchbase Java SDK и хочет знать, как работают внутренние компоненты. Это не введение в то, как использовать Java SDK, и мы рассмотрим некоторые довольно сложные темы.
Обычно, говоря о SDK, мы имеем в виду все, что нужно для работы (клиентская библиотека, документация, заметки о выпуске и т. Д.). В этой статье SDK ссылается на клиентскую библиотеку (код), если не указано иное.
Вступление
Прежде всего, важно понимать, что SDK обертывает и расширяет функциональность библиотеки memcached spymemcached (называемой «spy»). Одним из внутренних протоколов является протокол memcached, и многие функции можно использовать повторно. С другой стороны, как только вы начнете снимать первые слои SDK, вы заметите, что некоторые компоненты несколько сложнее из-за того факта, что шпион предоставляет больше возможностей, чем нужно SDK. Другая часть состоит в том, чтобы помнить, что многие компоненты переплетены, поэтому вы всегда должны правильно понимать зависимость. Большую часть времени мы выпускаем новую версию шпиона в тот же день с новым SDK, потому что новый материал был добавлен или исправлен.
Таким образом, помимо повторного использования функциональности, предоставляемой шпионом, SDK в основном добавляет два блока функциональности: автоматическое управление топологией кластера и начиная с версии 1.1 (и сервера 2.0) для представлений. Помимо этого он также предоставляет административные средства, такие как ведение и управление проектной документацией.
Чтобы понять, как работает клиент, мы разберем весь процесс на разных этапах жизненного цикла клиента. После того, как мы пройдем все три этапа (начальная загрузка, работа и отключение), у вас должно быть четкое представление о том, что происходит под капотом. Обратите внимание, что в процессе подготовки к обработке ошибок есть отдельная запись в блоге, поэтому мы не будем здесь подробно останавливаться на ней (которая будет опубликована через несколько недель в том же блоге здесь).
Фаза 1: Бутстрап
Прежде чем мы сможем начать обслуживать такие операции, как get () и set () , нам нужно загрузить объект CouchbaseClient . Важная часть, которую нам необходимо выполнить, — это сначала получить конфигурацию кластера (которая содержит узлы и карту vBucket), а также установить потоковое соединение для получения обновлений кластера в (почти) режиме реального времени.
Мы берем список узлов, проходящих во время начальной загрузки, и перебираем его. Первый узел в списке, с которым можно связаться через порт 8091, используется для обхода интерфейса RESTful на сервере. Если он недоступен, будет испробован следующий. Это означает, что, исходя из предоставленного
http: // host: port / pool URI, мы в конечном итоге перейдем по ссылкам на сущность корзины. Все это происходит внутри
ConfigurationProvider , который в данном случае является
com.couchbase.client.vbucket.ConfigurationProviderHTTP . Если вы хотите покопаться во внутренних
органах, поищите методы getBucketConfiguration и
readPools .
(Успешная) прогулка может быть проиллюстрирована следующим образом:
- GET / бассейны
- ищите пулы «по умолчанию»
- GET / pool / default
- ищите хеш «buckets», который содержит список bucket
- GET / pool / default / buckets
- разобрать список ведер и извлечь тот, который предоставляется приложением
- GET / pool / default / buckets /
Теперь мы находимся в конечной точке REST, которая нам нужна. В этом ответе JSON вы найдете все полезные детали, которые также используются SDK для внутреннего использования (например, streamingUri , node и vBucketServerMap ). Конфиг анализируется и сохраняется. Прежде чем мы продолжим, давайте быстро обсудим странную часть пулов в нашей прогулке по REST:
Концепция пула ресурсов для группировки сегментов была разработана для Couchbase Server, но в настоящее время не реализована. Тем не менее, REST API реализован таким образом, и поэтому все SDK поддерживают его. Тем не менее, хотя теоретически мы могли бы просто перейти непосредственно к
/ pools / default / buckets и пропустить первые несколько запросов, текущее поведение является будущим, поэтому вам не придется изменять загрузочный код, как только сервер его реализует.
Вернемся к нашей фазе начальной загрузки. Теперь, когда у нас есть действующая конфигурация кластера, которая содержит все узлы (и их имена хостов или IP-адреса), мы можем устанавливать с ними соединения. Помимо установления подключений к данным, нам также необходимо создать потоковое подключение к одному из них. По причинам простоты мы просто устанавливаем потоковое соединение с узлом из списка, в котором мы получили нашу первоначальную конфигурацию.
Это подводит нас к важному моменту, о котором следует помнить: если у вас много
объектов CouchbaseClient, запущенных на многих узлах, и все они загружаются с одним и тем же списком, они могут в конечном итоге подключиться к одному узлу для потокового соединения и создать возможное узкое место. Поэтому, чтобы распределить нагрузку немного лучше, я рекомендую перетасовать массив перед его передачей в
объект CouchbaseClient . Когда у вас есть только несколько
объектов CouchbaseClient, подключенных к вашему кластеру, это не будет проблемой вообще.
URI потокового соединения берется из конфигурации, которую мы получили ранее, и обычно выглядит так:
streamingUri: "/pools/default/bucketsStreaming/default?bucket_uuid=88cae4a609eea500d8ad072fe71a7290"
Если вы укажете свой браузер на этот адрес, вы также получите обновления топологии кластера в режиме реального времени. Поскольку потоковое соединение необходимо устанавливать постоянно и потенциально блокировать поток, это делается в фоновом режиме, обрабатываемом различными потоками. Для этой задачи мы используем инфраструктуру NIO Netty, которая обеспечивает очень удобный способ работы с асинхронными операциями. Если вы хотите начать копаться в этой части, имейте в виду, что все операции чтения полностью отделены от операций записи, поэтому вам нужно иметь дело с обработчиками, которые заботятся о том, что возвращается с сервера. Помимо некоторой проводки, необходимой для Netty, бизнес-логику можно найти в com.couchbase.client.vbucket.BucketMonitor и com.couchbase.client.vbucket.BucketUpdateResponseHandler.Мы также пытаемся восстановить это потоковое соединение, если сокет закрывается (например, если этот узел перебалансирован из кластера).
Чтобы фактически перетасовать данные на узлы кластера, нам нужно открыть для них различные сокеты. Обратите внимание, что внутри клиента абсолютно не требуется пул соединений, потому что мы активно управляем всеми сокетами. Помимо специального потокового соединения с одним из серверов (которое открыто для порта 8091), нам нужно открыть следующие соединения:
- Разъем Memcached: порт 11210
- Просмотр сокета: порт 8092
Обратите внимание, что порт 11211 не используется внутри клиентских SDK, но используется для подключения общих клиентов memcached, которые не поддерживают кластер. Это означает, что эти общие клиенты не получают обновленные топологии кластера.
Так что, как правило, если у вас работает кластер из 10 узлов, один объект CouchbaseClient откроет около 21 (2 * 10 + 1) клиентских сокетов. Они управляются напрямую, поэтому, если узел будет удален или добавлен, номера изменятся соответственно.
Теперь, когда все сокеты открыты, мы готовы выполнять обычные кластерные операции. Как видите, при загрузке объекта CouchbaseClient возникает много накладных расходов. В связи с этим мы настоятельно рекомендуем вам не создавать новый объект при каждом запросе или запускать множество объектов CouchbaseClient на одном сервере приложений. Это только добавляет ненужные накладные расходы и нагрузку на сервер приложений, а также увеличивает общее количество сокетов, открытых для кластера (что приводит к возможной проблеме производительности).
Для справки: при включенном ведении журнала на уровне INFO подключение и отключение кластера с 1 узлом (Couchbase Bucket) должны выглядеть следующим образом:
Apr 17, 2013 3:14:49 PM com.couchbase.client.CouchbaseProperties setPropertyFile INFO: Could not load properties file "cbclient.properties" because: File not found with system classloader. 2013-04-17 15:14:49.656 INFO com.couchbase.client.CouchbaseConnection: Added {QA sa=/127.0.0.1:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue 2013-04-17 15:14:49.673 INFO com.couchbase.client.CouchbaseConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@2adb1d4 2013-04-17 15:14:49.718 INFO com.couchbase.client.ViewConnection: Added localhost to connect queue 2013-04-17 15:14:49.720 INFO com.couchbase.client.CouchbaseClient: viewmode property isn't defined. Setting viewmode to production mode 2013-04-17 15:14:49.856 INFO com.couchbase.client.CouchbaseConnection: Shut down Couchbase client 2013-04-17 15:14:49.861 INFO com.couchbase.client.ViewConnection: Node localhost has no ops in the queue 2013-04-17 15:14:49.861 INFO com.couchbase.client.ViewNode: I/O reactor terminated for localhost
INFO: Could not load properties file "cbclient.properties" because: File not found with system classloader. 2013-04-17 15:16:44.295 INFO com.couchbase.client.CouchbaseConnection: Added {QA sa=/192.168.56.101:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue 2013-04-17 15:16:44.297 INFO com.couchbase.client.CouchbaseConnection: Added {QA sa=/192.168.56.102:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue 2013-04-17 15:16:44.298 INFO com.couchbase.client.CouchbaseConnection: Added {QA sa=/192.168.56.103:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue 2013-04-17 15:16:44.298 INFO com.couchbase.client.CouchbaseConnection: Added {QA sa=/192.168.56.104:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue 2013-04-17 15:16:44.306 INFO com.couchbase.client.CouchbaseConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@38b5dac4 2013-04-17 15:16:44.313 INFO com.couchbase.client.CouchbaseClient: viewmode property isn't defined. Setting viewmode to production mode 2013-04-17 15:16:44.332 INFO com.couchbase.client.CouchbaseConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@69945ce 2013-04-17 15:16:44.333 INFO com.couchbase.client.CouchbaseConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@6766afb3 2013-04-17 15:16:44.334 INFO com.couchbase.client.CouchbaseConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@2b2d96f2 2013-04-17 15:16:44.368 INFO net.spy.memcached.auth.AuthThread: Authenticated to 192.168.56.103/192.168.56.103:11210 2013-04-17 15:16:44.368 INFO net.spy.memcached.auth.AuthThread: Authenticated to 192.168.56.102/192.168.56.102:11210 2013-04-17 15:16:44.369 INFO net.spy.memcached.auth.AuthThread: Authenticated to 192.168.56.101/192.168.56.101:11210 2013-04-17 15:16:44.369 INFO net.spy.memcached.auth.AuthThread: Authenticated to 192.168.56.104/192.168.56.104:11210 2013-04-17 15:16:44.490 INFO com.couchbase.client.CouchbaseConnection: Shut down Couchbase client
Этап 2. Операции
Когда SDK загружен, он позволяет вашему приложению выполнять операции с подключенным кластером. Для этого поста в блоге нам необходимо различать операции, выполняемые в отношении стабильного кластера, и операции в кластере, который в настоящее время испытывает некоторую форму изменения топологии (будь то запланированное из-за добавления узлов или незапланированное из-за сбоя узла). ). Давайте сначала займемся обычными операциями.
Операции против стабильного кластера
Хотя в SDK это не видно напрямую, внутри SDK мы должны различать операции memcached и операции View. Все операции, имеющие уникальный ключ в сигнатуре метода, могут обрабатываться как операции memcached. Все они в конечном итоге попадают через шпиона. С другой стороны, операции просмотра полностью реализованы внутри самого SDK.
Операции View и memcached являются асинхронными. Внутри шпиона есть один поток (вызов потока ввода-вывода), предназначенный для работы с операциями ввода-вывода. Обратите внимание, что в средах с высоким трафиком весьма обычно то, что этот поток всегда активен. Он использует неблокирующие механизмы Java NIO для работы с трафиком и использует циклы вокруг «селекторов», которые получают уведомление, когда данные могут быть либо записаны, либо прочитаны. Если вы профилируете свое приложение, вы увидите, что этот поток тратит большую часть своего времени на ожидание метода select, это означает, что он находится в режиме ожидания и ожидает уведомления о новом трафике. Концепции, используемые внутри шпиона для решения этой проблемы, являются общими знаниями Java NIO, поэтому вы можете сначала изучить
внутреннюю часть NIO, прежде чем копаться в этом пути кода. Хорошей отправной точкой являются
Классы net.spy.memcached.MemcachedConnection и
net.spy.memcached.protocol.TCPMemcachedNodeImpl . Обратите внимание, что внутри SDK мы переопределяем MemcachedConnection, чтобы подключить нашу собственную логику реконфигурации. Этот класс можно найти внутри SDK по адресу
com.couchbase.client.CouchbaseConnection и для блоков типа memcached в
com.couchbase.client.CouchbaseMemcachedConnection .
Поэтому, если выполняются операции memcached (например,
get () ), они передаются до тех пор, пока не достигнут потока ввода-вывода. Затем поток ввода-вывода поместит его в очередь записи в направлении своего целевого узла. Это в конечном итоге записывается, а затем поток ввода-вывода добавляет информацию в очередь чтения, чтобы ответы могли отображаться соответственно. Этот подход основан на фьючерсах, поэтому, когда результат действительно приходит, будущее помечается как завершенное, результат анализируется и присоединяется как объект.
SDK использует только двоичный протокол memcached, хотя шпион также поддерживает ASCII. Бинарный формат намного эффективнее, и некоторые из расширенных операций реализованы только там.
Вы можете задаться вопросом, как SDK знает, куда отправить операцию? Поскольку у нас уже есть современная карта кластеров, мы можем хэшировать ключ, а затем на основе списка узлов и vBucketMap определить, к какому узлу получить доступ. VBucketMap содержит не только информацию для главного узла массива, но также информацию от нуля до трех узлов реплики. Посмотрите на этот (сокращенный) пример:
vBucketServerMap: { hashAlgorithm: "CRC", numReplicas: 1, serverList: [ "192.168.56.101:11210", "192.168.56.102:11210" ], vBucketMap: [ [0,1], [0,1], [0,1], [1,0], [1,0], [1,0] //..... },
com.couchbase.client.ViewNode once it has free connections and then executed. The result is also handled through futures. To implement this functionality, the SDK uses the third party Apache HTTP Commons (NIO) library.
The whole View API hides behind port 8092 on every node and is very similar to CouchDB. It also contains a RESTful API, but the structure is a little bit different. For example, you can reach a design document at /_design/. It contains the View definitions in JSON:
{ language: "javascript", views: { all: { map: "function (doc) { if(doc.type == "city") {emit([doc.continent, doc.country, doc.name], 1)}}", reduce: "_sum" } } }
{"total_rows":9,"rows":[ {"id":"city:shanghai","key":["asia","china","shanghai"],"value":1}, {"id":"city:tokyo","key":["asia","japan","tokyo"],"value":1}, {"id":"city:moscow","key":["asia","russia","moscow"],"value":1}, {"id":"city:vienna","key":["europe","austria","vienna"],"value":1}, {"id":"city:paris","key":["europe","france","paris"],"value":1}, {"id":"city:rome","key":["europe","italy","rome"],"value":1}, {"id":"city:amsterdam","key":["europe","netherlands","amsterdam"],"value":1}, {"id":"city:new_york","key":["north_america","usa","new_york"],"value":1}, {"id":"city:san_francisco","key":["north_america","usa","san_francisco"],"value":1} ] }
com.couchbase.client.protocol.views namespace. You’ll find abstract classes and interfaces like ViewResponse in there, and then their special implementations like ViewResponseNoDocs, ViewResponseWithDocs or ViewResponseReduced. It also makes a different if setIncludeDocs() is used on the Query object, because the SDK also needs to load the full documents using the memcached protocol behind the scenes. This is also done while parsing the Views.