소프트웨어 통합 간소화: Apache Camel 자습서
게시 됨: 2022-03-11소프트웨어가 정보의 공백 상태에 있는 경우는 거의 없습니다. 적어도 그것은 우리가 개발하는 대부분의 응용 프로그램에 대해 우리 소프트웨어 엔지니어가 할 수 있는 가정입니다.
규모에 상관없이 모든 소프트웨어는 여러 가지 이유로 다른 소프트웨어와 통신합니다. 어딘가에서 참조 데이터를 가져오기 위해, 모니터링 신호를 보내기 위해, 분산된 서비스의 일부인 동안 다른 서비스와 연락하기 위해 시스템 등.
이 튜토리얼에서는 대규모 소프트웨어 통합의 가장 큰 문제가 무엇이며 Apache Camel이 이를 쉽게 해결하는 방법을 배우게 됩니다.
문제: 시스템 통합을 위한 아키텍처 설계
소프트웨어 엔지니어링 인생에서 적어도 한 번은 다음을 수행했을 것입니다.
- 데이터 전송을 시작해야 하는 비즈니스 로직의 단편을 식별하십시오.
- 동일한 애플리케이션 계층에서 수신자가 기대하는 바에 따라 데이터 변환을 작성합니다.
- 네트워크를 통한 전송 및 라우팅에 적합한 구조로 데이터를 래핑합니다.
- 적절한 드라이버 또는 클라이언트 SDK를 사용하여 대상 응용 프로그램에 대한 연결을 엽니다.
- 데이터를 보내고 응답을 처리합니다.
이것이 왜 나쁜 행동입니까?
이러한 종류의 연결은 몇 개뿐이지만 관리 가능한 상태로 유지됩니다. 시스템 간의 관계가 증가함에 따라 애플리케이션의 비즈니스 로직은 통합 로직과 혼합됩니다. 통합 로직은 데이터를 조정하고 두 시스템 간의 기술적 차이를 보상하며 SOAP, REST 또는 더 특이한 요청을 사용하여 데이터를 외부 시스템으로 전송합니다. .
여러 응용 프로그램을 통합하는 경우 이러한 코드에서 종속성의 전체 그림을 추적하는 것은 매우 어려울 것입니다. 데이터가 생성되는 위치와 데이터를 사용하는 서비스는 무엇입니까? 부팅하려면 통합 로직이 중복되는 곳이 많이 있을 것입니다.
이러한 접근 방식을 사용하면 작업이 기술적으로 완료되지만 통합의 유지 관리 가능성 및 확장성과 관련된 큰 문제가 발생합니다. 모니터링 부족, 회로 차단, 힘든 데이터 복구 등과 같은 더 깊은 문제는 말할 것도 없고 이 시스템에서 데이터 흐름의 빠른 재구성은 거의 불가능합니다.
이는 상당히 큰 기업의 범위에서 소프트웨어를 통합할 때 특히 중요합니다. 엔터프라이즈 통합을 다룬다는 것은 다양한 플랫폼에서 작동하고 다양한 위치에 존재하는 일련의 애플리케이션으로 작업하는 것을 의미합니다. 이러한 소프트웨어 환경에서 데이터 교환은 상당히 까다롭습니다. 업계의 높은 보안 표준을 충족하고 데이터를 전송하는 안정적인 방법을 제공해야 합니다. 엔터프라이즈 환경에서 시스템 통합에는 철저하게 정교한 별도의 아키텍처 설계가 필요합니다.
이 기사에서는 소프트웨어 통합에서 직면한 고유한 어려움을 소개하고 통합 작업에 대한 경험 중심 솔루션을 제공합니다. 통합 개발자의 골칫거리 중 최악의 부분을 완화할 수 있는 유용한 프레임워크인 Apache Camel에 익숙해질 것입니다. Camel이 Kubernetes로 구동되는 마이크로서비스 클러스터에서 통신을 설정하는 데 어떻게 도움이 되는지에 대한 예를 따르겠습니다.
통합의 어려움
이 문제를 해결하기 위해 널리 사용되는 접근 방식은 애플리케이션에서 통합 계층을 분리하는 것입니다. 동일한 애플리케이션 내에 있거나 독립적으로 실행되는 전용 소프트웨어(후자의 경우 미들웨어라고 함)로 존재할 수 있습니다.
미들웨어를 개발하고 지원할 때 일반적으로 어떤 문제에 직면합니까? 일반적으로 다음과 같은 주요 항목이 있습니다.
- 모든 데이터 채널은 어느 정도 신뢰할 수 없습니다. 데이터 집약도가 낮거나 중간일 때는 이러한 비신뢰성으로 인한 문제가 발생하지 않을 수 있습니다. 애플리케이션 메모리에서 하위 캐시 및 그 아래의 장비에 이르기까지 각 스토리지 수준은 잠재적인 오류가 발생할 수 있습니다. 일부 드문 오류는 엄청난 양의 데이터에서만 발생합니다. 생산 준비가 완료된 성숙한 공급업체 제품에도 데이터 손실과 관련된 버그 추적기 문제가 해결되지 않았습니다. 미들웨어 시스템은 이러한 데이터 손실을 사용자에게 알리고 적시에 메시지 재전송을 제공할 수 있어야 합니다.
- 응용 프로그램은 다양한 프로토콜과 데이터 형식을 사용합니다. 이는 통합 시스템이 데이터 변환을 위한 커튼이며 다른 참가자에 대한 어댑터이며 다양한 기술을 활용한다는 것을 의미합니다. 여기에는 일반 REST API 호출이 포함될 수 있지만 대기열 브로커에 액세스하거나 FTP를 통해 CSV 주문을 보내거나 일괄 처리 데이터를 데이터베이스 테이블로 가져올 수도 있습니다. 이것은 긴 목록이며 결코 줄어들지 않을 것입니다.
- 데이터 형식 및 라우팅 규칙의 변경은 불가피합니다. 데이터 구조를 변경하는 애플리케이션 개발 프로세스의 각 단계는 일반적으로 통합 데이터 형식 및 변환의 변경으로 이어집니다. 경우에 따라 재구성된 엔터프라이즈 데이터 흐름으로 인프라를 변경해야 합니다. 예를 들어, 회사 전체의 모든 마스터 데이터 항목을 처리해야 하는 단일 참조 데이터 검증 지점을 도입할 때 이러한 변경이 발생할 수 있습니다.
N
시스템을 사용하면 시스템 간에 최대N^2
연결이 생길 수 있으므로 변경 사항을 적용해야 하는 위치의 수가 매우 빠르게 증가합니다. 그것은 눈사태와 같을 것입니다. 유지 보수성을 유지하기 위해 미들웨어 계층은 다양한 라우팅 및 데이터 변환을 통해 종속성에 대한 명확한 그림을 제공해야 합니다.
통합을 설계하고 가장 적합한 미들웨어 솔루션을 선택할 때 이러한 아이디어를 염두에 두어야 합니다. 이를 처리하는 가능한 방법 중 하나는 ESB(엔터프라이즈 서비스 버스)를 활용하는 것입니다. 그러나 주요 공급업체에서 제공하는 ESB는 일반적으로 너무 무거우며 종종 가치보다 더 많은 문제가 발생합니다. ESB로 빠르게 시작하는 것은 거의 불가능하고 학습 곡선이 상당히 가파르며 유연성이 긴 목록에 희생됩니다. 기능 및 기본 제공 도구. 제 생각에는 경량 오픈 소스 통합 솔루션이 훨씬 더 우수합니다. 더 탄력적이고 클라우드에 배포하기 쉽고 확장하기 쉽습니다.
소프트웨어 통합은 쉽지 않습니다. 오늘날 우리는 마이크로서비스 아키텍처를 구축하고 소규모 서비스 떼를 처리하면서 그들이 얼마나 효율적으로 통신해야 하는지에 대한 높은 기대치를 가지고 있습니다.
엔터프라이즈 통합 패턴
예상할 수 있듯이 일반적인 소프트웨어 개발과 마찬가지로 데이터 라우팅 및 변환의 개발에는 반복적인 작업이 포함됩니다. 이 분야의 경험은 꽤 오랫동안 통합 문제를 처리한 전문가에 의해 요약되고 체계화되었습니다. 결과적으로 데이터 흐름을 설계하는 데 사용되는 엔터프라이즈 통합 패턴이라는 추출된 템플릿 집합이 있습니다. 이러한 통합 방법은 Gregor Hophe와 Bobby Wolfe의 같은 이름의 책에 설명되어 있습니다. 이 책은 중요한 Gang of Four의 책과 매우 유사하지만 접착 소프트웨어 영역에 있습니다.
예를 들어, 노멀라이저 패턴은 데이터 형식이 다른 의미론적으로 동일한 메시지를 단일 표준 모델에 매핑하는 구성 요소를 도입하거나 집계가 일련의 메시지를 하나로 결합하는 EIP입니다.
아키텍처 문제를 해결하는 데 사용되는 기술에 구애받지 않는 추상화로 설정되어 있기 때문에 EIP는 코드 수준을 탐구하지 않고 데이터 흐름을 충분히 자세히 설명하는 아키텍처 설계를 작성하는 데 도움이 됩니다. 이러한 통합 경로를 기술하는 표기법은 디자인을 간결하게 할 뿐만 아니라 공통 명명법과 공통 언어를 설정하는데, 이는 다양한 비즈니스 영역의 팀원들과 통합 작업을 해결하는 맥락에서 매우 중요합니다.
Apache Camel 소개
몇 년 전, 저는 광범위하게 분산된 위치에 매장이 있는 거대한 식료품 소매 네트워크에서 엔터프라이즈 통합을 구축하고 있었습니다. 저는 독점 ESB 솔루션으로 시작했는데 유지 관리가 너무 번거로운 것으로 판명되었습니다. 그런 다음 우리 팀은 Apache Camel을 발견했고 "개념 증명" 작업을 수행한 후 Camel 경로의 모든 데이터 흐름을 빠르게 다시 작성했습니다.
Apache Camel은 EIP 목록을 구현하는 메시지 지향 미들웨어 프레임워크인 "중개 라우터"로 설명할 수 있습니다. 이러한 패턴을 사용하고 모든 공통 전송 프로토콜을 지원하며 방대한 유용한 어댑터 세트가 포함되어 있습니다. Camel을 사용하면 고유한 코드를 작성할 필요 없이 여러 통합 루틴을 처리할 수 있습니다.
이 외에도 다음 Apache Camel 기능을 선별합니다.
- 통합 경로는 블록으로 구성된 파이프라인으로 작성됩니다. 데이터 흐름을 추적하는 데 도움이 되는 완전히 투명한 그림을 만듭니다.
- Camel에는 많은 인기 있는 API에 대한 어댑터가 있습니다. 예를 들어 Apache Kafka에서 데이터 가져오기, AWS EC2 인스턴스 모니터링, Salesforce와 통합 등 이러한 모든 작업은 즉시 사용 가능한 구성 요소를 사용하여 해결할 수 있습니다.
Apache Camel 경로는 Java 또는 Scala DSL로 작성할 수 있습니다. (XML 구성도 사용 가능하지만 너무 장황해지고 디버깅 기능이 나빠집니다.) 통신 서비스의 기술 스택에 제한을 두지 않지만 Java 또는 Scala로 작성하는 경우 응용 프로그램에 Camel을 대신 포함할 수 있습니다. 독립 실행형으로 실행하는 것입니다.
Camel에서 사용하는 라우팅 표기법은 다음과 같은 간단한 의사 코드로 설명할 수 있습니다.
from(Source) .transform(Transformer) .to(Destination)
Source
, Transformer
및 Destination
은 URI로 구현 구성 요소를 참조하는 끝점입니다.
Camel이 앞에서 설명한 통합 문제를 해결할 수 있는 것은 무엇입니까? 한번 봅시다. 첫째, 라우팅 및 변환 로직은 이제 전용 Apache Camel 구성에만 있습니다. 둘째, EIP의 사용과 함께 간결하고 자연스러운 DSL을 통해 시스템 간의 종속성 그림이 나타납니다. 이해할 수 있는 추상화로 구성되어 있으며 라우팅 논리를 쉽게 조정할 수 있습니다. 마지막으로 적절한 어댑터가 이미 포함되어 있을 가능성이 높기 때문에 변환 코드 힙을 작성할 필요가 없습니다.
Apache Camel은 성숙한 프레임워크이며 정기적으로 업데이트됩니다. 그것은 훌륭한 커뮤니티와 상당한 누적 지식 기반을 가지고 있습니다.
나름의 단점이 있습니다. Camel은 복잡한 통합 제품군으로 간주되어서는 안 됩니다. 비즈니스 프로세스 관리 도구나 활동 모니터와 같은 고급 기능이 없는 도구 상자이지만 이러한 소프트웨어를 만드는 데 사용할 수 있습니다.
대체 시스템은 예를 들어 Spring Integration 또는 Mule ESB일 수 있습니다. Spring Integration의 경우 경량으로 간주되지만 내 경험상 많은 XML 구성 파일을 통합하고 작성하는 것은 예기치 않게 복잡하고 쉬운 방법이 아닙니다. Mule ESB는 강력하고 매우 기능적인 도구 세트이지만 이름에서 알 수 있듯이 엔터프라이즈 서비스 버스이므로 다른 가중치 범주에 속합니다. Mule은 다양한 기능을 갖춘 Apache Camel 기반의 유사한 제품인 Fuse ESB와 비교할 수 있습니다. 나에게 있어 Apache Camel을 사용하여 서비스를 접착하는 것은 오늘날 쉬운 일이 아닙니다. 사용하기 쉽고 위치에 대한 명확한 설명을 생성하는 동시에 복잡한 통합을 구축하기에 충분히 기능적입니다.
샘플 경로 작성
코드 작성을 시작해 보겠습니다. 단일 소스에서 받는 사람 목록으로 메시지를 라우팅하는 동기식 데이터 흐름에서 시작하겠습니다. 라우팅 규칙은 Java DSL로 작성됩니다.
Maven을 사용하여 프로젝트를 빌드합니다. 먼저 pom.xml
에 다음 종속성을 추가합니다.
<dependencies> ... <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>2.20.0</version> </dependency> </dependencies>
또는 camel-archetype-java
원형 위에 응용 프로그램을 빌드할 수 있습니다.
낙타 경로 정의는 RouteBuilder.configure
메소드에서 선언됩니다.
public void configure() { errorHandler(defaultErrorHandler().maximumRedeliveries(0)); from("file:orders?noop=true").routeId("main") .log("Incoming File: ${file:onlyname}") .unmarshal().json(JsonLibrary.Jackson, Order.class) // unmarshal JSON to Order class containing List<OrderItem> .split().simple("body.items") // split list to process one by one .to("log:inputOrderItem") .choice() .when().simple("${body.type} == 'Drink'") .to("direct:bar") .when().simple("${body.type} == 'Dessert'") .to("direct:dessertStation") .when().simple("${body.type} == 'Hot Meal'") .to("direct:hotMealStation") .when().simple("${body.type} == 'Cold Meal'") .to("direct:coldMealStation") .otherwise() .to("direct:others"); from("direct:bar").routeId("bar").log("Handling Drink"); from("direct:dessertStation").routeId("dessertStation").log("Handling Dessert"); from("direct:hotMealStation").routeId("hotMealStation").log("Handling Hot Meal"); from("direct:coldMealStation").routeId("coldMealStation").log("Handling Cold Meal"); from("direct:others").routeId("others").log("Handling Something Other"); }
이 정의에서는 JSON 파일에서 레코드를 가져와서 항목으로 분할하고 메시지 내용을 기반으로 핸들러 세트로 라우팅하는 경로를 생성합니다.
준비된 테스트 데이터로 실행해보자. 결과는 다음과 같습니다.
INFO | Total 6 routes, of which 6 are started INFO | Apache Camel 2.20.0 (CamelContext: camel-1) started in 10.716 seconds INFO | Incoming File: order1.json INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Drink', name='Americano', qty='1'}] INFO | Handling Drink INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='French Omelette', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='Lasagna', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='Rice Balls', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Dessert', name='Blueberry Pie', qty='1'}] INFO | Handling Dessert
예상대로 Camel은 메시지를 목적지로 라우팅했습니다.
데이터 전송 선택
위의 예에서 구성 요소 간의 상호 작용은 동기식이며 애플리케이션 메모리를 통해 수행됩니다. 그러나 메모리를 공유하지 않는 별도의 응용 프로그램을 다룰 때 더 많은 통신 방법이 있습니다.
- 파일 교환. 한 응용 프로그램은 다른 응용 프로그램이 사용할 공유 데이터 파일을 생성합니다. 올드 스쿨 정신이 살아있는 곳입니다. 이 통신 방법은 트랜잭션 및 일관성 부족, 성능 저하, 시스템 간의 고립된 조정 등 많은 결과를 낳습니다. 많은 개발자들은 프로세스를 다소간 관리하기 쉽게 만들기 위해 집에서 만든 통합 솔루션을 작성하게 되었습니다.
- 공통 데이터베이스. 애플리케이션이 공유하려는 데이터를 단일 데이터베이스의 공통 스키마에 저장하도록 합니다. 통합 스키마를 설계하고 테이블에 대한 동시 액세스를 처리하는 것은 이 접근 방식의 가장 두드러진 과제입니다. 파일 교환과 마찬가지로 영구적인 병목 현상이 되기 쉽습니다.
- 원격 API 호출. 일반적인 메서드 호출과 같이 응용 프로그램이 실행 중인 다른 응용 프로그램과 상호 작용할 수 있도록 인터페이스를 제공합니다. 애플리케이션은 API 호출을 통해 기능을 공유하지만 프로세스에서 긴밀하게 연결됩니다.
- 메시징. 각 애플리케이션이 공통 메시징 시스템에 연결하고 메시지를 사용하여 비동기적으로 데이터를 교환하고 동작을 호출하도록 합니다. 보낸 사람이나 받는 사람이 동시에 실행되어 메시지가 배달될 필요는 없습니다.
더 많은 상호 작용 방법이 있지만 일반적으로 상호 작용에는 동기식과 비동기식의 두 가지 유형이 있다는 점을 염두에 두어야 합니다. 첫 번째는 코드에서 함수를 호출하는 것과 같습니다. 실행 흐름은 실행되어 값을 반환할 때까지 대기합니다. 비동기식 접근 방식에서는 동일한 데이터가 중간 메시지 대기열 또는 구독 주제를 통해 전송됩니다. 비동기 원격 기능 호출은 요청-응답 EIP로 구현될 수 있습니다.
그러나 비동기식 메시징이 만병통치약은 아닙니다. 특정 제한 사항이 포함됩니다. 웹에서는 메시징 API를 거의 볼 수 없습니다. 동기식 REST 서비스가 훨씬 더 유명합니다. 그러나 메시징 미들웨어는 엔터프라이즈 인트라넷 또는 분산 시스템 백엔드 인프라에서 널리 사용됩니다.
메시지 대기열 사용
예제를 비동기식으로 만들어 보겠습니다. 대기열 및 구독 주제를 관리하는 소프트웨어 시스템을 메시지 브로커라고 합니다. 테이블과 열을 위한 RDBMS와 같습니다. 대기열은 지점 간 통합 역할을 하는 반면 주제는 많은 수신자와의 게시-구독 통신을 위한 것입니다. Apache ActiveMQ는 견고하고 포함할 수 있기 때문에 JMS 메시지 브로커로 사용할 것입니다.
다음 종속성을 추가합니다. 때로는 모든 ActiveMQ jar를 포함하는 activemq-all
을 프로젝트에 추가하는 것이 과도하지만 애플리케이션의 종속성을 복잡하지 않게 유지합니다.
<dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.15.2</version> </dependency>
그런 다음 프로그래밍 방식으로 브로커를 시작합니다. Spring Boot에서는 spring-boot-starter-activemq
Maven 종속성을 연결하여 이에 대한 자동 구성을 얻습니다.
커넥터의 끝점만 지정하여 다음 명령으로 새 메시지 브로커를 실행합니다.
BrokerService broker = new BrokerService(); broker.addConnector("tcp://localhost:61616"); broker.start();
그리고 configure
메소드 본문에 다음 구성 스니펫을 추가합니다.
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); this.getContext().addComponent("activemq", ActiveMQComponent.jmsComponent(connectionFactory));
이제 메시지 대기열을 사용하여 이전 예제를 업데이트할 수 있습니다. 대기열은 메시지 배달 시 자동으로 생성됩니다.
public void configure() { errorHandler(defaultErrorHandler().maximumRedeliveries(0)); ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); this.getContext().addComponent("activemq", ActiveMQComponent.jmsComponent(connectionFactory)); from("file:orders?noop=true").routeId("main") .log("Incoming File: ${file:onlyname}") .unmarshal().json(JsonLibrary.Jackson, Order.class) // unmarshal JSON to Order class containing List<OrderItem> .split().simple("body.items") // split list to process one by one .to("log:inputOrderItem") .choice() .when().simple("${body.type} == 'Drink'") .to("activemq:queue:bar") .when().simple("${body.type} == 'Dessert'") .to("activemq:queue:dessertStation") .when().simple("${body.type} == 'Hot Meal'") .to("activemq:queue:hotMealStation") .when().simple("${body.type} == 'Cold Meal'") .to("activemq:queue:coldMealStation") .otherwise() .to("activemq:queue:others"); from("activemq:queue:bar").routeId("barAsync").log("Drinks"); from("activemq:queue:dessertStation").routeId("dessertAsync").log("Dessert"); from("activemq:queue:hotMealStation").routeId("hotMealAsync").log("Hot Meals"); from("activemq:queue:coldMealStation").routeId("coldMealAsync").log("Cold Meals"); from("activemq:queue:others").routeId("othersAsync").log("Others"); }
좋습니다. 이제 상호 작용이 비동기식으로 바뀌었습니다. 이 데이터의 잠재적 소비자는 준비가 되었을 때 액세스할 수 있습니다. 이것은 우리가 반응형 아키텍처에서 달성하려고 하는 느슨한 결합의 예입니다. 서비스 중 하나를 사용할 수 없어도 다른 서비스는 차단되지 않습니다. 또한 소비자는 대기열에서 병렬로 크기를 조정하고 읽을 수 있습니다. 대기열 자체는 확장되고 분할될 수 있습니다. 영구 대기열은 모든 참가자가 다운된 경우에도 처리를 기다리는 디스크에 데이터를 저장할 수 있습니다. 결과적으로 이 시스템은 더 나은 내결함성을 제공합니다.
놀라운 사실은 CERN이 Apache Camel과 ActiveMQ를 사용하여 LHC(Large Hadron Collider) 시스템을 모니터링한다는 것입니다. 이 작업에 적합한 미들웨어 솔루션의 선택을 설명하는 흥미로운 석사 논문도 있습니다. 따라서 기조연설에서 "JMS도 없고 입자 물리학도 없습니다!"라고 말합니다.
모니터링
이전 예에서 우리는 두 서비스 사이에 데이터 채널을 생성했습니다. 이는 아키텍처에서 추가적인 잠재적 실패 지점이므로 우리는 이를 돌봐야 합니다. Apache Camel이 제공하는 모니터링 기능을 살펴보겠습니다. 기본적으로 JMX에서 액세스할 수 있는 MBean을 통해 경로에 대한 통계 정보를 노출합니다. ActiveMQ는 같은 방식으로 대기열 통계를 노출합니다.
애플리케이션에서 JMX 서버를 켜서 명령줄 옵션으로 실행할 수 있도록 합시다.
-Dorg.apache.camel.jmx.createRmiConnector=true -Dorg.apache.camel.jmx.mbeanObjectDomainName=org.apache.camel -Dorg.apache.camel.jmx.rmiConnector.registryPort=1099 -Dorg.apache.camel.jmx.serviceUrlPath=camel
이제 경로가 작업을 완료하도록 애플리케이션을 실행하십시오. 표준 jconsole
도구를 열고 응용 프로그램 프로세스에 연결합니다. URL service:jmx:rmi:///jndi/rmi://localhost:1099/camel
에 연결합니다. MBeans 트리에서 org.apache.camel 도메인으로 이동합니다.

