Your SlideShare is downloading. ×

Spring AMQP × RabbitMQ

3,557

Published on

Published in: Technology
0 Comments
11 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
3,557
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
6
Comments
0
Likes
11
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Spring AMQP × RabbitMQ @Keisuke69
  • 2. 自己紹介西谷圭介 TIS株式会社 eXcaleというPaaSやってます http://www.excale.net/Twitter ID : @Keisuke69Github : Keisuke69
  • 3. Spring AMQPとは?Springのサブプロジェクトの1つJava版と.Net版がある比較的若いプロダクト 最初のGAリリースが2011/08 最新は1.1.3AMQPベースのメッセージングをSpringっぽく扱える アプリケーションロジックとインフラ周りの設定を切り離せるメッセージ送受信用に抽象化されたtemplateを提供POJOによるメッセージ送受信をサポート
  • 4. AMQPメッセージングミドルウェアのオープンなプロトコル仕様プラットフォーム問わず、実装言語非依存インターオペラビリティ高いAPIだけでなくワイヤレベルプロトコルである JMSはAPIだけビジネス上のリアルな問題を解決するようデザインされているhttp://www.amqp.org/
  • 5. 基本モデル Server Publisher Exchange Binding Consumer Message Queue Virtual HostExchangeがメッセージを受け取ってMessage Queueへと受け渡すMessage QueueはMessageをためてConsumerに渡すExchangeとMessageQueueを対応付けるのがBinding ExchangeのタイプはDirect、Fanout、Topic、Headerがある MessageQueueを複数にする構成も可能
  • 6. RabbitMQErlangで書かれているブローカー VMwareによって開発されているAMQPのインプリメンテーションクラスタリングやキューのミラーリングが可能 本来AMQPのSpecではクラスタ等については 定義されていない
  • 7. なぜメッセージングかコンポーネントを疎結合にできる非同期負荷分散とスケーラビリティ
  • 8. Spring AMQPを使う
  • 9. Java-based container configuration 従来、XMLで定義していたSpringの各種設定をJavaクラス で定義 冗長になりがちなXMLを書かなくていいので個人的に はおすすめ @Configurationをつけたクラスに、@Beanをつけたメ ソッドを実装してbean登録 ApplicationContextはAnnotationConfigApplicationContextで 今回のサンプルは全てこの方式で設定
  • 10. ConnectionFactoryRabbitMQへの接続を管理するインターフェースcom.rabbitmq.client.ConnectionのラッパーCachingConnectionFactoryだけが実装クラスとして提供されている
  • 11. TemplateAmqpTemplate 汎用インターフェースRabbitOperation RabbitMQ用インターフェースRabbitTemplate 実際のインプリメンテーション実情として現時点ではサポートしているのがRabbitMQのみ 後々、他のAMQPミドルウェアをサポート予定(らしい)
  • 12. Template 各種メソッドおよびコールバックを提供 メッセージの送受信は基本的にTemplateを利用して行 うvoid send(Message message) throws AmqpException;void convertAndSend(Object message) throws AmqpException;Message receive() throws AmqpException;Object receiveAndConvert() throws AmqpException;
  • 13. MessageConverterSpring AMQPの大きな特徴オブジェクトとメッセージ間で変換が行えるデフォルトではSimpleMessageConverter JsonMessageConverter MarshallingMessageConverter
  • 14. Configuration@Configurationpublic class SampleConfig { @Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost"); connectionFactory.setUsername("guest"); connectionFactory.setPassword("guest"); return connectionFactory; } @Bean public AmqpAdmin amqpAdmin() { return new RabbitAdmin(connectionFactory()); } @Bean public RabbitTemplate rabbitTemplate() { RabbitTemplate template = new RabbitTemplate(connectionFactory()); template.setRoutingKey("sample_queue"); template.setQueue("sample_queue"); return template; } @Bean public Queue helloWorldQueue() { return new Queue("sample_queue"); }}
  • 15. メッセージの送信 AmqpTemplateを使って行う 下記サンプルではアノテーションベースの設定を行なっ ているのでAnnotationConfitgApplicationContextを使用public class Publisher {    public static void main(String[] args){        ApplicationContext context = new AnnotationConfigApplicationContext(SampleConfig.class);        AmqpTemplate amqpTemplate = context.getBean(RabbitTemplate.class);        amqpTemplate.convertAndSend("Hello world");        System.out.println("Sent message.");    }}
  • 16. メッセージの受信 送信と同じくAmqpTemplateを使うpublic class Consumer {    public static void main(String[] args){        ApplicationContext context = newAnnotationConfigApplicationContext(SampleConfig.class);        AmqpTemplate amqpTemplate = context.getBean(RabbitTemplate.class);        String message = (String)amqpTemplate.receiveAndConvert();        System.out.println("Receive: " + message);    }}
  • 17. MessageConverter Templateのbean登録時に利用したいコンバータをセットする 今回は JsonMessageConverterを使用 JsonMessageConverterを利用する場合、Jacksonパーサ必須@Configurationpublic class SampleConfig { (省略) @Bean public RabbitTemplate rabbitTemplate() { RabbitTemplate template = new RabbitTemplate(connectionFactory()); template.setRoutingKey("sample_queue"); template.setQueue("sample_queue"); template.setMessageConverter(new JsonMessageConverter()); return template; } (省略)}
  • 18. MessageConverter POJOを用意する 今回はシンプルにkeyとmessageというフィールドを用意してそれらに対するgetter/setter を定義したものを用意 DB用のモデルクラスなどと兼用することも可能public class SimplePojo {    private String key;    private String message;       public String getKey() {        return key;    }    public void setKey(String key) {        this.key = key;    }    public String getMessage() {        return message;    }    public void setMessage(String message) {        this.message = message;    }}
  • 19. コンバータを使った送信 POJOをnewしたオブジェクトに値をセット AmqpTemplateのconvertAndSendの引数としてオブジェクトを渡 すpublic class Publisher {    public static void main(String[] args){        ApplicationContext context = newAnnotationConfigApplicationContext(SampleConfig.class);        AmqpTemplate amqpTemplate = context.getBean(RabbitTemplate.class);        SimplePojo simplePojo = new SimplePojo();        simplePojo.setKey("ABC");        simplePojo.setMessage("This is message made by pojo.");        amqpTemplate.convertAndSend(simplePojo);        System.out.println("Sent message.");    }}
  • 20. コンバータを使った受信 同期処理 AmqpTemplate.receiveAndConvert() 変換先の型にキャストする必要があるpublic class Consumer { public static void main(String[] args){ ApplicationContext context = new AnnotationConfigApplicationContext(SampleConfig.class); AmqpTemplate amqpTemplate = context.getBean(RabbitTemplate.class); SimplePojo simplePojo = (SimplePojo)amqpTemplate.receiveAndConvert(); System.out.println("Receive message: key = " + simplePojo.getKey() + ", message = " + simplePojo.getMessage()); }}
  • 21. MessageListener
  • 22. MessageListener非同期受信のためのインターフェース メッセージドリブンなコールバック処理を簡 単に実装できるSimpleMessageListenerContainer Springが提供する軽量リスナーコンテナメッセージ受信時の処理をハンドラとして実装
  • 23. MessageListener HelloWorldHandlerというハンドラクラスをMessageListenerAdapterで紐付け もちろん、MessageConverterも利用可能@Configurationpublic class SampleListenerConfig {    String queueName = "sample_queue";       @Bean    public ConnectionFactory connectionFactory() {        CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");        connectionFactory.setUsername("guest");        connectionFactory.setPassword("guest");        return connectionFactory;    }    @Bean    public SimpleMessageListenerContainer listenerContainer(){        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();        container.setConnectionFactory(connectionFactory());        container.setQueueNames(queueName);        container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler(),new JsonMessageConverter()));        return container;    }}
  • 24. ハンドラクラス 特別なクラスの継承やインターフェースの実装は不要 handleMessageというメソッドはデフォルト 変更可能 メソッドの引数として変換先の型を指定する 自動的に変換されて、getterでアクセス可能public class HelloWorldHandler {    public void handleMessage(SimplePojo simplePojo) {        System.out.println("Received: Key = " + simplePojo.getKey()                 + ", message = " + simplePojo.getMessage());    }}
  • 25. MessageListenerの起動 基本的に変わらない 標準ではApplicationContext起動時に自動でリスナーも 起動される 受信時の処理がハンドラで行われるようになっている ためその辺りの記述が不要public class Consumer {    public static void main(String[] args){        new AnnotationConfigApplicationContext(SampleListenerConfig.class);    }}
  • 26. 困ったところメッセージの型が固定になる ヘッダの値を元に変換先の型が特定されている(FQCN) PublisherがSpring以外でもこのヘッダのセットが必要 我々の場合、MQにメッセージを送る側が異なる言語で実装されていた 独自のクラスマッパーを用意 とは言ってもFQCNでの指定を不要にしただけ1つの型につき、定義できる処理が1つ 複数のキュー、型を利用したい場合に大量に定義が必要、かつ1つの型で複数の処理 は定義できない 同じ型で異なる処理をさせたかった 汎用ハンドラクラスを用意し、型と命名規則で実際のハンドラクラスへ処理を実装
  • 27. サンプル (独自ClassMapper)DefaultClassMapperを継承してtoClassメソッドをオーバーライドメッセージヘッダの"__TypeId__"に指定された型名(FQCNじゃない)を使って指定のパッケージ配下からクラスを探しだして返却
  • 28. サンプル (独自ClassMapper) @Override public Class<?> toClass(MessageProperties properties) { if (targetClass != null) { return targetClass; } Map<String, Object> headers = properties.getHeaders(); Object classIdFieldNameValue = headers.get(getClassIdFieldName()); String classId = null; if (classIdFieldNameValue != null) { classId = classIdFieldNameValue.toString(); } String className = null; try { className = ClassResolver.scan(classId, this.basePackage); if (className != null) { return ClassUtils.forName(className, getClass() .getClassLoader()); } } catch (IOException e1) { } catch (ClassNotFoundException e) { } return super.toClass(properties); }
  • 29. サンプル(汎用ハンドラクラス)メッセージヘッダのTypeId+Handlerという名前で委譲先のハンドラクラスを決定“__Method__”ヘッダにセットされたメソッドを実行全オブジェクト分のSimpleListenerContainerのbean登録が不要に1つのキューで複数のオブジェクトを元にしたメッセージの送受信が可能
  • 30. サンプル (汎用ハンドラクラス)public class GenericHandler { private String path; @Autowired BeanFactory beanFactory; public GenericHandler() { this.path = "org.hoge"; } public GenericHandler(String path) { this.path = path; } public void handleMessage(Object obj, MessageProperties message) { String typeId = null; String methodName = null; try { Map<String, Object> headers = message.getHeaders(); typeId = (String) headers.get("__TypeId__"); methodName = ((String) headers.get("__Method__")); methodName = StringUtils.uncapitalize(methodName); String delegateClassName = ClassResolver.scan(typeId + "Handler",this.path); Object delegate = beanFactory.getBean(ClassUtils.forName(delegateClassName, getClass().getClassLoader())); MethodInvoker methodInvoker = new MethodInvoker(); methodInvoker.setTargetObject(delegate); methodInvoker.setTargetMethod(methodName); Object[] objects = { obj }; methodInvoker.setArguments(objects); methodInvoker.prepare(); methodInvoker.invoke(); } catch (Exception e) { throw new HandlerExecutionFailureException(e); } }}
  • 31. その他の考慮事項RabbitMQを冗長化した場合の話 特にMirroredQueueを利用する場合 →Codezineさんに寄稿した記事に書いておきました  http://codezine.jp/article/detail/6943エラー時のハンドリング RetryTemplateとMessageRecovererとかトランザクション @TransactionalとTransactionManager
  • 32. なぜメッセージングかコンポーネントを疎結合にできる MQを境界としてコンポーネント間の依存関係を切り離せる メッセージのフォーマットさえ正しければ言語が異なってもOK非同期 疎結合になるため、障害時などの影響を小さくできる 部分的なスケールアウトなどが可能に負荷分散とスケーラビリティ Consumerプロセスを増やすだけでスケールできる SpringMQを使えば1プロセス内の並行度の設定も可能 Queueを増やすことも簡単、Exchangeの構成だけでラウンドロビン等も可能
  • 33. Spring AMQPとRabbitMQ使うと 簡単に非同期アプリが書けますよ! http://www.excale.net/

×