All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zipkin2.reporter.amqp.RabbitMQSender Maven / Gradle / Ivy

/*
 * Copyright 2016-2024 The OpenZipkin Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package zipkin2.reporter.amqp;

import com.rabbitmq.client.Address;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeoutException;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.Call;
import zipkin2.reporter.Callback;
import zipkin2.reporter.CheckResult;
import zipkin2.reporter.ClosedSenderException;
import zipkin2.reporter.Encoding;
import zipkin2.reporter.Sender;

import static zipkin2.reporter.Call.propagateIfFatal;

/**
 * This sends (usually json v2) encoded spans to a RabbitMQ queue.
 *
 * 

Usage

*

* This type is designed for {@link AsyncReporter.Builder#builder(BytesMessageSender) the async * reporter}. * *

Here's a simple configuration, configured for json: * *

{@code
 * sender = RabbitMQSender.create("localhost:5672");
 * }
* *

Here's an example with an explicit SSL connection factory and protocol buffers encoding: * *

{@code
 * connectionFactory = new ConnectionFactory();
 * connectionFactory.setHost("localhost");
 * connectionFactory.setPort(5671);
 * connectionFactory.useSslProtocol();
 * sender = RabbitMQSender.newBuilder()
 *   .connectionFactory(connectionFactory)
 *   .encoding(Encoding.PROTO3)
 *   .build();
 * }
* *

Compatibility with Zipkin Server

* * Zipkin server should be v2.1 or higher. * *

Implementation Notes

* *

The sender does not use RabbitMQ Publisher * Confirms, so messages considered sent may not necessarily be received by consumers in case of * RabbitMQ failure. * *

This sender is thread-safe: a channel is created for each thread that calls * {@link #send(List)}. */ public final class RabbitMQSender extends Sender { /** Creates a sender that sends {@link Encoding#JSON} messages. */ public static RabbitMQSender create(String addresses) { return newBuilder().addresses(addresses).build(); } public static Builder newBuilder() { return new Builder(); } /** Configuration including defaults needed to send spans to a RabbitMQ queue. */ public static final class Builder { ConnectionFactory connectionFactory = new ConnectionFactory(); List

addresses; String queue = "zipkin"; Encoding encoding = Encoding.JSON; int messageMaxBytes = 500000; Builder(RabbitMQSender sender) { connectionFactory = sender.connectionFactory.clone(); addresses = sender.addresses; queue = sender.queue; encoding = sender.encoding; messageMaxBytes = sender.messageMaxBytes; } public Builder connectionFactory(ConnectionFactory connectionFactory) { if (connectionFactory == null) throw new NullPointerException("connectionFactory == null"); this.connectionFactory = connectionFactory; return this; } public Builder addresses(List
addresses) { if (addresses == null) throw new NullPointerException("addresses == null"); this.addresses = addresses; return this; } /** Comma-separated list of host:port pairs. ex "192.168.99.100:5672" No Default. */ public Builder addresses(String addresses) { if (addresses == null) throw new NullPointerException("addresses == null"); this.addresses = convertAddresses(addresses); return this; } /** Queue zipkin spans will be send to. Defaults to "zipkin" */ public Builder queue(String queue) { if (queue == null) throw new NullPointerException("queue == null"); this.queue = queue; return this; } /** * Use this to change the encoding used in messages. Default is {@linkplain Encoding#JSON} * *

Note: If ultimately sending to Zipkin, version 2.8+ is required to process protobuf. */ public Builder encoding(Encoding encoding) { if (encoding == null) throw new NullPointerException("encoding == null"); this.encoding = encoding; return this; } /** Connection TCP establishment timeout in milliseconds. Defaults to 60 seconds */ public Builder connectionTimeout(int connectionTimeout) { connectionFactory.setConnectionTimeout(connectionTimeout); return this; } /** The virtual host to use when connecting to the broker. Defaults to "/" */ public Builder virtualHost(String virtualHost) { connectionFactory.setVirtualHost(virtualHost); return this; } /** The AMQP user name to use when connecting to the broker. Defaults to "guest" */ public Builder username(String username) { connectionFactory.setUsername(username); return this; } /** The password to use when connecting to the broker. Defaults to "guest" */ public Builder password(String password) { connectionFactory.setPassword(password); return this; } /** Maximum size of a message. Default 500KB. */ public Builder messageMaxBytes(int messageMaxBytes) { this.messageMaxBytes = messageMaxBytes; return this; } public final RabbitMQSender build() { return new RabbitMQSender(this); } Builder() { } } final Encoding encoding; final int messageMaxBytes; final List

addresses; final String queue; final ConnectionFactory connectionFactory; RabbitMQSender(Builder builder) { if (builder.addresses == null) throw new NullPointerException("addresses == null"); encoding = builder.encoding; messageMaxBytes = builder.messageMaxBytes; addresses = builder.addresses; queue = builder.queue; connectionFactory = builder.connectionFactory.clone(); } public Builder toBuilder() { return new Builder(this); } /** get and close are typically called from different threads */ volatile Connection connection; volatile boolean closeCalled; @Override public Encoding encoding() { return encoding; } @Override public int messageMaxBytes() { return messageMaxBytes; } @Override public int messageSizeInBytes(List encodedSpans) { return encoding.listSizeInBytes(encodedSpans); } @Override public int messageSizeInBytes(int encodedSizeInBytes) { return encoding.listSizeInBytes(encodedSizeInBytes); } /** {@inheritDoc} */ @Override @Deprecated public Call sendSpans(List encodedSpans) { if (closeCalled) throw new ClosedSenderException(); byte[] message = encoding.encode(encodedSpans); return new RabbitMQCall(message); } /** {@inheritDoc} */ @Override public void send(List encodedSpans) throws IOException { if (closeCalled) throw new ClosedSenderException(); publish(encoding.encode(encodedSpans)); } void publish(byte[] message) throws IOException { localChannel().basicPublish("", queue, null, message); } /** {@inheritDoc} */ @Override @Deprecated public CheckResult check() { try { if (localChannel().isOpen()) return CheckResult.OK; throw new IllegalStateException("Not Open"); } catch (Throwable e) { propagateIfFatal(e); return CheckResult.failed(e); } } @Override public String toString() { return "RabbitMQSender{addresses=" + addresses + ", queue=" + queue + "}"; } Connection get() { if (connection == null) { synchronized (this) { if (connection == null) { connection = newConnection(); } } } return connection; } Connection newConnection() { try { return connectionFactory.newConnection(addresses); } catch (IOException e) { throw new RuntimeException("Unable to establish connection to RabbitMQ server", e); } catch (TimeoutException e) { throw new RuntimeException("Unable to establish connection to RabbitMQ server", e); } } @Override public synchronized void close() throws IOException { if (closeCalled) return; Connection connection = this.connection; if (connection != null) connection.close(); closeCalled = true; } final ThreadLocal CHANNEL = new ThreadLocal(); /** * In most circumstances there will only be one thread calling {@link #send(List)}, the * {@link AsyncReporter}. Just in case someone is flushing manually, we use a thread-local. All of * this is to avoid recreating a channel for each publish, as that costs two additional network * roundtrips. */ Channel localChannel() throws IOException { Channel channel = CHANNEL.get(); if (channel == null) { channel = get().createChannel(); CHANNEL.set(channel); } return channel; } class RabbitMQCall extends Call.Base { // RabbitMQFuture is not cancelable private final byte[] message; RabbitMQCall(byte[] message) { this.message = message; } @Override protected Void doExecute() throws IOException { publish(message); return null; } @Override protected void doEnqueue(Callback callback) { try { publish(message); callback.onSuccess(null); } catch (Throwable t) { Call.propagateIfFatal(t); callback.onError(t); } } @Override public Call clone() { return new RabbitMQCall(message); } } static List
convertAddresses(String addresses) { String[] addressStrings = addresses.split(","); Address[] addressArray = new Address[addressStrings.length]; for (int i = 0; i < addressStrings.length; i++) { String[] splitAddress = addressStrings[i].split(":"); String host = splitAddress[0]; Integer port = null; try { if (splitAddress.length == 2) port = Integer.parseInt(splitAddress[1]); } catch (NumberFormatException ignore) { } addressArray[i] = (port != null) ? new Address(host, port) : new Address(host); } return Arrays.asList(addressArray); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy