View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.replication.regionserver;
20  
21  import java.io.EOFException;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.BlockingQueue;
28  import java.util.concurrent.LinkedBlockingQueue;
29  import java.util.concurrent.PriorityBlockingQueue;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.fs.FileSystem;
35  import org.apache.hadoop.fs.Path;
36  import org.apache.hadoop.hbase.Cell;
37  import org.apache.hadoop.hbase.CellUtil;
38  import org.apache.hadoop.hbase.classification.InterfaceAudience;
39  import org.apache.hadoop.hbase.classification.InterfaceStability;
40  import org.apache.hadoop.hbase.protobuf.generated.WALProtos.BulkLoadDescriptor;
41  import org.apache.hadoop.hbase.protobuf.generated.WALProtos.StoreDescriptor;
42  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
43  import org.apache.hadoop.hbase.replication.ReplicationQueueInfo;
44  import org.apache.hadoop.hbase.replication.WALEntryFilter;
45  import org.apache.hadoop.hbase.replication.regionserver.WALEntryStream.WALEntryStreamRuntimeException;
46  import org.apache.hadoop.hbase.util.Pair;
47  import org.apache.hadoop.hbase.util.Threads;
48  import org.apache.hadoop.hbase.wal.WAL.Entry;
49  
50  /**
51   * Reads and filters WAL entries, groups the filtered entries into batches, and puts the batches onto a queue
52   *
53   */
54  @InterfaceAudience.Private
55  @InterfaceStability.Evolving
56  public class ReplicationSourceWALReaderThread extends Thread {
57    private static final Log LOG = LogFactory.getLog(ReplicationSourceWALReaderThread.class);
58  
59    private PriorityBlockingQueue<Path> logQueue;
60    private FileSystem fs;
61    private Configuration conf;
62    private BlockingQueue<WALEntryBatch> entryBatchQueue;
63    // max (heap) size of each batch - multiply by number of batches in queue to get total
64    private long replicationBatchSizeCapacity;
65    // max count of each batch - multiply by number of batches in queue to get total
66    private int replicationBatchCountCapacity;
67    // position in the WAL to start reading at
68    private long currentPosition;
69    private WALEntryFilter filter;
70    private long sleepForRetries;
71    //Indicates whether this particular worker is running
72    private boolean isReaderRunning = true;
73    private ReplicationQueueInfo replicationQueueInfo;
74    private int maxRetriesMultiplier;
75    private MetricsSource metrics;
76  
77    /**
78     * Creates a reader worker for a given WAL queue. Reads WAL entries off a given queue, batches the
79     * entries, and puts them on a batch queue.
80     * @param manager replication manager
81     * @param replicationQueueInfo
82     * @param logQueue The WAL queue to read off of
83     * @param startPosition position in the first WAL to start reading from
84     * @param fs the files system to use
85     * @param conf configuration to use
86     * @param filter The filter to use while reading
87     * @param metrics replication metrics
88     */
89    public ReplicationSourceWALReaderThread(ReplicationSourceManager manager,
90        ReplicationQueueInfo replicationQueueInfo, PriorityBlockingQueue<Path> logQueue,
91        long startPosition,
92        FileSystem fs, Configuration conf, WALEntryFilter filter, MetricsSource metrics) {
93      this.replicationQueueInfo = replicationQueueInfo;
94      this.logQueue = logQueue;
95      this.currentPosition = startPosition;
96      this.fs = fs;
97      this.conf = conf;
98      this.filter = filter;
99      this.replicationBatchSizeCapacity =
100         this.conf.getLong("replication.source.size.capacity", 1024 * 1024 * 64);
101     this.replicationBatchCountCapacity = this.conf.getInt("replication.source.nb.capacity", 25000);
102     // memory used will be batchSizeCapacity * (nb.batches + 1)
103     // the +1 is for the current thread reading before placing onto the queue
104     int batchCount = conf.getInt("replication.source.nb.batches", 1);
105     this.sleepForRetries =
106         this.conf.getLong("replication.source.sleepforretries", 1000);    // 1 second
107     this.maxRetriesMultiplier =
108         this.conf.getInt("replication.source.maxretriesmultiplier", 300); // 5 minutes @ 1 sec per
109     this.metrics = metrics;
110     this.entryBatchQueue = new LinkedBlockingQueue<>(batchCount);
111     LOG.info("peerClusterZnode=" + replicationQueueInfo.getPeerClusterZnode()
112         + ", ReplicationSourceWALReaderThread : " + replicationQueueInfo.getPeerId()
113         + " inited, replicationBatchSizeCapacity=" + replicationBatchSizeCapacity
114         + ", replicationBatchCountCapacity=" + replicationBatchCountCapacity
115         + ", replicationBatchQueueCapacity=" + batchCount);
116   }
117 
118   @Override
119   public void run() {
120     int sleepMultiplier = 1;
121     while (isReaderRunning()) { // we only loop back here if something fatal happened to our stream
122       try (WALEntryStream entryStream =
123           new WALEntryStream(logQueue, fs, conf, currentPosition, metrics)) {
124         while (isReaderRunning()) { // loop here to keep reusing stream while we can
125           WALEntryBatch batch = null;
126           while (entryStream.hasNext()) {
127             if (batch == null) {
128               batch = new WALEntryBatch(replicationBatchCountCapacity, entryStream.getCurrentPath());
129             }
130             Entry entry = entryStream.next();
131             entry = filterEntry(entry);
132             if (entry != null) {
133               WALEdit edit = entry.getEdit();
134               if (edit != null && !edit.isEmpty()) {
135                 long entrySize = getEntrySize(entry);
136                 batch.addEntry(entry);
137                 updateBatchStats(batch, entry, entryStream.getPosition(), entrySize);
138                 // Stop if too many entries or too big
139                 if (batch.getHeapSize() >= replicationBatchSizeCapacity
140                     || batch.getNbEntries() >= replicationBatchCountCapacity) {
141                   break;
142                 }
143               }
144             }
145           }
146           if (batch != null && (!batch.getLastSeqIds().isEmpty() || batch.getNbEntries() > 0)) {
147             if (LOG.isTraceEnabled()) {
148               LOG.trace(String.format("Read %s WAL entries eligible for replication",
149                 batch.getNbEntries()));
150             }
151             entryBatchQueue.put(batch);
152             sleepMultiplier = 1;
153           } else { // got no entries and didn't advance position in WAL
154             LOG.trace("Didn't read any new entries from WAL");
155             if (replicationQueueInfo.isQueueRecovered()) {
156               // we're done with queue recovery, shut ourself down
157               setReaderRunning(false);
158               // shuts down shipper thread immediately
159               entryBatchQueue.put(batch != null ? batch
160                   : new WALEntryBatch(replicationBatchCountCapacity, entryStream.getCurrentPath()));
161             } else {
162               Thread.sleep(sleepForRetries);
163             }
164           }
165           currentPosition = entryStream.getPosition();
166           entryStream.reset(); // reuse stream
167         }
168       } catch (IOException | WALEntryStreamRuntimeException e) { // stream related
169         if (sleepMultiplier < maxRetriesMultiplier) {
170           LOG.debug("Failed to read stream of replication entries: " + e);
171           sleepMultiplier++;
172         } else {
173           LOG.error("Failed to read stream of replication entries", e);
174           handleEofException(e);
175         }
176         Threads.sleep(sleepForRetries * sleepMultiplier);
177       } catch (InterruptedException e) {
178         LOG.trace("Interrupted while sleeping between WAL reads");
179         Thread.currentThread().interrupt();
180       }
181     }
182   }
183 
184   // if we get an EOF due to a zero-length log, and there are other logs in queue
185   // (highly likely we've closed the current log), we've hit the max retries, and autorecovery is
186   // enabled, then dump the log
187   private void handleEofException(Exception e) {
188     if (e.getCause() instanceof EOFException && logQueue.size() > 1
189         && conf.getBoolean("replication.source.eof.autorecovery", false)) {
190       try {
191         if (fs.getFileStatus(logQueue.peek()).getLen() == 0) {
192           LOG.warn("Forcing removal of 0 length log in queue: " + logQueue.peek());
193           logQueue.remove();
194           currentPosition = 0;
195         }
196       } catch (IOException ioe) {
197         LOG.warn("Couldn't get file length information about log " + logQueue.peek());
198       }
199     }
200   }
201 
202   public Path getCurrentPath() {
203     // if we've read some WAL entries, get the Path we read from
204     WALEntryBatch batchQueueHead = entryBatchQueue.peek();
205     if (batchQueueHead != null) {
206       return batchQueueHead.lastWalPath;
207     }
208     // otherwise, we must be currently reading from the head of the log queue
209     return logQueue.peek();
210   }
211 
212   private Entry filterEntry(Entry entry) {
213     Entry filtered = filter.filter(entry);
214     if (entry != null && filtered == null) {
215       metrics.incrLogEditsFiltered();
216     }
217     return filtered;
218   }
219 
220   /**
221    * Retrieves the next batch of WAL entries from the queue, waiting up to the specified time for a
222    * batch to become available
223    * @return A batch of entries, along with the position in the log after reading the batch
224    * @throws InterruptedException if interrupted while waiting
225    */
226   public WALEntryBatch take() throws InterruptedException {
227     return entryBatchQueue.take();
228   }
229 
230   private long getEntrySize(Entry entry) {
231     WALEdit edit = entry.getEdit();
232     return edit.heapSize() + calculateTotalSizeOfStoreFiles(edit);
233   }
234 
235   private void updateBatchStats(WALEntryBatch batch, Entry entry, long entryPosition, long entrySize) {
236     WALEdit edit = entry.getEdit();
237     if (edit != null && !edit.isEmpty()) {
238       batch.incrementHeapSize(entrySize);
239       Pair<Integer, Integer> nbRowsAndHFiles = countDistinctRowKeysAndHFiles(edit);
240       batch.incrementNbRowKeys(nbRowsAndHFiles.getFirst());
241       batch.incrementNbHFiles(nbRowsAndHFiles.getSecond());
242     }
243     batch.lastWalPosition = entryPosition;
244   }
245 
246   /**
247    * Count the number of different row keys in the given edit because of mini-batching. We assume
248    * that there's at least one Cell in the WALEdit.
249    * @param edit edit to count row keys from
250    * @return number of different row keys and HFiles
251    */
252   private Pair<Integer, Integer> countDistinctRowKeysAndHFiles(WALEdit edit) {
253     List<Cell> cells = edit.getCells();
254     int distinctRowKeys = 1;
255     int totalHFileEntries = 0;
256     Cell lastCell = cells.get(0);
257 
258     int totalCells = edit.size();
259     for (int i = 0; i < totalCells; i++) {
260       // Count HFiles to be replicated
261       if (CellUtil.matchingQualifier(cells.get(i), WALEdit.BULK_LOAD)) {
262         try {
263           BulkLoadDescriptor bld = WALEdit.getBulkLoadDescriptor(cells.get(i));
264           List<StoreDescriptor> stores = bld.getStoresList();
265           int totalStores = stores.size();
266           for (int j = 0; j < totalStores; j++) {
267             totalHFileEntries += stores.get(j).getStoreFileList().size();
268           }
269         } catch (IOException e) {
270           LOG.error("Failed to deserialize bulk load entry from wal edit. "
271               + "Then its hfiles count will not be added into metric.");
272         }
273       }
274 
275       if (!CellUtil.matchingRow(cells.get(i), lastCell)) {
276         distinctRowKeys++;
277       }
278       lastCell = cells.get(i);
279     }
280 
281     Pair<Integer, Integer> result = new Pair<>(distinctRowKeys, totalHFileEntries);
282     return result;
283   }
284 
285   /**
286    * Calculate the total size of all the store files
287    * @param edit edit to count row keys from
288    * @return the total size of the store files
289    */
290   private int calculateTotalSizeOfStoreFiles(WALEdit edit) {
291     List<Cell> cells = edit.getCells();
292     int totalStoreFilesSize = 0;
293 
294     int totalCells = edit.size();
295     for (int i = 0; i < totalCells; i++) {
296       if (CellUtil.matchingQualifier(cells.get(i), WALEdit.BULK_LOAD)) {
297         try {
298           BulkLoadDescriptor bld = WALEdit.getBulkLoadDescriptor(cells.get(i));
299           List<StoreDescriptor> stores = bld.getStoresList();
300           int totalStores = stores.size();
301           for (int j = 0; j < totalStores; j++) {
302             totalStoreFilesSize += stores.get(j).getStoreFileSizeBytes();
303           }
304         } catch (IOException e) {
305           LOG.error("Failed to deserialize bulk load entry from wal edit. "
306               + "Size of HFiles part of cell will not be considered in replication "
307               + "request size calculation.",
308             e);
309         }
310       }
311     }
312     return totalStoreFilesSize;
313   }
314 
315   /**
316    * @return whether the reader thread is running
317    */
318   public boolean isReaderRunning() {
319     return isReaderRunning && !isInterrupted();
320   }
321 
322   /**
323    * @param readerRunning the readerRunning to set
324    */
325   public void setReaderRunning(boolean readerRunning) {
326     this.isReaderRunning = readerRunning;
327   }
328 
329   /**
330    * Holds a batch of WAL entries to replicate, along with some statistics
331    *
332    */
333   static class WALEntryBatch {
334     private List<Entry> walEntries;
335     // last WAL that was read
336     private Path lastWalPath;
337     // position in WAL of last entry in this batch
338     private long lastWalPosition = 0;
339     // number of distinct row keys in this batch
340     private int nbRowKeys = 0;
341     // number of HFiles
342     private int nbHFiles = 0;
343     // heap size of data we need to replicate
344     private long heapSize = 0;
345     // save the last sequenceid for each region if the table has serial-replication scope
346     private Map<String, Long> lastSeqIds = new HashMap<>();
347 
348     /**
349      * @param walEntries
350      * @param lastWalPath Path of the WAL the last entry in this batch was read from
351      * @param lastWalPosition Position in the WAL the last entry in this batch was read from
352      */
353     private WALEntryBatch(int maxNbEntries, Path lastWalPath) {
354       this.walEntries = new ArrayList<>(maxNbEntries);
355       this.lastWalPath = lastWalPath;
356     }
357 
358     public void addEntry(Entry entry) {
359       walEntries.add(entry);
360     }
361 
362     /**
363      * @return the WAL Entries.
364      */
365     public List<Entry> getWalEntries() {
366       return walEntries;
367     }
368 
369     /**
370      * @return the path of the last WAL that was read.
371      */
372     public Path getLastWalPath() {
373       return lastWalPath;
374     }
375 
376     /**
377      * @return the position in the last WAL that was read.
378      */
379     public long getLastWalPosition() {
380       return lastWalPosition;
381     }
382 
383     public int getNbEntries() {
384       return walEntries.size();
385     }
386 
387     /**
388      * @return the number of distinct row keys in this batch
389      */
390     public int getNbRowKeys() {
391       return nbRowKeys;
392     }
393 
394     /**
395      * @return the number of HFiles in this batch
396      */
397     public int getNbHFiles() {
398       return nbHFiles;
399     }
400 
401     /**
402      * @return total number of operations in this batch
403      */
404     public int getNbOperations() {
405       return getNbRowKeys() + getNbHFiles();
406     }
407 
408     /**
409      * @return the heap size of this batch
410      */
411     public long getHeapSize() {
412       return heapSize;
413     }
414 
415     /**
416      * @return the last sequenceid for each region if the table has serial-replication scope
417      */
418     public Map<String, Long> getLastSeqIds() {
419       return lastSeqIds;
420     }
421 
422     private void incrementNbRowKeys(int increment) {
423       nbRowKeys += increment;
424     }
425 
426     private void incrementNbHFiles(int increment) {
427       nbHFiles += increment;
428     }
429 
430     private void incrementHeapSize(long increment) {
431       heapSize += increment;
432     }
433 
434     private void setLastPosition(String region, Long sequenceId) {
435       getLastSeqIds().put(region, sequenceId);
436     }
437   }
438 }