Package com.linkedin.databus2.producers

Source Code of com.linkedin.databus2.producers.OpenReplicatorEventProducer

/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* 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 com.linkedin.databus2.producers;

import java.lang.management.ManagementFactory;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.apache.log4j.Logger;

import com.google.code.or.OpenReplicator;
import com.linkedin.databus.core.DatabusRuntimeException;
import com.linkedin.databus.core.DatabusThreadBase;
import com.linkedin.databus.core.DbusEventBufferAppendable;
import com.linkedin.databus.core.UnsupportedKeyException;
import com.linkedin.databus.core.monitoring.mbean.DbusEventsStatisticsCollector;
import com.linkedin.databus.core.util.InvalidConfigException;
import com.linkedin.databus.monitoring.mbean.EventSourceStatistics;
import com.linkedin.databus2.core.DatabusException;
import com.linkedin.databus2.core.seq.MaxSCNReaderWriter;
import com.linkedin.databus2.producers.ORListener.TransactionProcessor;
import com.linkedin.databus2.producers.db.EventReaderSummary;
import com.linkedin.databus2.producers.db.EventSourceStatisticsIface;
import com.linkedin.databus2.producers.db.ReadEventCycleSummary;
import com.linkedin.databus2.producers.ds.DbChangeEntry;
import com.linkedin.databus2.producers.ds.PerSourceTransaction;
import com.linkedin.databus2.producers.ds.Transaction;
import com.linkedin.databus2.relay.config.LogicalSourceStaticConfig;
import com.linkedin.databus2.relay.config.PhysicalSourceStaticConfig;
import com.linkedin.databus2.schemas.SchemaRegistryService;

/**
* Fetcher for mysql using OpenReplicator.
*
* Connectivity Params :
* URI format:  mysql://username/password@mysql_host[:mysql_port]/mysql_serverid/binlog_prefix
* binLogFilePath : <Diretory Path where the binlogs reside>
*
* SCN format: <4 higher order bytes file number><4 low order-bytes binlog offset>
*
*/
public class OpenReplicatorEventProducer extends AbstractEventProducer
{
  public static final Integer DEFAULT_MYSQL_PORT = 3306;
  public static final Pattern PATH_PATTERN = Pattern.compile("/([0-9]+)/[0-9a-zA-Z-]+");

  protected final Logger _log;
  private final OpenReplicator _or;
  private String _binlogFilePrefix;
  private final MaxSCNReaderWriter _maxSCNReaderWriter;
  private final PhysicalSourceStaticConfig _physicalSourceStaticConfig;
  private final SchemaRegistryService _schemaRegistryService;
  private final String _physicalSourceName;
  private EventProducerThread _producerThread;
  private final Map<Integer, OpenReplicatorAvroEventFactory> _eventFactoryMap;
  private final DbusEventsStatisticsCollector _relayInboundStatsCollector;

  // special source to collect global data
  public static final short GLOBAL_SOURCE_ID=0;

  /** Table URI to Source Id Map */
  private final Map<String, Short> _tableUriToSrcIdMap;
  /** Table URI to Source Name Map */
  private final Map<String, String> _tableUriToSrcNameMap;

  private final List<ObjectName> _registeredMbeans = new ArrayList<ObjectName>();
  private final MBeanServer _mbeanServer = ManagementFactory.getPlatformMBeanServer();
  private final String _jmxDomain;

  //list of all sources we are interested in
  private final Map<Short, ORMonitoredSourceInfo> _monitoredSources = new HashMap<Short, ORMonitoredSourceInfo>();

  public OpenReplicatorEventProducer(List<OpenReplicatorAvroEventFactory> eventFactories,
                                     DbusEventBufferAppendable eventBuffer,
                                     MaxSCNReaderWriter maxSCNReaderWriter,
                                     PhysicalSourceStaticConfig physicalSourceStaticConfig,
                                     DbusEventsStatisticsCollector relayInboundStatsCollector,
                                     MBeanServer mbeanServer,
                                     Logger log,
                                     SchemaRegistryService schemaRegistryService,
                                     String jmxDomain)
                                         throws DatabusException, InvalidConfigException
  {
    super(eventBuffer,
          maxSCNReaderWriter,
          physicalSourceStaticConfig,
          mbeanServer);
    _maxSCNReaderWriter = maxSCNReaderWriter;
    _physicalSourceStaticConfig = physicalSourceStaticConfig;
    _physicalSourceName = physicalSourceStaticConfig.getName();
    _log = (null != log) ? log : Logger.getLogger("com.linkedin.databus2.producers.or_" +
        _physicalSourceName);

    _eventFactoryMap = new HashMap<Integer, OpenReplicatorAvroEventFactory>();
    for (OpenReplicatorAvroEventFactory s : eventFactories)
    {
      _eventFactoryMap.put(Integer.valueOf(s.getSourceId()), s);
    }

    _or = new OpenReplicator();
    _jmxDomain = jmxDomain;
    try
    {
      _binlogFilePrefix =  processUri(new URI(physicalSourceStaticConfig.getUri()), _or);
    } catch (URISyntaxException u)
    {
      throw new InvalidConfigException(u);
    }

    _schemaRegistryService = schemaRegistryService;
    _relayInboundStatsCollector = relayInboundStatsCollector;

    _tableUriToSrcIdMap = new HashMap<String, Short>();
    _tableUriToSrcNameMap = new HashMap<String, String>();

    for (LogicalSourceStaticConfig l : _physicalSourceStaticConfig.getSources())
    {
      _tableUriToSrcIdMap.put(l.getUri().toLowerCase(), l.getId());
      _tableUriToSrcNameMap.put(l.getUri().toLowerCase(), l.getName());
      ORMonitoredSourceInfo source = buildORMonitoredSourceInfo(l, _physicalSourceStaticConfig);
      _monitoredSources.put(source.getSourceId(), source);
    }

    // get one fake global source for total stats
    LogicalSourceStaticConfig logicalSourceStaticConfig = new LogicalSourceStaticConfig(GLOBAL_SOURCE_ID, _physicalSourceStaticConfig.getName(), "",
                                                                                        "constant:1", (short)0, false, null, null, null);
    ORMonitoredSourceInfo source = buildORMonitoredSourceInfo(logicalSourceStaticConfig, _physicalSourceStaticConfig);
    _monitoredSources.put(source.getSourceId(), source);
  }

  private ORMonitoredSourceInfo buildORMonitoredSourceInfo( LogicalSourceStaticConfig sourceConfig,
                                                           PhysicalSourceStaticConfig pConfig)
         throws DatabusException, InvalidConfigException
  {
    EventSourceStatistics statisticsBean = new EventSourceStatistics(sourceConfig.getName());

    ORMonitoredSourceInfo sourceInfo =
        new ORMonitoredSourceInfo(sourceConfig.getId(), sourceConfig.getName(), statisticsBean);

    registerMbeans(sourceInfo);

    return sourceInfo;
  }

  /**
   *
   * Responsible for parsing the URI and setting up OpenReplicator connectivity information
   * @param uri Open Replicator URI
   * @param or Open Replicator
   * @return Bin Log Prefix
   * @throws InvalidConfigException if URI is incorrect or missing information
   */
  public static String processUri(URI uri, OpenReplicator or) throws InvalidConfigException
  {
    String userInfo = uri.getUserInfo();
    if (null == userInfo)
      throw new InvalidConfigException("missing user info in: " + uri);
    int slashPos = userInfo.indexOf('/');
    if (slashPos < 0 )
      slashPos = userInfo.length();
    else if (0 == slashPos)
      throw new InvalidConfigException("missing user name in user info: " + userInfo);

    String userName = userInfo.substring(0, slashPos);
    String userPass = slashPos < userInfo.length() - 1 ? userInfo.substring(slashPos + 1) : null;

    String hostName = uri.getHost();

    int port = uri.getPort();
    if (port < 0) port = DEFAULT_MYSQL_PORT;

    String path = uri.getPath();
    if (null == path)
      throw new InvalidConfigException("missing path: " + uri);
    Matcher m = PATH_PATTERN.matcher(path);
    if (!m.matches())
      throw new InvalidConfigException("invalid path:" + path);

    String[] gp = m.group().split("/");
    if (gp.length != 3)
      throw new InvalidConfigException("Invalid format " + Arrays.toString(gp));
    String serverIdStr = gp[1];

    int serverId = -1;
    try
    {
      serverId = Integer.parseInt(serverIdStr);
    }
    catch (NumberFormatException e)
    {
      throw new InvalidConfigException("incorrect mysql serverid:" + serverId);
    }

    // Assign them to incoming variables
    if (null != or)
    {
      or.setUser(userName);
      if (null != userPass) or.setPassword(userPass);
      or.setHost(hostName);
      or.setPort(port);
      or.setServerId(serverId);
    }
    return gp[2];
  }

  /**
   * Returns the logid ( upper 32 bits  of the SCN )
   * For e.g., mysql-bin.000001 is said to have an id 000001
   */
  public static int logid(long scn)
  {
    // During startup, read from the first binlog file
    if (scn == -1 || scn == 0)
    {
      return 1;
    }
    return (int)((scn >> 32) & 0xFFFFFFFF);
  }

  /**
   * Returns the binlogoffset ( lower 32 bits  of the SCN )
   */
  public static int offset(long scn)
  {
    // During startup, read from offset 0 (or should it be 4)
    if (scn == -1 || scn == 0)
    {
      return 4;
    }
    return (int)(scn & 0xFFFFFFFF);
  }


  /* (non-Javadoc)
   * @see com.linkedin.databus2.producers.AbstractEventProducer#start(long)
   */
  @Override
  public synchronized void start(long sinceSCN)
  {
    long sinceSCNToUse = 0;

    if (sinceSCN > 0)
    {
      sinceSCNToUse = sinceSCN;
    }
    else
    {
      /*
       * If the maxScn is persisted in maxSCN file and is greater than 0, then honor it.
       * Else use sinceSCN passed in from above which is -1 or 0
       * Currently, both of them mean to start from the first binlog file with filenum 000001, and offset 4
       */
      if (_maxSCNReaderWriter != null)
      {
        try
        {
          long scn = _maxSCNReaderWriter.getMaxScn();
          if(scn > 0)
          {
            sinceSCNToUse = scn;
          }
        }
        catch (DatabusException e)
        {
          _log.warn("Could not read saved maxScn: Defaulting to startSCN="+ sinceSCNToUse);
        }
      }
    }
    _producerThread = new EventProducerThread(_physicalSourceName, sinceSCNToUse);
    _producerThread.start();
  }

  public class EventProducerThread extends DatabusThreadBase implements TransactionProcessor
  {
    // The scn with which the event buffer is started
    private final AtomicLong _startPrevScn = new AtomicLong(-1);

    private final long _sinceScn;

    public EventProducerThread(String sourceName, long sinceScn)
    {
      super("OpenReplicator_" + sourceName);
      _sinceScn = sinceScn;
    }

    @Override
    public void run()
    {
      _eventBuffer.start(_sinceScn);
      _startPrevScn.set(_sinceScn);

      int offset = offset(_sinceScn);
      int logid = logid(_sinceScn);

      String binlogFile = String.format("%s.%06d", _binlogFilePrefix, logid);
      ORListener orl = new ORListener(logid, _log, _binlogFilePrefix,
          _producerThread, _tableUriToSrcIdMap, _tableUriToSrcNameMap,
          _schemaRegistryService);

      _or.setBinlogFileName(binlogFile);
      _or.setBinlogPosition(offset);
      _or.setBinlogEventListener(orl);

      try
      {
        _log.info(String.format("Open Replicator starting from %s@%d", binlogFile, offset));
        _or.start();
      } catch (Exception e)
      {
        throw new DatabusRuntimeException("failed to start open replicator: " + e.getMessage(), e);
      }
      _log.info("Event Producer Thread done");

    }

    @Override
    public void onEndTransaction(Transaction txn)
        throws DatabusException
    {
      if (! isShutdownRequested())
      {
        if (isPauseRequested())
        {
          LOG.info("Pause requested for OpenReplicator. Pausing !!");
          signalPause();
          LOG.info("Pausing. Waiting for resume command");
          try { awaitUnPauseRequest(); } catch (InterruptedException e) { _log.info("Interrupted !!"); }
          LOG.info("Resuming OpenReplicator !!");
          signalResumed();
          LOG.info("OpenReplicator resumed !!");
        }

        try
        {
          addTxnToBuffer(txn);
          _maxSCNReaderWriter.saveMaxScn(txn.getScn());
        }
        catch (UnsupportedKeyException e)
        {
          _log.fatal("Got UnsupportedKeyException exception while adding txn (" + txn + ") to the buffer", e);
          throw new DatabusException(e);
        }
        catch (EventCreationException e)
        {
          _log.fatal("Got EventCreationException exception while adding txn (" + txn + ") to the buffer", e);
          throw new DatabusException(e);
        }
      }

      if (isShutdownRequested())
      {
        try
        {
          _or.stop(10, TimeUnit.SECONDS);
        }
        catch (Exception e)
        {
          _log.error("Got exception while stopping open replicator",e);
        }
        doShutdownNotify();
        return;
      }
    }

    /**
     * Add all txn events to Buffer
     * @param txn Transaction to be added
     * @throws EventCreationException
     */
    private void addTxnToBuffer(Transaction txn)
        throws DatabusException, UnsupportedKeyException, EventCreationException
    {
      /**
       * We skip the start scn of the relay, we have already added a EOP for this SCN in the buffer.
       * Why is this not a problem ?
       * There are two cases:
       * 1. When we use the earliest/latest scn if there is no maxScn (We don't really have a start point). So it's really OK to miss the first event.
       * 2. If it's the maxSCN, then event was already seen by the relay.
       */
      if(txn.getScn() <= _startPrevScn.get())
      {
        _log.info("Skipping this transaction, EOP already send for this event. Txn SCN =" + txn.getScn()
                                    + ", _startPrevScn=" + _startPrevScn.get());
        return;
      }

      EventSourceStatistics globalStats = getSource(GLOBAL_SOURCE_ID).getStatisticsBean();

      _eventBuffer.startEvents();

      long scn = txn.getScn();
      long timestamp = txn.getTxnNanoTimestamp();
      List<EventReaderSummary> summaries = new ArrayList<EventReaderSummary>();

      for (PerSourceTransaction t: txn.getOrderedPerSourceTransactions() )
      {
        long startDbUpdatesMs = System.currentTimeMillis();
        short sourceId = (short)t.getSrcId();
        EventSourceStatistics perSourceStats = getSource(sourceId).getStatisticsBean();
        long dbUpdatesEventsSize = 0;

        for (DbChangeEntry c : t.getDbChangeEntrySet())
        {
          int length = 0;

          try
          {
            length = _eventFactoryMap.get(t.getSrcId()).createAndAppendEvent(
                                                                  c,
                                                                  _eventBuffer,
                                                                  false,
                                                                  _relayInboundStatsCollector);
            if ( length < 0)
            {
              _log.error("Unable to append DBChangeEntry (" + c + ") to event buffer !! EVB State : " + _eventBuffer);
              throw new DatabusException("Unable to append DBChangeEntry (" + c + ") to event buffer !!");
            }
            dbUpdatesEventsSize += length;
          } catch (DatabusException e) {
            _log.error("Got databus exception :", e);
            perSourceStats.addError();
            globalStats.addEmptyEventCycle();
            throw e;
          } catch (UnsupportedKeyException e) {
            perSourceStats.addError();
            globalStats.addEmptyEventCycle();
            _log.error("Got UnsupportedKeyException :", e);
            throw e;
          } catch (EventCreationException e) {
            perSourceStats.addError();
            globalStats.addEmptyEventCycle();
            _log.error("Got EventCreationException :", e);
            throw e;
          }
          perSourceStats.addEventCycle(1, txn.getTxnReadLatencyNanos(), length, scn);
          globalStats.addEventCycle(1, txn.getTxnReadLatencyNanos(), length, scn);
        }

        long endDbUpdatesMs = System.currentTimeMillis();
        long dbUpdatesElapsedTimeMs = endDbUpdatesMs - startDbUpdatesMs;

        // Log Event Summary at logical source level
        EventReaderSummary summary = new EventReaderSummary(sourceId, _monitoredSources.get(sourceId).getSourceName(),
                                                            scn, t.getDbChangeEntrySet().size(), dbUpdatesEventsSize,-1L /* Not supported */,
                                                            dbUpdatesElapsedTimeMs,  timestamp, timestamp, -1L /* Not supported */);
        if (_eventsLog.isInfoEnabled())
        {
          _eventsLog.info(summary.toString());
        }
        summaries.add(summary);

        long tsEnd = System.currentTimeMillis();
        perSourceStats.addTimeOfLastDBAccess(tsEnd);
        globalStats.addTimeOfLastDBAccess(tsEnd);
      }
      _eventBuffer.endEvents(scn, _relayInboundStatsCollector);
      // Log Event Summary at Physical source level
      ReadEventCycleSummary summary = new ReadEventCycleSummary(_physicalSourceStaticConfig.getName(),
                                                                summaries,
                                                                scn,
                                                                -1 /* Overall time including query time not calculated */);

      if (_eventsLog.isInfoEnabled())
      {
        _eventsLog.info(summary.toString());
      }
    }
  }

  /**
   * Since we have a push architecture, this is used only for logging.
   * @see com.linkedin.databus2.producers.AbstractEventProducer#readEventsFromAllSources(long)
   */
  @Override
  protected ReadEventCycleSummary readEventsFromAllSources(long sinceSCN) throws DatabusException,
  EventCreationException,
  UnsupportedKeyException
  {
    throw new RuntimeException("Not supported !!");
  }


  /* (non-Javadoc)
   * @see com.linkedin.databus2.producers.AbstractEventProducer#unpause()
   */
  @Override
  public synchronized void unpause()
  {
    try
    {
      _producerThread.unpause();
    }
    catch (InterruptedException e)
    {
      _log.info("Interrupted while unpausing EventProducer", e);
    }
    super.unpause();
  }

  /* (non-Javadoc)
   * @see com.linkedin.databus2.producers.AbstractEventProducer#pause()
   */
  @Override
  public synchronized void pause()
  {
    try
    {
      _producerThread.pause();
    }
    catch (InterruptedException e)
    {
      _log.info("Interrupted while pausing EventProducer", e);
    }    super.pause();
  }

  /* (non-Javadoc)
   * @see com.linkedin.databus2.producers.AbstractEventProducer#shutdown()
   */
  @Override
  public synchronized void shutdown()
  {
    _producerThread.shutdown();
    super.shutdown();

    for (ObjectName name:_registeredMbeans)
    {
      try {
        _mbeanServer.unregisterMBean(name);
        _log.info("Unregistered or-source mbean: " + name);
      } catch (MBeanRegistrationException e) {
        _log.warn("Exception when unregistering or-source statistics mbean: " + name + e) ;
      } catch (InstanceNotFoundException e) {
        _log.warn("Exception when unregistering or-source statistics mbean: " + name + e) ;
      }
    }
  }

  @Override
  public List<? extends EventSourceStatisticsIface> getSources()
  {
    return new ArrayList<ORMonitoredSourceInfo>(_monitoredSources.values());
  }

  // register each source with the mbeanServer
  private void registerMbeans(ORMonitoredSourceInfo source) throws DatabusException
  {
    try
    {
      Hashtable<String,String> props = new Hashtable<String,String>();
      props.put("type", "SourceStatistics");
      props.put("name", source.getSourceName());
      ObjectName objectName = new ObjectName(_jmxDomain, props);

      if (_mbeanServer.isRegistered(objectName))
      {
        _log.warn("Unregistering old or-source statistics mbean: " + objectName);
        _mbeanServer.unregisterMBean(objectName);
      }

      _mbeanServer.registerMBean(source.getStatisticsBean(), objectName);
      _log.info("Registered or-source statistics mbean: " + objectName);
      _registeredMbeans.add(objectName);
    }
    catch(Exception ex)
    {
      _log.error("Failed to register the or-source statistics mbean for source (" + source.getSourceName() + ") due to an exception.", ex);
      throw new DatabusException("Failed to initialize or event statistics mbeans.", ex);
    }
  }

  /**
   * return MonitoredSourceInfo per source
   * @param sourceId
   * @return MonitoredSourceInof for this source id
   */
  public ORMonitoredSourceInfo getSource(short sourceId) {
    return _monitoredSources.get(sourceId);
  }
}
TOP

Related Classes of com.linkedin.databus2.producers.OpenReplicatorEventProducer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.