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

c4zn0huecdpwdc07r4vokl-mhau.png

В данной статье я расскажу вам о том, каким образом можно построить граф маршрутов для приложений с 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 файле (пример ниже), закинуть его в нужную директорию сервиса и он будет развернут.



    
      
        
        
        
      
    

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

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

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


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

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


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

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


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

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


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

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

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



  
  
      
      
    
    
      
      
    
    
      
      
    
  


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

Метод 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": "\n\n    \n    \n<\/route>\n",
    "timestamp": 1545040727,
    "status": 200
}


Рисуем граф

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


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

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

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

Можно объединить все данные по маршрутам из servicemix и приложения и нарисовать общий граф
ou_9exsfvhosre_r6zwie1kvobq.png
Обратите внимание, что на схеме появился новый маршрут из jms:topic:timer в bean:service.


Заключение

Реализовав описанный подход к построению графа маршрутов, мы смогли получить общую картину как для шины, так и для интегрированных сервисов. Собственно граф нашей шины выглядит так
p-si4ndyze4n5ur_wla4bqnacyu.png
Proof of concept приложения можно посмотреть тут — github

© Habrahabr.ru