/* * @(#) HintLogHandoffManager.java * Created Aug 31, 2011 by oleg * (C) ONE, SIA */ package org.apache.cassandra.db.hints; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.HintedHandOffManager; import org.apache.cassandra.db.RowMutation; import org.apache.cassandra.gms.FailureDetector; import org.apache.cassandra.net.IAsyncResult; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.service.DigestMismatchException; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.thrift.InvalidRequestException; import org.apache.log4j.Logger; /** * Implementation of {@link HintedHandOffManager} using HintLogs for hist storage. * * @author Oleg Anastasyev<oa@hq.one.lv> * */ public class HintLogHandoffManager extends HintedHandOffManager { private static final Logger logger_ = Logger.getLogger(HintedHandOffManager.class); /* (non-Javadoc) * @see org.apache.cassandra.db.HintedHandOffManager#deliverHintsToEndpoint(java.net.InetAddress) */ @Override protected void deliverHintsToEndpoint(InetAddress endPoint) throws IOException, DigestMismatchException, InvalidRequestException, TimeoutException { queuedDeliveries.remove(endPoint); if (logger_.isDebugEnabled()) logger_.debug("Check hintlog for deliverables for endPoint " + endPoint.getHostAddress()); if (!FailureDetector.instance.isAlive(endPoint)) { logger_.info("Hints delivery to "+endPoint.getHostAddress()+" is cancelled - endpoint is dead. Will restart as soon as it gets UP again"); return; } if (!StorageService.instance.getTokenMetadata().isMember(endPoint)) { // this is bootstrapping/decommissioned node return; } long started=System.currentTimeMillis(); long counter = 0; Iterator<List<byte[]>> hintsToDeliver = HintLog.instance().getHintsToDeliver(endPoint); String throttleRaw = System.getProperty("hinted_handoff_throttle"); int throttle = throttleRaw == null ? 0 : Integer.valueOf(throttleRaw); if (hintsToDeliver.hasNext()) logger_.info("Started hinted handoff for endPoint " + endPoint.getHostAddress()); HINT_DELIVERY: while (hintsToDeliver.hasNext()) { List<byte[]> rm = hintsToDeliver.next(); int leftRetries = 10; long timeout = DatabaseDescriptor.getRpcTimeout(); while (!deliverHint(endPoint, rm, timeout)) { leftRetries --; if (leftRetries == 0){ logger_.error("Hint delivery skipped to "+endPoint.getHostAddress() +" due to multiple errors"); break; } // may be this is temporary problem. Trying to pause for some time. try { Thread.sleep(DatabaseDescriptor.getRpcTimeout()); } catch (InterruptedException e) { break HINT_DELIVERY; } // checking, is endpoint still in ring if (!FailureDetector.instance.isAlive(endPoint)) { logger_.info("Hints delivery to "+endPoint.getHostAddress()+" is cancelled - endpoint is dead. Will restart as soon as it gets UP again"); break HINT_DELIVERY; } //каждый следующий раз увеличиваем таймаут в 2 раза timeout += timeout; } hintsToDeliver.remove(); counter +=rm.size(); if (throttle>0) { try { Thread.sleep(throttle); } catch (InterruptedException e) { throw new AssertionError(e); } } } if (counter>0) logger_.info("Finished hinted handoff for endPoint " + endPoint.getHostAddress() + " total "+counter+" mutations delivered in " + (System.currentTimeMillis()-started)/1000+" seconds"); else logger_.info("Finished hinted handoff check for endPoint " + endPoint.getHostAddress() + " in " + (System.currentTimeMillis()-started)/1000+" seconds"); } private boolean deliverHint(InetAddress endPoint, List<byte[]> rm, long timeout) throws IOException { assert !rm.isEmpty() : "HintLog Reader issued empty list for "+endPoint+", which must never happen"; int packSize = rm.size(); ArrayList<IAsyncResult> results = new ArrayList<IAsyncResult>(packSize); long bytesSize = 0; for (byte[] b : rm ) { Message message = RowMutation.makeRowMutationMessage(b); IAsyncResult result = MessagingService.instance.sendRR(message, endPoint, false /* we don't want this hint to be saved again on timeout **/); results.add(result); bytesSize += b.length; } long sentMillis = System.currentTimeMillis(); // going from tail to head for index number stability int i = results.size(); while (i-->0) { IAsyncResult result = results.get(i); if (!result.isDone()) { try { result.get(timeout-System.currentTimeMillis()+sentMillis, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { removeAppliedHints(rm,results,i); logger_.error ("Timeout sending hint pack to "+endPoint+", size = "+packSize+", bytes = "+bytesSize+", "+rm.size()+" hints were not applied"); return rm.isEmpty(); } } } return true; } private void removeAppliedHints(List<byte[]> rm, ArrayList<IAsyncResult> results, int i) { while (i>=0) { if (results.get(i).isDone()) { rm.remove(i); } i--; } } /** * Stores new hint for later delivery * * @param hint * @param rm * @throws IOException */ public void storeHint(InetAddress hint, RowMutation rm, byte[] serializedMutation) throws IOException { HintLog.instance().add(hint, serializedMutation); }; }