Граф маршрутов для Apache Camel


    В данной статье я расскажу вам о том, каким образом можно построить граф маршрутов для приложений с Apache Camel, отслеживать состояния этих маршрутов и собирать для них метрики.
    Мы используем Apache Camel в spring приложениях и в Apache ServiceMix. И если маршруты в отдельном сервисе — это штука понятная и легко обозримая, то в рамках шины данных, где таких маршрутов много, не все так просто.


    Что такое Apache ServiceMix

    Apache Camel — открытый фреймворк для интеграции приложений за счет использования простого dsl и богатого набора готовых компонентов доступа к данным.


    Apache ServiceMix — открытое решение на базе Apache ActiveMQ, Camel, CXF, Karaf, позволяющее построить платформу для интеграционных решений. Apache ServiceMix можно использовать в роли корпоративной сервисной шины. Camel в этом случае позволит упростить создание маршрутов в шине с помощью dsl в виде xml, java, scala и т.д. Например, если нам нужно перекидывать сообщения из одной очереди в другую (давайте не будем думать о том, зачем это нам нужно), мы можем описать маршрут в xml файле (пример ниже), закинуть его в нужную директорию сервиса и он будет развернут.


    <?xml version="1.0" encoding="UTF-8"?>
    <blueprint
        xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
          http://www.osgi.org/xmlns/blueprint/v1.0.0
          http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
        <camelContext xmlns="http://camel.apache.org/schema/blueprint">
          <route>
            <from uri="file:camel/input"/>
            <log message="Moving ${file:name} to the output directory"/>
            <to uri="file:camel/output"/>
          </route>
        </camelContext>
    </blueprint>

    Описанный маршрут перекладывает файлы из одной директории в другую.


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


    Для построения графа понадобятся вершины и ребра. И из них-то мы и слепим нечто прекрасное!


    Элементы маршрута


    Точка входа (она одна) для маршрута описывается оператором from с указанием endpoint'a. Т.е. для


    <from uri="file:camel/input"/>

    endpoint'ом будет file:camel/input. Он говорит нам о том, что в начале маршрута файлы будут забираться из директории camel/input.


    Точки выхода из маршрута (их много, именно поэтому я указал множественное число) определяются по разному — в зависимости от шаблона обмена сообщениями также с указанием endpoint'a. В приведенном выше примере такая точка описывается через to. Т.е. для


    <to uri="file:camel/output"/>

    endpoint'ом будет file:camel/output. Он говорит нам о том, что в конце маршрута будет происходить сохранение файлов в директорию camel/output.


    Endpoint'ы — это нужные нам "вершины". Ребра же будут определять сами маршруты.


    Получение описания маршрутов


    Servicemix предоставляет возможность получить доступ к различной информации средствами JMX и мы воспользуемся jolokia для доступа к этой информации через http.


    В качестве примера возьмем такое описание маршрутов


    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="
             http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
             http://camel.apache.org/schema/spring
               http://camel.apache.org/schema/spring/camel-spring.xsd">
      <camelContext xmlns="http://camel.apache.org/schema/spring">
      <route id="the-clock-is-ticking">
          <from uri="timer://foo?fixedRate=true&amp;period=1000"/>
          <to uri="jms:topic:timer?connectionFactory=demo"/>
        </route>
        <route id="service-a">
          <from uri="jms:topic:timer?connectionFactory=demo"/>
          <to uri="jms:queue:service-a?connectionFactory=demo"/>
        </route>
        <route id="service-a-log">
          <from uri="jms:queue:service-a?connectionFactory=demo"/>
          <to uri="log:service-a"/>
        </route>
      </camelContext>
    </beans>

    Список маршрутов


    Метод http://host:8181/jolokia/read/org.apache.camel:type=routes,* возвращает список маршрутов с деталями.


    Пример возвращаемых данных для маршрута service-a:


    "org.apache.camel:context=a.xml,name=\"service-a\",type=routes": {
                "StatisticsEnabled": true,
                "EndpointUri": "jms:\/\/topic:timer?connectionFactory=demo",
                "CamelManagementName": "a.xml",
                "ExchangesCompleted": 173,
                "LastProcessingTime": 2,
                "ExchangesFailed": 0,
                "Description": null,
                "FirstExchangeCompletedExchangeId": "ID-...",
                "StartTimestamp": "2018-12-17T07:01:12Z",
                "FirstExchangeCompletedTimestamp": "2018-12-17T07:01:13Z",
                "LastExchangeFailureTimestamp": null,
                "MaxProcessingTime": 35,
                "LastExchangeCompletedTimestamp": "2018-12-17T07:04:05Z",
                "Load15": "",
                "DeltaProcessingTime": -8,
                "OldestInflightDuration": null,
                "ExternalRedeliveries": 0,
                "ExchangesTotal": 173,
                "ResetTimestamp": "2018-12-17T07:01:12Z",
                "ExchangesInflight": 0,
                "MeanProcessingTime": 4,
                "LastExchangeFailureExchangeId": null,
                "FirstExchangeFailureExchangeId": null,
                "Uptime": "2 minutes",
                "CamelId": "camel-3",
                "TotalProcessingTime": 827,
                "FirstExchangeFailureTimestamp": null,
                "RouteId": "service-a",
                "RoutePolicyList": "",
                "FailuresHandled": 0,
                "MessageHistory": true,
                "Load05": "",
                "OldestInflightExchangeId": null,
                "State": "Started",
                "InflightExchanges": 0,
                "Redeliveries": 0,
                "MinProcessingTime": 0,
                "LastExchangeCompletedExchangeId": "ID-...",
                "Tracing": false,
                "Load01": ""
            }

    Деталей много и среди них особый интерес для построения графа представляют RouteId, Context, EndpointUri, State, Uptime.


    Важно упомянуть, что метод возвращает и метрики по маршруту: ExchangesTotal, ExchangesCompleted, ExchangesFailed, ExchangesInflight и т.д.


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


    Детали маршрута


    Детали маршрута получаем из метода
    http://host:8181/jolokia/exec/org.apache.camel:context=a.xml,type=routes,name="service-a"/createRouteStaticEndpointJson(boolean)/true


    Пример возвращаемых данных:


    {
        "request": {
            "mbean": "org.apache.camel:context=a.xml,name=\"service-a\",type=routes",
            "arguments": ["true"],
            "type": "exec",
            "operation": "createRouteStaticEndpointJson(boolean)"
        },
        "value": "{\"routes\": {  \"service-a\": {    \"inputs\": [      { \"uri\": \"jms:\/\/topic:timer?connectionFactory=demo\" }    ],    \"outputs\": [      { \"uri\": \"jms:\/\/queue:service-a?connectionFactory=demo\" }    ]  }}\n}\n",
        "timestamp": 1545040570,
        "status": 200
    }

    Схема маршрута


    Схему маршрута получаем из метода
    http://host:8181/jolokia/exec/org.apache.camel:context=a.xml,type=routes,name="service-a"/dumpRouteAsXml(boolean)/true.


    Метод возвращает схему маршрута в xml виде только в том случае, если она была в нем оформлена. К примеру, если маршрут описан с помощью org.apache.camel.builder.RouteBuilder (используется при описании маршрутов в spring приложении), то метод ничего не вернет.


    Пример возвращаемых данных:


    {
        "request": {
            "mbean": "org.apache.camel:context=a.xml,name=\"service-a\",type=routes",
            "arguments": ["true"],
            "type": "exec",
            "operation": "dumpRouteAsXml(boolean)"
        },
        "value": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<route customId=\"true\" id=\"service-a\" xmlns=\"http:\/\/camel.apache.org\/schema\/spring\">\n    <from uri=\"jms:topic:timer?connectionFactory=demo\"\/>\n    <to uri=\"jms:queue:service-a?connectionFactory=demo\" id=\"to5\"\/>\n<\/route>\n",
        "timestamp": 1545040727,
        "status": 200
    }

    Рисуем граф


    Объединив всю полученную информацию можно смело рисовать граф, я воспользовался vis.js и получил такой результат

    Точки — точки входа и выхода, ребра — маршруты, а серые цифры на маршрутах — метрика ExchangesTotal.


    Построение графа для нескольких сервисов


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


    @Component
    public class EventRoutes extends RouteBuilder {
        @Override
        public void configure() throws Exception {
            from("jms:topic:timer")
                    .inOnly("bean:service?method=handle");
        }
    }

    Можно объединить все данные по маршрутам из servicemix и приложения и нарисовать общий граф

    Обратите внимание, что на схеме появился новый маршрут из jms:topic:timer в bean:service.


    Заключение


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

    Proof of concept приложения можно посмотреть тут — github

    Поделиться публикацией

    Комментарии 10

      +1
      А в сторону hawt.io не смотрели? По крайней мере некая статистика и роуты в нем визуально оценить можно. Может даже допилить под себя…
        0
        Конечно, хорошая штука. Но на сколько я помню, через нее удобно работать с отдельными маршрутами. Оценить же весь граф маршрутов в целом сложно.
        0

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

          0
          В нашем случае такая архитектура, когда общение между сервисами реализовано в асинхронном режиме в виде событий.
            0
            В нашем это именно потоковые данные (биржа, например). Сделки, котировки и разные другие документы.
            0
            Эк вы ловко объединили два jms:topic в один, из разных контекстов. Я догадываюсь, что у вас наверное возможно такое — но в общем случае это могут быть совсем разные топики на разных jms серверах. И простое объединение узлов графа на базе их названий в camel некорректно (а другого способа не существует, потому что у нас например соединение проходит через qpid, и там внутри тоже существует обработка — которую в подобный граф не включить.
              0
              Эк вы ловко объединили два jms:topic в один, из разных контекстов

              Почему из разных? Общий jms брокер. И в рамках одного брокера всякий jms:topic:foo ссылается на один и тот же топик foo.
              И простое объединение узлов графа на базе их названий в camel некорректно

              Да, некорректно. И я так не делаю. Информации, чтобы отличать похожие узлы, достаточно и я их отличаю. Но я прячу детали от пользователя и использую, например, цвета, чтобы визуально показать, что это разные сервисы или контексты.
                0
                >Почему из разных
                Потому что по тексту это выглядит как объединение двух разных routes из разных контекстов (ну, во всяком случае я вас так понял). А в разных контекстах jms:topic:foo вовсе не обязаны быть двумя одинаковыми брокерами.

                Ну то есть, я не говорю что так нельзя, но скажем на нашей шине такое бы не прокатило, потому что jms: (или в нашем случае amqp:) это всего-лишь имя в некотором camel registry, и два разных jms вполне могут ссылаться как на один брокер, так и на разные, и понять это можно только если залезть в определение самого jms, и даже там это будет не очевидно.
                  0
                  Интересно, а как Вы отличаете jms брокеров тогда? Через connectionFactory?
                    0
                    Конечно. Ну то есть, я хочу сказать, что только посмотрев туда в свойства, вы точно можете сказать, один это брокер, или два разных. Впрочем, у нас такая задача не стояла, как у вас, и никто не пытался построить граф всех маршрутов, в первую очередь потому, что большая часть обработки была в qpid, и в других местах вне camel, поэтому даже если бы мы такой граф построили, от него было бы не очень много пользы. Поэтому нас вполне устроила hawt.io.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое