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

org.apache.flume.api.ThriftRpcClient Maven / Gradle / Ivy

There is a newer version: 4.15.0-HBase-1.5
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.flume.api;

import org.apache.flume.Event;
import org.apache.flume.EventDeliveryException;
import org.apache.flume.FlumeException;
import org.apache.flume.thrift.Status;
import org.apache.flume.thrift.ThriftFlumeEvent;
import org.apache.flume.thrift.ThriftSourceProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.transport.TFastFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThriftRpcClient extends AbstractRpcClient {
  private static final Logger LOGGER =
    LoggerFactory.getLogger(ThriftRpcClient.class);

  private int batchSize;
  private long requestTimeout;
  private final Lock stateLock;
  private State connState;
  private String hostname;
  private int port;
  private ConnectionPoolManager connectionManager;
  private final ExecutorService callTimeoutPool;
  private final AtomicLong threadCounter;
  private int connectionPoolSize;
  private final Random random = new Random();

  public ThriftRpcClient() {
    stateLock = new ReentrantLock(true);
    connState = State.INIT;

    threadCounter = new AtomicLong(0);
    // OK to use cached threadpool, because this is simply meant to timeout
    // the calls - and is IO bound.
    callTimeoutPool = Executors.newCachedThreadPool(new ThreadFactory() {
      @Override
      public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setName("Flume Thrift RPC thread - " + String.valueOf(
          threadCounter.incrementAndGet()));
        return t;
      }
    });
  }


  @Override
  public int getBatchSize() {
    return batchSize;
  }

  @Override
  public void append(Event event) throws EventDeliveryException {
    // Thrift IPC client is not thread safe, so don't allow state changes or
    // client.append* calls unless the lock is acquired.
    ClientWrapper client = null;
    boolean destroyedClient = false;
    try {
      if (!isActive()) {
        throw new EventDeliveryException("Client was closed due to error. " +
          "Please create a new client");
      }
      client = connectionManager.checkout();
      final ThriftFlumeEvent thriftEvent = new ThriftFlumeEvent(event
        .getHeaders(), ByteBuffer.wrap(event.getBody()));
      doAppend(client, thriftEvent).get(requestTimeout, TimeUnit.MILLISECONDS);
    } catch (Throwable e) {
      if (e instanceof ExecutionException) {
        Throwable cause = e.getCause();
        if (cause instanceof EventDeliveryException) {
          throw (EventDeliveryException) cause;
        } else if (cause instanceof TimeoutException) {
          throw new EventDeliveryException("Append call timeout", cause);
        }
      }
      destroyedClient = true;
      // If destroy throws, we still don't want to reuse the client, so mark it
      // as destroyed before we actually do.
      if (client != null) {
        connectionManager.destroy(client);
      }
      if (e instanceof Error) {
        throw (Error) e;
      } else if (e instanceof RuntimeException) {
        throw (RuntimeException) e;
      }
      throw new EventDeliveryException("Failed to send event. ", e);
    } finally {
      if (client != null && !destroyedClient) {
        connectionManager.checkIn(client);
      }
    }
  }

  @Override
  public void appendBatch(List events) throws EventDeliveryException {
    // Thrift IPC client is not thread safe, so don't allow state changes or
    // client.append* calls unless the lock is acquired.
    ClientWrapper client = null;
    boolean destroyedClient = false;
    try {
      if (!isActive()) {
        throw new EventDeliveryException("Client was closed " +
          "due to error or is not yet configured.");
      }
      client = connectionManager.checkout();
      final List thriftFlumeEvents = new ArrayList
        ();
      Iterator eventsIter = events.iterator();
      while (eventsIter.hasNext()) {
        thriftFlumeEvents.clear();
        for (int i = 0; i < batchSize && eventsIter.hasNext(); i++) {
          Event event = eventsIter.next();
          thriftFlumeEvents.add(new ThriftFlumeEvent(event.getHeaders(),
            ByteBuffer.wrap(event.getBody())));
        }
        if (!thriftFlumeEvents.isEmpty()) {
          doAppendBatch(client, thriftFlumeEvents).get(requestTimeout,
            TimeUnit.MILLISECONDS);
        }
      }
    } catch (Throwable e) {
      if (e instanceof ExecutionException) {
        Throwable cause = e.getCause();
        if (cause instanceof EventDeliveryException) {
          throw (EventDeliveryException) cause;
        } else if (cause instanceof TimeoutException) {
          throw new EventDeliveryException("Append call timeout", cause);
        }
      }
      destroyedClient = true;
      // If destroy throws, we still don't want to reuse the client, so mark it
      // as destroyed before we actually do.
      if (client != null) {
        connectionManager.destroy(client);
      }
      if (e instanceof Error) {
        throw (Error) e;
      } else if (e instanceof RuntimeException) {
        throw (RuntimeException) e;
      }
      throw new EventDeliveryException("Failed to send event. ", e);
    } finally {
      if (client != null && !destroyedClient) {
        connectionManager.checkIn(client);
      }
    }
  }

  private Future doAppend(final ClientWrapper client,
    final ThriftFlumeEvent e) throws Exception {

    return callTimeoutPool.submit(new Callable() {
      @Override
      public Void call() throws Exception {
        Status status = client.client.append(e);
        if (status != Status.OK) {
          throw new EventDeliveryException("Failed to deliver events. Server " +
            "returned status : " + status.name());
        }
        return null;
      }
    });
  }

  private Future doAppendBatch(final ClientWrapper client,
    final List e) throws Exception {

    return callTimeoutPool.submit(new Callable() {
      @Override
      public Void call() throws Exception {
        Status status = client.client.appendBatch(e);
        if (status != Status.OK) {
          throw new EventDeliveryException("Failed to deliver events. Server " +
            "returned status : " + status.name());
        }
        return null;
      }
    });
  }

  @Override
  public boolean isActive() {
    stateLock.lock();
    try {
      return (connState == State.READY);
    } finally {
      stateLock.unlock();
    }
  }

  @Override
  public void close() throws FlumeException {
    try {
      //Do not release this, because this client is not to be used again
      stateLock.lock();
      connState = State.DEAD;
      connectionManager.closeAll();
      callTimeoutPool.shutdown();
      if(!callTimeoutPool.awaitTermination(5, TimeUnit.SECONDS)) {
        callTimeoutPool.shutdownNow();
      }
    } catch (Throwable ex) {
      if(ex instanceof Error) {
        throw (Error) ex;
      } else if (ex instanceof RuntimeException) {
        throw (RuntimeException) ex;
      }
      throw new FlumeException("Failed to close RPC client. ", ex);
    } finally {
      stateLock.unlock();
    }
  }

  @Override
  protected void configure(Properties properties) throws FlumeException {
    if (isActive()) {
      throw new FlumeException("Attempting to re-configured an already " +
        "configured client!");
    }
    stateLock.lock();
    try {
      HostInfo host = HostInfo.getHostInfoList(properties).get(0);
      hostname = host.getHostName();
      port = host.getPortNumber();
      batchSize = Integer.parseInt(properties.getProperty(
        RpcClientConfigurationConstants.CONFIG_BATCH_SIZE,
        RpcClientConfigurationConstants.DEFAULT_BATCH_SIZE.toString()));
      requestTimeout = Long.parseLong(properties.getProperty(
        RpcClientConfigurationConstants.CONFIG_REQUEST_TIMEOUT,
        String.valueOf(
          RpcClientConfigurationConstants.DEFAULT_REQUEST_TIMEOUT_MILLIS)));
      if (requestTimeout < 1000) {
        LOGGER.warn("Request timeout specified less than 1s. " +
          "Using default value instead.");
        requestTimeout =
          RpcClientConfigurationConstants.DEFAULT_REQUEST_TIMEOUT_MILLIS;
      }
      connectionPoolSize = Integer.parseInt(properties.getProperty(
        RpcClientConfigurationConstants.CONFIG_CONNECTION_POOL_SIZE,
        String.valueOf(RpcClientConfigurationConstants
          .DEFAULT_CONNECTION_POOL_SIZE)));
      if(connectionPoolSize < 1) {
        LOGGER.warn("Connection Pool Size specified is less than 1. " +
          "Using default value instead.");
        connectionPoolSize = RpcClientConfigurationConstants
          .DEFAULT_CONNECTION_POOL_SIZE;
      }
      connectionManager = new ConnectionPoolManager(connectionPoolSize);
      connState = State.READY;
    } catch (Throwable ex) {
      //Failed to configure, kill the client.
      connState = State.DEAD;
      if(ex instanceof Error) {
        throw (Error) ex;
      } else if (ex instanceof RuntimeException) {
        throw (RuntimeException) ex;
      }
      throw new FlumeException("Error while configuring RpcClient. ", ex);
    } finally {
      stateLock.unlock();
    }
  }

  private static enum State {
    INIT, READY, DEAD
  }

  /**
   * Wrapper around a client and transport, so we can clean up when this
   * client gets closed.
   */
  private class ClientWrapper {
    public final ThriftSourceProtocol.Client client;
    public final TFastFramedTransport transport;
    private final int hashCode;

    public ClientWrapper() throws Exception{
      transport = new TFastFramedTransport(new TSocket(hostname, port));
      transport.open();
      client = new ThriftSourceProtocol.Client(new TCompactProtocol
        (transport));
      // Not a great hash code, but since this class is immutable and there
      // is at most one instance of the components of this class,
      // this works fine [If the objects are equal, hash code is the same]
      hashCode = random.nextInt();
    }

    public boolean equals(Object o) {
      if(o == null) {
        return false;
      }
      // Since there is only one wrapper with any given client,
      // direct comparison is good enough.
      if(this == o) {
        return true;
      }
      return false;
    }

    public int hashCode() {
      return hashCode;
    }
  }

  private class ConnectionPoolManager {
    private final Queue availableClients;
    private final Set checkedOutClients;
    private final int maxPoolSize;
    private int currentPoolSize;
    private final Lock poolLock;
    private final Condition availableClientsCondition;

    public ConnectionPoolManager(int poolSize) {
      this.maxPoolSize = poolSize;
      availableClients = new LinkedList();
      checkedOutClients = new HashSet();
      poolLock = new ReentrantLock();
      availableClientsCondition = poolLock.newCondition();
      currentPoolSize = 0;
    }

    public ClientWrapper checkout() throws Exception {

      ClientWrapper ret = null;
      poolLock.lock();
      try {
        if (availableClients.isEmpty() && currentPoolSize < maxPoolSize) {
          ret = new ClientWrapper();
          currentPoolSize++;
          checkedOutClients.add(ret);
          return ret;
        }
        while (availableClients.isEmpty()) {
          availableClientsCondition.await();
        }
        ret = availableClients.poll();
        checkedOutClients.add(ret);
      } finally {
        poolLock.unlock();
      }
      return ret;
    }

    public void checkIn(ClientWrapper client) {
      poolLock.lock();
      try {
        availableClients.add(client);
        checkedOutClients.remove(client);
        availableClientsCondition.signal();
      } finally {
        poolLock.unlock();
      }
    }

    public void destroy(ClientWrapper client) {
      poolLock.lock();
      try {
        checkedOutClients.remove(client);
        currentPoolSize--;
      } finally {
        poolLock.unlock();
      }
      client.transport.close();
    }

    public void closeAll() {
      poolLock.lock();
      try {
        for (ClientWrapper c : availableClients) {
          c.transport.close();
          currentPoolSize--;
        }
      /*
       * Be cruel and close even the checked out clients. The threads writing
       * using these will now get an exception.
       */
        for (ClientWrapper c : checkedOutClients) {
          c.transport.close();
          currentPoolSize--;
        }
      } finally {
        poolLock.unlock();
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy