package org.apache.cassandra.streaming; /* * * 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. * */ import java.net.InetAddress; import java.util.*; import java.io.IOException; import java.io.File; import java.io.IOError; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.apache.log4j.Logger; import org.apache.commons.lang.StringUtils; import org.apache.cassandra.dht.Range; import org.apache.cassandra.db.Table; import org.apache.cassandra.io.SSTable; import org.apache.cassandra.io.SSTableReader; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessagingService; /** * This class handles streaming data from one node to another. * * For bootstrap, * 1. BOOTSTRAP_TOKEN asks the most-loaded node what Token to use to split its Range in two. * 2. STREAM_REQUEST tells source nodes to send us the necessary Ranges * 3. source nodes send STREAM_INITIATE to us to say "get ready to receive data" [if there is data to send] * 4. when we have everything set up to receive the data, we send STREAM_INITIATE_DONE back to the source nodes and they start streaming * 5. when streaming is complete, we send STREAM_FINISHED to the source so it can clean up on its end * * For unbootstrap, the leaving node starts with step 3 (1 and 2 are skipped entirely). This is why * STREAM_INITIATE is a separate verb, rather than just a reply to STREAM_REQUEST; the REQUEST is optional. */ public class StreamOut { private static Logger logger = Logger.getLogger(StreamOut.class); static String TABLE_NAME = "STREAMING-TABLE-NAME"; /** * Split out files for all tables on disk locally for each range and then stream them to the target endpoint. */ public static void transferRanges(InetAddress target, String tableName, Collection<Range> ranges, Runnable callback) { assert ranges.size() > 0; logger.debug("Beginning transfer process to " + target + " for ranges " + StringUtils.join(ranges, ", ")); /* * (1) dump all the memtables to disk. * (2) anticompaction -- split out the keys in the range specified * (3) transfer the data. */ try { Table table = Table.open(tableName); logger.info("Flushing memtables for " + tableName + "..."); for (Future f : table.flush()) { try { f.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } } logger.info("Performing anticompaction ..."); /* Get the list of files that need to be streamed */ transferSSTables(target, table.forceAntiCompaction(ranges, target), tableName); // SSTR GC deletes the file when done } catch (IOException e) { throw new IOError(e); } finally { StreamOutManager.remove(target); } if (callback != null) callback.run(); } /** * Transfers a group of sstables from a single table to the target endpoint * and then marks them as ready for local deletion. */ public static void transferSSTables(InetAddress target, List<SSTableReader> sstables, String table) throws IOException { PendingFile[] pendingFiles = new PendingFile[SSTable.FILES_ON_DISK * sstables.size()]; int i = 0; for (SSTableReader sstable : sstables) { for (String filename : sstable.getAllFilenames()) { File file = new File(filename); pendingFiles[i++] = new PendingFile(file.getAbsolutePath(), file.length(), table); } } logger.info("Stream context metadata " + StringUtils.join(pendingFiles, ", " + " " + sstables.size() + " sstables.")); StreamOutManager.get(target).addFilesToStream(pendingFiles); StreamInitiateMessage biMessage = new StreamInitiateMessage(pendingFiles); Message message = StreamInitiateMessage.makeStreamInitiateMessage(biMessage); message.setHeader(StreamOut.TABLE_NAME, table.getBytes()); logger.info("Sending a stream initiate message to " + target + " ..."); MessagingService.instance.sendOneWay(message, target); if (pendingFiles.length > 0) { logger.info("Waiting for transfer to " + target + " to complete"); StreamOutManager.get(target).waitForStreamCompletion(); // todo: it would be good if there were a dafe way to remove the StreamManager for target. // (StreamManager will delete the streamed file on completion.) logger.info("Done with transfer to " + target); } } }