Точная разработка плагинов

Java задняя часть API maven

1. Запустите систему Pinpoint

Самый простой способ запустить систему Pinpoint — использовать Docker.

# 运行 Pinpoint
$ git clone https://github.com/dawidmalina/docker-pinpoint
$ cd docker-pinpoint
$ docker-compose up -d

Во-вторых, создайте среду компиляции

Для компиляции исходного кода Pinpoint 1.5.2 требуется поддержка JDK 6, JDK 7+ и Maven 3.2.x+.Последняя версия инструментов компиляции, отвечающая вышеуказанным требованиям, указана ниже:

Требовать Последняя версия Примечание
JDK 6 JDK 6u45 перестал обновлять
JDK 7+ JDK 8u112
Maven 3.2.x+ Maven 3.2.9 Используется Maven 3.2.x, предположительно потому, что только Maven 3.2.x поддерживает JDK 6.

А также установить две переменные среды:

JAVA_6_HOME
а также
JAVA_7_HOME
Укажите соответствующий каталог установки JDK, а затем выполните следующие команды для завершения компиляции:

# 编译 Pinpoint
$ git clone https://github.com/naver/pinpoint
$ cd pinpoint
$ mvn install -Dmaven.test.skip=true

С помощью Docker проще запускать сборки, что устраняет необходимость в настройке среды. Сначала загрузите исходный код Pinpoint в локальный каталог, например./projects/pinpoint, затем выполните команду:

# 使用 Docker 编译 Pinpoint
$ docker run -v /projects/pinpoint:/pinpoint:rw -v </path/to/.m2>:/root/.m2:rw tangrui/pinpoint-development

двое из них-vПараметры используются для сопоставления каталогов. Первый-vпараметр должен быть локальным/projects/pinpointкаталог сопоставляется с контейнером/pinpointкаталог; а второй-vПараметр должен сопоставить локальный репозиторий Maven с контейнером./root/.m2Цель этого — позволить локальному репозиторию обмениваться контентом с репозиторием в Docker, избежать загрузки большого количества пакетов зависимостей из сети при каждой компиляции и повысить эффективность работы.

3. Технический обзор

3.1 Архитектурная композиция

Pinpoint 架构

Pinpoint в основном состоит из трех компонентов плюс база данных Hbase, три компонента: агент, сборщик и веб-интерфейс.

3.2. Функции системы

  1. Распределенное отслеживание транзакций, отслеживание сообщений, проходящих через распределенные системы.
  2. Автоматическое определение топологии приложения для определения конфигурации приложения.
  3. Горизонтальное масштабирование для поддержки больших групп серверов
  4. Обеспечивает видимость на уровне кода, чтобы легко выявлять точки сбоя и узкие места.
  5. Используя технологию внедрения байт-кода, новые функции могут быть добавлены без изменения кода.

3.3 Как работает распределенная система трассировки

И Pinpoint, и Zipkin основаны на статье Google Dapper. Основная идея состоит в том, чтобы записывать и передавать тег уровня приложения, когда каждый сервисный узел вызывает друг друга, и этот тег можно использовать для связывания отношений между каждым сервисным узлом. Например, если в качестве протокола запроса между двумя узлами используется HTTP, то эти теги будут добавлены в заголовок HTTP. Поэтому то, как передавать эти теги, связано с протоколом связи, используемым между узлами.Некоторые протоколы легко добавляют такой контент, но некоторые протоколы относительно сложны или даже невозможны, поэтому это напрямую определяет реализацию распределенной системы трассировки.

3.4 Структура данных Pinpoint

Структура данных сообщений Pinpoint в основном включает три типа Span, Trace и TraceId.

Span — это самая основная единица отслеживания вызовов.Когда поступает удаленный вызов, Span относится к заданию, которое обрабатывает вызов и передает данные отслеживания. Для обеспечения видимости на уровне кода Span также содержит слой структуры данных SpanEvent под ним. Каждый Span содержит SpanId.

Трассировка — это набор взаимосвязанных коллекций Span.Span в одной и той же Trace имеют общий TransactionId и будут организованы в иерархическую древовидную структуру в соответствии с SpanId и ParentSpanId.

TraceId представляет собой комбинацию TransactionId, SpanId и ParentSpanId. TransactionId (TxId) — это идентификатор для отправки и получения сообщений по всей распределенной системе в рамках транзакции, который должен быть глобально уникальным во всей группе серверов. То есть TransactionId идентифицирует всю цепочку вызовов; SpanId (SpanId) — это идентификатор задания удаленного вызова, который генерируется, когда вызов достигает узла; ParentSpanId (pSpanId), как следует из названия, — это идентификатор вызывающий Span, сгенерировавший текущий Span. Если узел является первоначальным инициатором транзакции, его ParentSpanId равен -1, чтобы указать, что это корневой диапазон всей транзакции. На следующем рисунке можно более наглядно проиллюстрировать взаимосвязь между этими структурами идентификаторов.

Pinpoint ID 结构之间的关系

3.5 Как использовать Java-агент

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

# 使用 Java Agent
-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar
-Dpinpoint.agentId=<Agent's UniqueId>
-Dpinpoint.applicationName=<The name indicating a same service (AgentId collection)>

3.6 Как работает внедрение кода

Pinpoint 字节码注入

Инкапсуляция внедрения кода в Pinpoint очень похожа на АОП. Когда класс загружается, он вводит логику до и после в указанный метод через Interceptor. В этой логике можно получить статус работы системы и создать сообщения трассировки. через TraceContext и отправляется на сервер Pinpoint. Но в отличие от АОП, Pinpoint больше учитывает взаимодействие с целевым кодом при инкапсуляции интерфейса, поэтому написание логики внедрения с помощью API, предоставляемого Pinpoint, будет выглядеть проще и профессиональнее, чем АОП. (Они будут объяснены более подробно позже)

3.7 Пример применения Pinpoint

На рисунке ниже показаны данные трассировки, собранные после применения Pinpoint к двум серверам Tomcat.

Pinpoint 数据收集

4. Разработка плагина агента

Чтобы разработать подключаемый модуль агента Pinpoint, вам нужно сосредоточиться только на двух интерфейсах: TraceMetadataProvider и ProfilerPlugin.Класс реализации загружается через механизм обнаружения служб Java.

4.1, конфигурация ServiceLoader

Плагины Pinpoint развернуты в виде пакетов jar.Чтобы агент Pinpoint мог найти реализацию интерфейсов TraceMetadataProvider и ProfilerPlugin,META-INF/servicesСоздайте два файла в каталоге:

META-INF/services/com.navercorp.pinpoint.common.trace.TraceMetadataProvider
META-INF/services/com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin

Каждая строка в этих двух файлах может содержать полное имя соответствующего класса реализации.

4.2. Провайдер трассировки метаданных

TraceMetadataProvider обеспечивает управление ServiceType и AnnotationKey.

4.2.1 Тип услуги

Каждый Span и SpanEvent содержит ServiceType, который определяет, к какой библиотеке они принадлежат (например, Jetty, MySQL JDBC Client или Apache HTTP Client и т. д.), и как должны обрабатываться службы отслеживания Span и SpanEvent этого типа. Структура данных ServiceType выглядит следующим образом:

Атрибуты описывать
name Имя ServiceType, которое должно быть уникальным.
code Кодировка ServiceType, короткое целое, должна быть уникальной.
desc описывать
properties Дополнительные свойства

Чтобы максимально сжать размер пакета данных от Агента к Коллектору, Пинпоинт предназначен для отправки ServiceType не в виде сериализованной строки, а в виде целого числа (кодовое поле). Это требует установления отношения сопоставления для преобразования кода в соответствующий экземпляр ServiceType, этот механизм сопоставления отвечает за TraceMetadataProvider.

Код ServiceType должен быть глобально уникальным. Во избежание конфликтов Pinpoint официально управляет этой таблицей сопоставления. Если разработанный подключаемый модуль хочет объявить новое отношение сопоставления, он должен уведомить команду Pinpoint, чтобы эта таблица сопоставления могла быть обновил и выпустил.. Как и в случае с сегментом частных IP-адресов, команда Pinpoint также резервирует частную область для использования при разработке внутренних сервисов. Справочная таблица конкретных диапазонов идентификаторов выглядит следующим образом:

Код ServiceType полный диапазон

Типы Сфера
Internal Use 0 ~ 999
Server 1000 ~ 1999
DB Client 2000 ~ 2999
Cache Client 8000 ~ 8999
RPC Client 9000 ~ 9999
Others 5000 ~ 7999

Область частной области ServiceType Code

Типы Сфера
Server 1900 ~ 1999
DB Client 2900 ~ 2999
Cache Client 8900 ~ 8999
RPC Client 9900 ~ 9999
Others 7500 ~ 7999

4.2.2. Ключ аннотации

Annotation — это более подробные данные, содержащиеся в Span и SpanEvent, в виде пары ключ-значение, ключ — AnnotationKey, а значение может быть строкой или массивом байтов. В Pinpoint много встроенных AnnotationKey, если их недостаточно, вы также можете настроить их через TraceMetadataProvider. Структура данных AnnotationKey выглядит следующим образом:

Атрибуты описывать
name Имя AnnotationKey
code Кодировка AnnotationKey, целое число, должно быть уникальным
properties Дополнительные свойства

Как и поле кода ServiceType, поле кода AnnotationKey глобально уникально, а частная область, заданная командой Pinpoint, находится в диапазоне от 900 до 999.

4.2.3, интерфейс TraceMetadataProvider

Интерфейс TraceMetadataProvider имеет только один метод настройки, который получает параметр типа TraceMetadataSetupContext, который имеет три метода:

метод описывать
addServiceType(ServiceType) Зарегистрировать тип службы
addServiceType(ServiceType, AnnotationKeyMatcher) Зарегистрируйте ServiceType и используйте AnnotationKey, соответствующий AnnotationKeyMatcher, в качестве типичной аннотации для этого ServiceType, которая будет отображаться в столбце Argument каскадного представления.
addAnnotationKey(AnnotationKey) Зарегистрируйте AnnotationKey, AnnotationKey, зарегистрированный здесь, будет помечен как VIEW_IN_RECORD_SET, отображен в отдельной строке в виде водопада и имеет синий значок i перед ним.

Подробное использование см. в официальном образце файла.SampleTraceMetadataProvider.

4.3 Плагин профилировщика

ProfilerPlugin перехватывает целевой код посредством внедрения байт-кода для сбора данных трассировки.

4.3.1, принцип работы плагина

  1. Агент Pinpoint запускается с JVM
  2. Агент загружает всеpluginплагины в каталоге
  3. Агент вызывает метод ProfilerPlugin.setup(ProfilerPluginSetupContext) каждого загруженного плагина.
  4. В методе настройки плагин определяет классы, которые необходимо преобразовать, и регистрирует TransformerCallback.
  5. целевое приложение запускается
  6. Когда класс загружен, агент Pinpoint будет искать TransformerCallback, зарегистрированный в классе.
  7. Если TransformerCallback зарегистрирован, Агент вызывает свой метод doInTransform.
  8. TransformerCallback изменяет байт-код целевого класса (например, добавляет перехватчики, поля и т. д.).
  9. Измененный код возвращается в JVM, и измененный байт-код используется при загрузке типа.
  10. приложение продолжается
  11. Когда вызывается модифицированный метод, вызываются методы перехватчика до и после
  12. Перехватчик записывает данные отслеживания

Принцип работы плагина Pinpoint кажется очень похожим на АОП, но все же есть некоторые отличия и свои особенности:

  1. Поскольку сценарии инъекций, которые должен обрабатывать Пинпойнт, относительно просты, API инъекций, который он предоставляет, относительно прост, в то время как АОП спроектирован очень сложным для обработки различных возможных аспектов.
  2. Перехват подключаемого модуля Pinpoint реализован с помощью методов перехватчика до и после, как и аспект вокруг.Если вы не хотите выполнять один из этих методов, вы можете игнорировать его через аннотацию @IgnoreMethod.
  3. Перехватчики Pinpoint могут произвольно перехватывать методы, поэтому между перехваченными методами может быть взаимосвязь вызова, что приведет к повторному сбору данных отслеживания, поэтому Pinpoint предоставляет функции Scope и ExecutionPolicy. В Scope может быть определена политика выполнения перехватчика: выполняется ли он каждый раз (ExecutionPolicy.ALWAYS), выполняется, когда внешний перехватчик не существует (ExecutionPolicy.BOUNDARY), или должен быть перехвачен внешним уровнем. устройство существует (ExecutionPolicy.INTERNAL). Пожалуйста, обратитесь к этомуОбразец.
  4. Перехватчики внутри Scope также могут передавать данные друг другу. Перехватчики в одной и той же области видимости совместно используют объект InterceptorScopeInvocation, который можно использовать для обмена данными, см.Образец.
  5. В дополнение к методам перехвата Pinpoint также может внедрять поля и методы получения и установки в целевой класс, которые можно использовать для хранения некоторых контекстных данных.

Из приведенного выше содержания мы можем понять, что если вы хотите написать подключаемый модуль Pinpoint, в дополнение к более глубокому пониманию логики вызова целевого кода, вы также должны спроектировать, как хранить и передавать данные контекста, и как чтобы избежать повторного сбора информации через Scope и другие вопросы. Эти проблемы также существуют в сценариях АОП, но Pinpoint предлагает более последовательное и удобное решение, и реализации на основе АОП должны учитывать эти проблемы сами по себе.

4.3.2, метод перехвата

Как упоминалось ранее, подключаемый модуль Pinpoint должен реализовать интерфейс ProfilerPlugin, который имеет только один метод настройки (ProfilerPluginSetupContext). Чтобы упростить работу с API внедрения кода Pinpoint, вам также необходимо реализовать интерфейс TransformTemplateAware, который будет внедрять класс TransformTemplate.

public class SamplePlugin implements ProfilerPlugin, TransformTemplateAware {

  private TransformTemplate transformTemplate;

  @Override
  public void setup(ProfilerPluginSetupContext context) {
  }

  @Override
  public void setTransformTemplate(TransformTemplate transformTemplate) {
    this.transformTemplate = transformTemplate;
  }

}

ProfilerPluginSetupContext имеет два метода: getConfig() и addApplicationTypeDetector(ApplicationTypeDetector…). Первый метод используется для получения объекта ProfilerConfig, который содержит информацию о конфигурации всех подключаемых модулей, а второй метод используется для добавления ApplicationTypeDetector. ApplicationTypeDetector используется для автоматического определения типа службы, запущенной на узле. Например, в проекте pinpoint-tomcat-plugin есть класс TomcatDetector, функция этого класса — определить, является ли текущая служба Tomcat, посредством следующего логического обнаружения:

  1. Проверьте, является ли основной класс org.apache.catalina.startup.Bootstrap
  2. Проверить системные переменные
    catalina.home
  3. Проверьте, существует ли указанный класс (для обнаружения Tomcat этот конкретный тип также является org.apache.catalina.startup.Bootstrap)

Если три вышеуказанных условия соблюдены, установите для ServiceType текущего узла значение Tomcat.

TransformTemplate имеет только один метод transform(String, TransformCallback), первый параметр — полное имя преобразуемого класса, а второй параметр — интерфейс TransformCallback, упомянутый в главе 4.3.1, этот интерфейс также имеет только один метод doInTransform. , где выполняется вся логика внедрения.

public byte[] doInTransform(Instrumentor instrumentor,
        ClassLoader classLoader,
        String className,
        Class<?>; classBeingRedefined,
        ProtectionDomain protectionDomain,
        byte[] classfileBuffer) throws InstrumentException {

  // 1. Get InstrumentClass of the target class
  InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);

  // 2. Get InstrumentMethod of the target method.
  InstrumentMethod targetMethod = target.getDeclaredMethod("targetMethod", "java.lang.String");

  // 3. Add interceptor. The first argument is FQN of the interceptor class,
  // followed by arguments for the interceptor's constructor.
  targetMethod.addInterceptor("com.navercorp.pinpoint.bootstrap.interceptor.BasicMethodInterceptor", va(SamplePluginConstants.MY_SERVICE_TYPE));

  // 4. Return resulting byte code.
  return target.toBytecode();

}
  1. Процесс внедрения начинается с получения класса InstrumentClass.
  2. Если вы хотите перехватить метод или добавить поля и методы получения и установки, вы можете вызвать для этого API, соответствующий InstrumentClass.Вот метод, чья сигнатура — targetMethod(String), а возвращаемый объект имеет тип InstrumentMethod.
  3. Вызовите метод addInterceptor метода InstrumentMethod, чтобы внедрить перехватчик. Все способы сбора информации об отслеживании реализованы в перехватчике. Здесь добавлен перехватчик com.navercorp.pinpoint.bootstrap.interceptor.BasicMethodInterceptor, который по умолчанию предоставляется платформой Pinpoint. базовый перехватчик, который собирает некоторую информацию о вызове targetMethod. Последний va является статическим методом (представляющим список переменных параметров), и параметры, заданные в va, будут переданы конструктору BasicMethodInterceptor.
  4. Вызовите метод InstrumentClass.toBytecode(), чтобы вернуть введенный байт-код, и оставьте остальное агенту Pinpoint для завершения.

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

Пожалуйста, обратитесь к приведенному вышеОбразец.

Interceptor — это интерфейс маркера, что действительно имеет смысл, так это интерфейс AroundInterceptor, который определяет следующие два метода:

public interface AroundInterceptor extends Interceptor {

  void before(Object target, Object[] args);

  void after(Object target, Object[] args, Object result, Throwable throwable);

}

Чтобы иметь дело со списком параметров разного количества перехваченных методов, AroundInterceptor имеет несколько подинтерфейсов: AroundInterceptor0, AroundInterceptor1,...,AroundInterceptor5, которые соответствуют методам без параметров, с одним параметром и пятью параметрами соответственно. При реализации интерфейса Interceptor вам необходимо предоставить следующий конструктор:

public RecordArgsAndReturnValueInterceptor(TraceContext traceContext,
        MethodDescriptor descriptor) {

  this.traceContext = traceContext;
  this.descriptor = descriptor;

}

TraceContext и MethodDescriptor будут внедрены агентом Pinpoint во время выполнения. Конечно, могут быть добавлены дополнительные параметры. Эти дополнительные параметры необходимо указать при добавлении Interceptor, как описано выше.

Имея в руках объект TraceContext, вы можете начать сбор информации. Вызовите метод traceContext.getCurrentTraceObject(), чтобы получить текущую трассировку, а затем вызовите trace.traceBlockBegin(), чтобы начать запись нового блока трассировки (здесь, как я понимаю, это должен быть Span). После traceBlockBegin можно вызвать метод currentSpanEventRecorder, чтобы получить объект SpanEventRecorder, который предоставляет такие методы, как RecordServiceType, RecordApi, RecordException и RecordAttribute, которые могут соответственно записывать информацию о методе. Но SpanEventRecorder не предоставляет такой метод, как recordReturnValue, который может быть записан только через recordAttribute. Вся саморасширяемая информация также записывается через recordAttribute. Наконец, когда вся информация записана, вызывается метод traceBlockEnd() для закрытия блока.

Пожалуйста, обратитесь к приведенному вышеОбразец.

V. Резюме

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