라우팅에 대한 모든 것이 제어되고 있음을 알 수 있습니다. 진행 중인 메시지 수, 오류 수 및 대기열에 있는 메시지 수를 알 수 있습니다. 이 정보는 Graphana 또는 Kibana와 같은 풍부한 기능을 갖춘 일부 모니터링 도구 세트로 파이프라인될 수 있습니다. 잘 알려진 ELK 스택을 구현하여 이를 수행할 수 있습니다.
또한 Hawt.io라는 Camel, ActiveMQ 등을 관리하기 위한 UI를 제공하는 플러그 가능하고 확장 가능한 웹 콘솔이 있습니다.
테스트 경로
Apache Camel은 모의 구성 요소로 테스트 경로를 작성하기 위한 매우 광범위한 기능을 가지고 있습니다. 강력한 도구이지만 테스트용으로만 별도의 경로를 작성하는 것은 시간이 많이 걸리는 프로세스입니다. 파이프라인을 수정하지 않고 프로덕션 경로에서 테스트를 실행하는 것이 더 효율적입니다. Camel에는 이 기능이 있으며 AdviceWith 구성 요소를 사용하여 구현할 수 있습니다.
예제에서 테스트 로직을 활성화하고 샘플 테스트를 실행해 보겠습니다.
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-test</artifactId> <version>2.20.0</version> <scope>test</scope> </dependency>
테스트 클래스는 다음과 같습니다.
public class AsyncRouteTest extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() throws Exception { return new AsyncRouteBuilder(); } @Before public void mockEndpoints() throws Exception { context.getRouteDefinition("main").adviceWith(context, new AdviceWithRouteBuilder() { @Override public void configure() throws Exception { // we substitute all actual queues with mock endpoints mockEndpointsAndSkip("activemq:queue:bar"); mockEndpointsAndSkip("activemq:queue:dessertStation"); mockEndpointsAndSkip("activemq:queue:hotMealStation"); mockEndpointsAndSkip("activemq:queue:coldMealStation"); mockEndpointsAndSkip("activemq:queue:others"); // and replace the route's source with test endpoint replaceFromWith("file://testInbox"); } }); } @Test public void testSyncInteraction() throws InterruptedException { String testJson = "{\"id\": 1, \"order\": [{\"id\": 1, \"name\": \"Americano\", \"type\": \"Drink\", \"qty\": \"1\"}, {\"id\": 2, \"name\": \"French Omelette\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 3, \"name\": \"Lasagna\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 4, \"name\": \"Rice Balls\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 5, \"name\": \"Blueberry Pie\", \"type\": \"Dessert\", \"qty\": \"1\"}]}"; // get mocked endpoint and set an expectation MockEndpoint mockEndpoint = getMockEndpoint("mock:activemq:queue:hotMealStation"); mockEndpoint.expectedMessageCount(3); // simulate putting file in the inbox folder template.sendBodyAndHeader("file://testInbox", testJson, Exchange.FILE_NAME, "test.json"); //checks that expectations were met assertMockEndpointsSatisfied(); } }
이제 mvn test
를 사용하여 애플리케이션에 대한 테스트를 실행합니다. 테스트 조언으로 경로가 성공적으로 실행되었음을 알 수 있습니다. 실제 대기열을 통해 전달된 메시지가 없으며 테스트가 통과되었습니다.
INFO | Route: main started and consuming from: file://testInbox <...> INFO | Incoming File: test.json <...> INFO | Asserting: mock://activemq:queue:hotMealStation is satisfied
Kubernetes 클러스터와 함께 Apache Camel 사용
오늘날 통합 문제 중 하나는 애플리케이션이 더 이상 정적이지 않다는 것입니다. 클라우드 인프라에서는 동시에 여러 노드에서 실행되는 가상 서비스를 다룹니다. 그것은 서로 상호 작용하는 작고 가벼운 서비스 네트를 사용하여 마이크로 서비스 아키텍처를 가능하게 합니다. 이러한 서비스는 신뢰할 수 없는 수명을 가지며 동적으로 검색해야 합니다.
클라우드 서비스를 함께 연결하는 것은 Apache Camel로 해결할 수 있는 작업입니다. EIP의 특징과 Camel이 많은 어댑터를 가지고 있고 광범위한 프로토콜을 지원한다는 사실 때문에 특히 흥미롭습니다. 최신 버전 2.18에는 API를 호출하고 클러스터 검색 메커니즘을 통해 해당 주소를 확인하는 기능을 도입한 ServiceCall 구성 요소가 추가되었습니다. 현재 Consul, Kubernetes, Ribbon 등을 지원합니다. ServiceCall이 Consul로 구성된 코드의 일부 예를 쉽게 찾을 수 있습니다. Kubernetes는 내가 가장 좋아하는 클러스터링 솔루션이기 때문에 여기에서 사용할 것입니다.
통합 스키마는 다음과 같습니다.
Order
서비스와 Inventory
서비스는 정적 데이터를 반환하는 몇 가지 간단한 Spring Boot 애플리케이션이 될 것입니다. 여기서 우리는 특정 기술 스택에 묶여 있지 않습니다. 이러한 서비스는 처리하려는 데이터를 생성합니다.
주문 서비스 컨트롤러:
@RestController public class OrderController { private final OrderStorage orderStorage; @Autowired public OrderController(OrderStorage orderStorage) { this.orderStorage = orderStorage; } @RequestMapping("/info") public String info() { return "Order Service UU/orders") public List<Order> getAll() { return orderStorage.getAll(); } @RequestMapping("/orders/{id}") public Order getOne(@PathVariable Integer id) { return orderStorage.getOne(id); } }
다음 형식의 데이터를 생성합니다.
[{"id":1,"items":[2,3,4]},{"id":2,"items":[5,3]}]
Inventory
서비스 컨트롤러는 Order
서비스와 절대적으로 유사합니다.
@RestController public class InventoryController { private final InventoryStorage inventoryStorage; @Autowired public InventoryController(InventoryStorage inventoryStorage) { this.inventoryStorage = inventoryStorage; } @RequestMapping("/info") public String info() { return "Inventory Service UU/items") public List<InventoryItem> getAll() { return inventoryStorage.getAll(); } @RequestMapping("/items/{id}") public InventoryItem getOne(@PathVariable Integer id) { return inventoryStorage.getOne(id); } }
InventoryStorage
는 데이터를 보관하는 일반 저장소입니다. 이 예제에서는 다음 형식으로 마샬링되는 미리 정의된 정적 개체를 반환합니다.
[{"id":1,"name":"Laptop","description":"Up to 12-hours battery life","price":499.9},{"id":2,"name":"Monitor","description":"27-inch, response time: 7ms","price":200.0},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9},{"id":4,"name":"Mouse","description":"Designed for comfort and portability","price":19.0},{"id":5,"name":"Keyboard","description":"Layout: US","price":10.5}]
이들을 연결하는 게이트웨이 경로를 작성해 보겠습니다. 그러나 이 단계에서는 ServiceCall이 없습니다.
rest("/orders") .get("/").description("Get all orders with details").outType(TestResponse.class) .route() .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .to("http4://localhost:8082/orders?bridgeEndpoint=true") .unmarshal(formatOrder) .enrich("direct:enrichFromInventory", new OrderAggregationStrategy()) .to("log:result") .endRest(); from("direct:enrichFromInventory") .transform().simple("${null}") .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .to("http4://localhost:8081/items?bridgeEndpoint=true") .unmarshal(formatInventory);
이제 각 서비스가 더 이상 특정 인스턴스가 아니라 하나로 작동하는 인스턴스 클라우드라고 상상해 보십시오. Minikube를 사용하여 Kubernetes 클러스터를 로컬에서 사용해 보겠습니다.
Kubernetes 노드를 로컬에서 볼 수 있도록 네트워크 경로를 구성합니다(주어진 예는 Mac/Linux 환경용).
# remove existing routes sudo route -n delete 10/24 > /dev/null 2>&1 # add routes sudo route -n add 10.0.0.0/24 $(minikube ip) # 172.17.0.0/16 ip range is used by docker in minikube sudo route -n add 172.17.0.0/16 $(minikube ip) ifconfig 'bridge100' | grep member | awk '{print $2}' # use interface name from the output of the previous command # needed for xhyve driver, which I'm using for testing sudo ifconfig bridge100 -hostfilter en5
다음과 같은 Dockerfile 구성을 사용하여 Docker 컨테이너의 서비스를 래핑합니다.
FROM openjdk:8-jdk-alpine VOLUME /tmp ADD target/order-srv-1.0-SNAPSHOT.jar app.jar ADD target/lib lib ENV JAVA_OPTS="" ENTRYPOINT exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar
서비스 이미지를 빌드하고 Docker 레지스트리에 푸시합니다. 이제 로컬 Kubernetes 클러스터에서 노드를 실행합니다.
Kubernetes.yaml 배포 구성:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: inventory spec: replicas: 3 selector: matchLabels: app: inventory template: metadata: labels: app: inventory spec: containers: - name: inventory image: inventory-srv:latest imagePullPolicy: Never ports: - containerPort: 8081
이러한 배포를 클러스터의 서비스로 노출합니다.
kubectl expose deployment order-srv --type=NodePort kubectl expose deployment inventory-srv --type=NodePort
이제 클러스터에서 무작위로 선택된 노드에서 요청이 제공되는지 확인할 수 있습니다. curl -X http://192.168.99.100:30517/info
를 순차적으로 여러 번 실행하여 노출된 서비스(호스트 및 포트 사용)에 대한 minikube NodePort에 액세스합니다. 출력에서 요청 밸런싱을 달성했음을 알 수 있습니다.
Inventory Service UUID = 22f8ca6b-f56b-4984-927b-cbf9fcf81da5 Inventory Service UUID = b7a4d326-1e76-4051-a0a6-1016394fafda Inventory Service UUID = b7a4d326-1e76-4051-a0a6-1016394fafda Inventory Service UUID = 22f8ca6b-f56b-4984-927b-cbf9fcf81da5 Inventory Service UUID = 50323ddb-3ace-4424-820a-6b4e85775af4
프로젝트의 pom.xml
에 camel-kubernetes
및 camel-netty4-http
종속성을 추가하십시오. 그런 다음 경로 정의 간의 모든 서비스 호출에 대해 공유되는 Kubernetes 마스터 노드 검색을 사용하도록 ServiceCall 구성 요소를 구성합니다.
KubernetesConfiguration kubernetesConfiguration = new KubernetesConfiguration(); kubernetesConfiguration.setMasterUrl("https://192.168.64.2:8443"); kubernetesConfiguration.setClientCertFile("/Users/antongoncharov/.minikube/client.crt"); kubernetesConfiguration.setClientKeyFile("/Users/antongoncharov/.minikube/client.key"); kubernetesConfiguration.setNamespace("default”); ServiceCallConfigurationDefinition config = new ServiceCallConfigurationDefinition(); config.setServiceDiscovery(new KubernetesClientServiceDiscovery(kubernetesConfiguration)); context.setServiceCallConfiguration(config);
ServiceCall EIP는 Spring Boot를 잘 보완합니다. 대부분의 옵션은 application.properties
파일에서 직접 구성할 수 있습니다.
ServiceCall 구성 요소를 사용하여 Camel 경로를 강화합니다.
rest("/orders") .get("/").description("Get all orders with details").outType(TestResponse.class) .route() .hystrix() .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .serviceCall("customer-srv","http4:customer-deployment?bridgeEndpoint=true") .unmarshal(formatOrder) .enrich("direct:enrichFromInventory", new OrderAggregationStrategy()) .to("log:result") .endRest(); from("direct:enrichFromInventory") .transform().simple("${null}") .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .serviceCall("order-srv","http4:order-srv?bridgeEndpoint=true") .unmarshal(formatInventory);
우리는 또한 경로에서 서킷 브레이커를 활성화했습니다. 배달 오류 또는 받는 사람을 사용할 수 없는 경우 원격 시스템 호출을 일시 중지할 수 있는 통합 후크입니다. 이것은 캐스케이드 시스템 오류를 방지하도록 설계되었습니다. Hystrix 구성 요소는 회로 차단기 패턴을 구현하여 이를 달성하는 데 도움이 됩니다.
그것을 실행하고 테스트 요청을 보내자. 두 서비스에서 집계된 응답을 받게 됩니다.
[{"id":1,"items":[{"id":2,"name":"Monitor","description":"27-inch, response time: 7ms","price":200.0},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9},{"id":4,"name":"Mouse","description":"Designed for comfort and portability","price":19.0}]},{"id":2,"items":[{"id":5,"name":"Keyboard","description":"Layout: US","price":10.5},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9}]}]
결과는 예상대로입니다.
기타 사용 사례
Apache Camel이 클러스터에 마이크로서비스를 통합하는 방법을 보여주었습니다. 이 프레임워크의 다른 용도는 무엇입니까? 일반적으로 규칙 기반 라우팅이 솔루션이 될 수 있는 모든 곳에서 유용합니다. For instance, Apache Camel can be a middleware for the Internet of Things with the Eclipse Kura adapter. It can handle monitoring by ferrying log signals from various components and services, like in the CERN system. It can also be an integration framework for enterprise SOA or be a pipeline for batch data processing, although it doesn't compete well with Apache Spark in this area.
결론
You can see that systems integration isn't an easy process. We're lucky because a lot of experience has been gathered. It's important to apply it correctly to build flexible and fault-tolerant solutions.
To ensure correct application, I recommend having a checklist of important integration aspects. Must-have items include:
- Is there a separate integration layer?
- Are there tests for integration?
- Do we know the expected peak data intensity?
- Do we know the expected data delivery time?
- Does message correlation matter? What if a sequence breaks?
- Should we do it in a synchronous or asynchronous way?
- Where do formats and routing rules change more frequently?
- Do we have ways to monitor the process?
In this article, we tried Apache Camel, a lightweight integration framework, which helps save time and effort when solving integration problems. As we showed, it can serve as a tool, supporting the relevant microservice architecture by taking full responsibility for data exchange between microservices.
If you're interested in learning more about Apache Camel, I highly recommend the book “Camel in Action” by the framework's creator, Claus Ibsen. Official documentation is available at camel.apache.org.