Zipkin — это система распределенной трассировки, которая помогает нам собирать данные о времени выполнения всех разрозненных служб на Twitter. Он управляет сбором и поиском данных через сервисы Collector и Query. Мы проектировали Zipkin по образцу Google Dapper. Подпишитесь на @ZipkinProject
и следите за развитием событий.
И зачем эта распределенная трассировка?
Сбор трассировок помогает разработчикам получить более глубокие знания о том, как определенные запросы выполняются в распределенной системе. Скажем, у нас возникли проблемы с запросами пользователей, допустим, превышение тайм-аута. Мы можем просмотреть трассировки запросов, которые отвалились и показать их в веб-интерфейсе. Мы сможем быстро найти службу, виновную за нежданную прибавку времени на ответ. Если служба была подробно проаннотирована, мы также сможем найти, в каком именно месте сервиса возникла проблема.
Архитектура
Данные компоненты составляют полноценную систему трассировки.
Инструментальные библиотеки
Сведения о трассировке собираются на каждом узле с помощью инструментальных библиотек, которые направляют их на узел с Zipkin. Когда хост делает запрос к другой службе, он передает некоторые трассировочные идентификаторы вместе с запросом, поэтому позже можно связать эти данные воедино.
Finagle
Finagle — асинхронный сетевой стек для JVM, который можно использовать для создания асинхронных клиентов и серверов удаленного вызова процедур (RPC) в Java, Scala или любом другом языке, использующем JVM.
Finagle активно используется внутри Twitter, поэтому и стал очевидной отправной точкой для поддержки трассировки. Пока у нас есть клиент/сервер, поддерживающие Thrift и HTTP, а также клиент поддерживающий только Memcache и Redis.
Чтобы настроить сервер Finagle в Scala, просто выполните следующие действия. Добавление трассировки — это просто добавление зависимости от finagle-zipkin и вызов tracer
в ServerBuilder.
ServerBuilder()
.codec(ThriftServerFramedCodec())
.bindTo(serverAddr)
.name("servicename")
.tracer(ZipkinTracer.mk())
.build(new SomeService.FinagledService(queryService, new TBinaryProtocol.Factory()))
Настройка трассировки для клиентов похожа. После того, как вы задали трассировщик Zipkin как показано выше, небольшая выборка из ваших запросов будет протрассирована автоматически. Мы будем записывать, когда запрос начался, когда закончился, а также участвующие в запросе службы и хосты.
В случае, если вы хотите записать дополнительную информацию можно добавить в код настраиваемую заметку.
Trace.record("starting that extremely expensive computation");
В примере выше заметка в виде строки будет прилагается к тому моменту времени, когда она выполнилась. Можно также добавить заметку по значению ключа. Это выглядит следующим образом:
Trace.recordBinary("http.response.code", "500");
Ruby Thrift
Это gem, который мы используем для трассировки запросов. Для того, чтобы встроить трассировщик и генерировать трассировочные идентификаторы запроса, можно использовать этот gem в RackHandler. Смотрите пример zipkin-web, где мы отслеживаем трассировщики.
Для отслеживания вызовов из Ruby кода мы полагаемся на клиента Twitter Ruby Thrift. См. ниже пример как использовать клиент.
client = ThriftClient.new(SomeService::Client, "127.0.0.1:1234")
client_id = FinagleThrift::ClientId.new(:name => "service_example.sample_environment")
FinagleThrift.enable_tracing!(client, client_id), "service_name")
Querulous
Querulous — это библиотека Scala для взаимодействия с базами данных. Включает в себя отслеживание таймингов запроса и исполнения SQL.
Cassie
Cassie — это основанная на Finagle клиентская библиотека для Cassandra. Настройка трассировщика в Cassie почти совпадает с таковой в Finagle, но в случае Cassie используется KeyspaceBuilder.
cluster.keyspace(keyspace).tracer(ZipkinTracer.mk());
Транспорт
Мы используем Scribe для передачи всех трассировок от различных служб к Zipkin и Hadoop. Scribe был разработан в Facebook, и он состоит из демона, который можно запустить на каждом сервере в вашей системе. Он просматривает лог сообщений и направляет их в правильной сервис-приемник в зависимости от категории.
Демон Zipkin Сollector
Как только данные о трассировке прибывают к демону Collector, мы проверяем их корректность, сохраняем и строим индекс для поиска.
Хранилище
Мы используем Cassandra для хранения данных. Она масштабируемая, имеет гибкую схему и интенсивно используется в Twitter. Мы попробовали сделать этот компонент модульным, так что не должно быть трудно заменить ее чем-то другим.
Демон запросов
После того, как данные записаны и проиндексированы нам нужен удобный способ их извлечения. Здесь на помощь приходит Query daemon, предоставляя пользователям с помощью простого Thrift API возможность поиска и извлечения трассировок. Смотрите пример Thrift-файла.
Интерфейс пользователя
Большинство наших пользователей получают доступ к данным через разработанный нами UI. Это Rails-приложение, которое использует D3 для визуализации данных о трассировке. Обратите внимание, что в пользовательском интерфейсе нет встроенной функции авторизации.
Модули
Установка
Чтобы быстро войти в курс дела, смотрите руководства Ubuntu Quickstart и Mac Quickstart. Они помогут запустить Zipkin на одиночном компьютере, чтобы вы смогли поэкспериментировать с ним.
Немного о настройке.
Cassandra
Zipkin чаще всего используется совместно с Cassandra. Также существует плагин для Redis и мы хотели бы увидеть поддержку для других баз данных.
- Посетите сайт Cassandra для получения инструкций о том, как запустить кластер.
- Используйте адаптированную под Zipkin схему. Создать ее можно следующей коммандой:
cassandra-cli -host localhost -port 9160 -f zipkin-cassandra/src/schema/cassandra-schema.txt
ZooKeeper
Zipkin может использовать ZooKeeper для координации. Это то место, где мы храним эталонные значения и регистрируем сами серверы.
Посетите сайт ZooKeeper для получения инструкции по установке.
Scribe
Scribe это фреймворк для ведения логов, который мы используем в Twitter в качестве транспорта данных о трассировке. Есть несколько других способов передать Zipkin данные трассировки; в частности если вы просто желаете попробовать Zipkin, то можно пропустить этот шаг полностью и направить ZipkinTracer непосредственно на Collector.
Чтобы использовать Scribe с Zipkin, вам нужно создать сетевое хранилище, которое указывается в демоне Collector. Настройка хранилища в Zipkin может выглядеть примерно так:
<store>
category=zipkin
type=network
remote_host=123.123.123.123
remote_port=9410
use_conn_pool=yes
default_max_msg_before_reconnect=50000
allowable_delta_before_reconnect=12500
must_succeed=no
</store>
Если вы не хотите жестко зашивать IP-адрес вашего коллектора, то существует несколько вариантов. Один заключается в использовании внутренней DNS-записи для коллекторов, так что у вас будет одно место, в котором придется изменять адреса при добавлении или удалении коллекторов. Кроме того, можно использовать модифицированную версию Scribe, который забирает адреса коллекторов через ZooKeeper. Когда коллектор запускается, то он регистрирует себя у ZooKeeper, а когда завершает работу — автоматически удаляется. Модифицированный Scribe получает уведомления при изменениях, происходящих с коллекторами. Для активации этого режима измените remote_host
в конфигурации на zk://zookeeper-hostname:2181/scribe/zipkin
или аналогичное.
Серверы Zipkin
Мы разрабатывали Zipkin со Scala 2.9.1, SBT 0.11.2 и JDK7.
Руководства Ubuntu Quickstart и Mac Quickstart объясняют как установить и запустить сервисы Collector и Query.
Zipkin UI
UI это обычное Rails 3 приложение.
- Обновите конфигурация вашего сервера ZooKeeper. Он используется для поиска Query-демонов.
- Разверните подходящий сервер приложений под Rails 3. Для тестирования подойдет и встроенный:
bundle install && bundle exec rails server
Zipkin-tracer gem добавляет трассировку к Rails-приложению через Rack Handler. Добавьте в config.ru:
use ZipkinTracer::RackHandler
run <YOUR_APPLICATION>
Если Rails-приложение само обслуживает static assets, то трассировка этих запросов будет сохранена.
Запуск задачи в Hadoop
Возможно настроить Scribe для хранения данных в Hadoop. Если вы сделаете это, то можете создавать различные отчеты из данных, которые нелегко обработать на лету в самом Zipkin.
Мы используем библиотеку под названием Scalding для формирования заданий для Hadoop из Scala.
- Для запуска задачи в Hadoop в первую очередь нужно собрать jar.
sbt 'project zipkin-hadoop' compile assembly
- Измените в scald.rb имя узла, на который вы хотите скопировать jar и запустить задание.
- Обновите версию jar в scald.rb, в случае необходимости.
- Теперь можно запустить задание, используя наш скрипт scald.rb.
./scald.rb --hdfs com.twitter.zipkin.hadoop.[classname] --date yyyy-mm-ddThh:mm yyyy-mm-ddThh:mm --output [dir]
Как подготовить библиотеки
Мы проинструментировали несколько библиотек и протоколов самостоятельно, но все же надеемся получить некоторую помощь в расширении их количества. Прежде чем мы начнем, мы должны знать немного вещей о том, как структурируются данные о трассировке.
- Annotation — включает в себя какое-то значение, временную отметку и адрес узла;
- Span — набор Annotation, которые соответствуют определенному RPC;
- Trace — множество Span, которые разделяют один корневой Span.
Вышеуказанное используется при отправке данных трассировки в Zipkin. Некоторые подробности описаны здесь.
Еще одной важной частью трассировки является легковесный заголовок, который мы используем для передачи информации между службами. Заголовок состоит из:
- Trace Id — идентифицирует трассировку;
- Span Id — идентификаторы отдельных запросов;
- Необязательный Parent Span Id — добавляется, если этот запрос был сделан в рамках другого;
- Sampled (логическое поле) — говорит нам, следует ли хранить данные трассировки или нет.
Теперь, когда мы знаем немного о типах данных, давайте рассмотрим шаг за шагом как работает инструментирование. В примере ниже опишу, как работает трассировка HTTP в Finagle. Для других библиотек и протоколов, действия, конечно, могут отличаться, но общий принцип сохраняется.
На стороне сервера
- Проверьте, есть ли заголовки трассировки во входящем запросе. Если есть, мы принимаем идентификаторы, связанные с этим запросом. Если нет, то мы генерируем Trace id, Span id и решаем, сохранять данные или нет. См. HttpServerTracingFilter в качестве примера.
- Если текущий запрос участвует в выборке, то мы собираем информацию, такую как имя службы, имя хоста, имя Span (http get/put к примеру) и непосредственно нашу заметку. Мы создаем заметки «server received», когда мы получили запрос и «server send» когда мы завершили обработку и собираемся отправить результат. Опять же вы можете увидеть пример в HttpServerTracingFilter.
- Созданные данные трассировки передаются объекту tracer, который был указан в ServerBuilder. Это может быть, например, ConsoleTracer, но в нашем случае используется ZipkinTracer. Когда ZipkinTracer получает данные трассировки, то группирует их по Span Id.
- Как только ZipkinTracer получает событие «end of span», такое как заметка «server received» или тайм-аут, он направляет к Scribe агрегированные данные в виде Thrift-структуры. Если такого события не происходит, данные все равно будут переданы. Мы открыты для добавления других способов доставки данных, для нас имеет смысл Thrift и Scribe, но возможно JSON и Http будет работать лучше для некоторых случаев.
На стороне клиента
- Перед выполнением запроса, выясните, являемся ли мы уже частью трассировки. Может быть, этот клиент используется в пределах сервера и Trace Id уже назначен. Мы повторно использовать этот Trace id, но создаем новый Span Id для нового запроса. Мы также устанавливаем Parent Span Id предыдущего Span, если таковой имеется. Некоторые примеры здесь и здесь.
- Аналогично серверному варианту, у нас есть HttpClientTracingFilter, который добавляет трассировочные заголовки к исходящему Http запросу.
- Мы также должны создать соответствующие заметки, такие как «client send» перед запросом и «client receive» после получения ответа от сервера.
- Аналогично серверному варианту, данные передаются ZipkinTracer, который отправляет их к Zipkin.
Автор: Lucyfer