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  
20  
21  package org.apache.hadoop.hbase.wal;
22  
23  import java.io.IOException;
24  import java.util.Arrays;
25  import java.io.InterruptedIOException;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.concurrent.atomic.AtomicReference;
29  
30  import com.google.common.annotations.VisibleForTesting;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.hbase.classification.InterfaceAudience;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.fs.FSDataInputStream;
36  import org.apache.hadoop.fs.FileSystem;
37  import org.apache.hadoop.fs.Path;
38  import org.apache.hadoop.hbase.HConstants;
39  import org.apache.hadoop.hbase.wal.WAL.Reader;
40  import org.apache.hadoop.hbase.wal.WALProvider.Writer;
41  import org.apache.hadoop.hbase.util.CancelableProgressable;
42  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
43  import org.apache.hadoop.hbase.util.LeaseNotRecoveredException;
44  
45  // imports for things that haven't moved from regionserver.wal yet.
46  import org.apache.hadoop.hbase.regionserver.wal.MetricsWAL;
47  import org.apache.hadoop.hbase.regionserver.wal.ProtobufLogReader;
48  import org.apache.hadoop.hbase.regionserver.wal.SequenceFileLogReader;
49  import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
50  
51  /**
52   * Entry point for users of the Write Ahead Log.
53   * Acts as the shim between internal use and the particular WALProvider we use to handle wal
54   * requests.
55   *
56   * Configure which provider gets used with the configuration setting "hbase.wal.provider". Available
57   * implementations:
58   * <ul>
59   *   <li><em>defaultProvider</em> : whatever provider is standard for the hbase version. Currently
60   *                                  "filesystem"</li>
61   *   <li><em>filesystem</em> : a provider that will run on top of an implementation of the Hadoop
62   *                             FileSystem interface, normally HDFS.</li>
63   *   <li><em>multiwal</em> : a provider that will use multiple "filesystem" wal instances per region
64   *                           server.</li>
65   * </ul>
66   *
67   * Alternatively, you may provide a custome implementation of {@link WALProvider} by class name.
68   */
69  @InterfaceAudience.Private
70  public class WALFactory {
71  
72    private static final Log LOG = LogFactory.getLog(WALFactory.class);
73  
74    /**
75     * Maps between configuration names for providers and implementation classes.
76     */
77    static enum Providers {
78      defaultProvider(DefaultWALProvider.class),
79      filesystem(DefaultWALProvider.class),
80      multiwal(RegionGroupingProvider.class);
81  
82      final Class<? extends WALProvider> clazz;
83  
84      Providers(Class<? extends WALProvider> clazz) {
85        this.clazz = clazz;
86      }
87    }
88  
89    public static final String WAL_PROVIDER = "hbase.wal.provider";
90    static final String DEFAULT_WAL_PROVIDER = Providers.defaultProvider.name();
91  
92    static final String META_WAL_PROVIDER = "hbase.wal.meta_provider";
93    static final String DEFAULT_META_WAL_PROVIDER = Providers.defaultProvider.name();
94  
95    final String factoryId;
96    final WALProvider provider;
97    // The meta updates are written to a different wal. If this
98    // regionserver holds meta regions, then this ref will be non-null.
99    // lazily intialized; most RegionServers don't deal with META
100   final AtomicReference<WALProvider> metaProvider = new AtomicReference<WALProvider>();
101 
102   /**
103    * Configuration-specified WAL Reader used when a custom reader is requested
104    */
105   private final Class<? extends DefaultWALProvider.Reader> logReaderClass;
106 
107   /**
108    * How long to attempt opening in-recovery wals
109    */
110   private final int timeoutMillis;
111 
112   private final Configuration conf;
113 
114   // Used for the singleton WALFactory, see below.
115   private WALFactory(Configuration conf) {
116     // this code is duplicated here so we can keep our members final.
117     // until we've moved reader/writer construction down into providers, this initialization must
118     // happen prior to provider initialization, in case they need to instantiate a reader/writer.
119     timeoutMillis = conf.getInt("hbase.hlog.open.timeout", 300000);
120     /* TODO Both of these are probably specific to the fs wal provider */
121     logReaderClass = conf.getClass("hbase.regionserver.hlog.reader.impl", ProtobufLogReader.class,
122         DefaultWALProvider.Reader.class);
123     this.conf = conf;
124     // end required early initialization
125 
126     // this instance can't create wals, just reader/writers.
127     provider = null;
128     factoryId = SINGLETON_ID;
129   }
130 
131   Class<? extends WALProvider> getProviderClass(String key, String defaultValue) {
132     try {
133       return Providers.valueOf(conf.get(key, defaultValue)).clazz;
134     } catch (IllegalArgumentException exception) {
135       // Fall back to them specifying a class name
136       // Note that the passed default class shouldn't actually be used, since the above only fails
137       // when there is a config value present.
138       return conf.getClass(key, DefaultWALProvider.class, WALProvider.class);
139     }
140   }
141 
142   WALProvider createProvider(Class<? extends WALProvider> clazz,
143       List<WALActionsListener> listeners, String providerId) throws IOException {
144     LOG.info("Instantiating WALProvider of type " + clazz);
145     try {
146       final WALProvider result = clazz.getDeclaredConstructor().newInstance();
147       result.init(this, conf, listeners, providerId);
148       return result;
149     } catch (Exception e) {
150       LOG.error("couldn't set up WALProvider, the configured class is " + clazz);
151       LOG.debug("Exception details for failure to load WALProvider.", e);
152       throw new IOException("couldn't set up WALProvider", e);
153     }
154   }
155 
156   /**
157    * instantiate a provider from a config property.
158    * requires conf to have already been set (as well as anything the provider might need to read).
159    */
160   WALProvider getProvider(final String key, final String defaultValue,
161       final List<WALActionsListener> listeners, final String providerId) throws IOException {
162     Class<? extends WALProvider> clazz = getProviderClass(key, defaultValue);
163     return createProvider(clazz, listeners, providerId);
164   }
165 
166   /**
167    * @param conf must not be null, will keep a reference to read params in later reader/writer
168    *     instances.
169    * @param listeners may be null. will be given to all created wals (and not meta-wals)
170    * @param factoryId a unique identifier for this factory. used i.e. by filesystem implementations
171    *     to make a directory
172    */
173   public WALFactory(final Configuration conf, final List<WALActionsListener> listeners,
174       final String factoryId) throws IOException {
175     // until we've moved reader/writer construction down into providers, this initialization must
176     // happen prior to provider initialization, in case they need to instantiate a reader/writer.
177     timeoutMillis = conf.getInt("hbase.hlog.open.timeout", 300000);
178     /* TODO Both of these are probably specific to the fs wal provider */
179     logReaderClass = conf.getClass("hbase.regionserver.hlog.reader.impl", ProtobufLogReader.class,
180         DefaultWALProvider.Reader.class);
181     this.conf = conf;
182     this.factoryId = factoryId;
183     // end required early initialization
184     if (conf.getBoolean("hbase.regionserver.hlog.enabled", true)) {
185       provider = getProvider(WAL_PROVIDER, DEFAULT_WAL_PROVIDER, listeners, null);
186     } else {
187       // special handling of existing configuration behavior.
188       LOG.warn("Running with WAL disabled.");
189       provider = new DisabledWALProvider();
190       provider.init(this, conf, null, factoryId);
191     }
192   }
193 
194   /**
195    * Shutdown all WALs and clean up any underlying storage.
196    * Use only when you will not need to replay and edits that have gone to any wals from this
197    * factory.
198    */
199   public void close() throws IOException {
200     final WALProvider metaProvider = this.metaProvider.get();
201     if (null != metaProvider) {
202       metaProvider.close();
203     }
204     // close is called on a WALFactory with null provider in the case of contention handling
205     // within the getInstance method.
206     if (null != provider) {
207       provider.close();
208     }
209   }
210 
211   /**
212    * Tell the underlying WAL providers to shut down, but do not clean up underlying storage.
213    * If you are not ending cleanly and will need to replay edits from this factory's wals,
214    * use this method if you can as it will try to leave things as tidy as possible.
215    */
216   public void shutdown() throws IOException {
217     IOException exception = null;
218     final WALProvider metaProvider = this.metaProvider.get();
219     if (null != metaProvider) {
220       try {
221         metaProvider.shutdown();
222       } catch(IOException ioe) {
223         exception = ioe;
224       }
225     }
226     provider.shutdown();
227     if (null != exception) {
228       throw exception;
229     }
230   }
231 
232   public List<WAL> getWALs() throws IOException {
233     return provider.getWALs();
234   }
235 
236   /**
237    * @param identifier may not be null, contents will not be altered
238    * @param namespace could be null, and will use default namespace if null
239    */
240   public WAL getWAL(final byte[] identifier, final byte[] namespace) throws IOException {
241     return provider.getWAL(identifier, namespace);
242   }
243 
244   /**
245    * @param identifier may not be null, contents will not be altered
246    */
247   public WAL getMetaWAL(final byte[] identifier) throws IOException {
248     WALProvider metaProvider = this.metaProvider.get();
249     if (null == metaProvider) {
250       final WALProvider temp = getProvider(META_WAL_PROVIDER, DEFAULT_META_WAL_PROVIDER,
251           Collections.<WALActionsListener>singletonList(new MetricsWAL()),
252           DefaultWALProvider.META_WAL_PROVIDER_ID);
253       if (this.metaProvider.compareAndSet(null, temp)) {
254         metaProvider = temp;
255       } else {
256         // reference must now be to a provider created in another thread.
257         temp.close();
258         metaProvider = this.metaProvider.get();
259       }
260     }
261     return metaProvider.getWAL(identifier, null);
262   }
263 
264   public Reader createReader(final FileSystem fs, final Path path) throws IOException {
265     return createReader(fs, path, (CancelableProgressable)null);
266   }
267 
268   /**
269    * Create a reader for the WAL. If you are reading from a file that's being written to and need
270    * to reopen it multiple times, use {@link WAL.Reader#reset()} instead of this method
271    * then just seek back to the last known good position.
272    * @return A WAL reader.  Close when done with it.
273    * @throws IOException
274    */
275   public Reader createReader(final FileSystem fs, final Path path,
276       CancelableProgressable reporter) throws IOException {
277     return createReader(fs, path, reporter, true);
278   }
279 
280   public Reader createReader(final FileSystem fs, final Path path,
281       CancelableProgressable reporter, boolean allowCustom)
282       throws IOException {
283     Class<? extends DefaultWALProvider.Reader> lrClass =
284         allowCustom ? logReaderClass : ProtobufLogReader.class;
285 
286     try {
287       // A wal file could be under recovery, so it may take several
288       // tries to get it open. Instead of claiming it is corrupted, retry
289       // to open it up to 5 minutes by default.
290       long startWaiting = EnvironmentEdgeManager.currentTime();
291       long openTimeout = timeoutMillis + startWaiting;
292       int nbAttempt = 0;
293       FSDataInputStream stream = null;
294       DefaultWALProvider.Reader reader = null;
295       while (true) {
296         try {
297           if (lrClass != ProtobufLogReader.class) {
298             // User is overriding the WAL reader, let them.
299             reader = lrClass.getDeclaredConstructor().newInstance();
300             reader.init(fs, path, conf, null);
301             return reader;
302           } else {
303             stream = fs.open(path);
304             // Note that zero-length file will fail to read PB magic, and attempt to create
305             // a non-PB reader and fail the same way existing code expects it to. If we get
306             // rid of the old reader entirely, we need to handle 0-size files differently from
307             // merely non-PB files.
308             byte[] magic = new byte[ProtobufLogReader.PB_WAL_MAGIC.length];
309             boolean isPbWal = (stream.read(magic) == magic.length)
310                 && Arrays.equals(magic, ProtobufLogReader.PB_WAL_MAGIC);
311             reader =
312                 isPbWal ? new ProtobufLogReader() : new SequenceFileLogReader();
313             reader.init(fs, path, conf, stream);
314             return reader;
315           }
316         } catch (IOException e) {
317           if (stream != null) {
318             try {
319               stream.close();
320             } catch (IOException exception) {
321               LOG.warn("Could not close DefaultWALProvider.Reader" + exception.getMessage());
322               LOG.debug("exception details", exception);
323             }
324           }
325           if (reader != null) {
326             try {
327               reader.close();
328             } catch (IOException exception) {
329               LOG.warn("Could not close FSDataInputStream" + exception.getMessage());
330               LOG.debug("exception details", exception);
331             }
332           }
333           String msg = e.getMessage();
334           if (msg != null && (msg.contains("Cannot obtain block length")
335               || msg.contains("Could not obtain the last block")
336               || msg.matches("Blocklist for [^ ]* has changed.*"))) {
337             if (++nbAttempt == 1) {
338               LOG.warn("Lease should have recovered. This is not expected. Will retry", e);
339             }
340             if (reporter != null && !reporter.progress()) {
341               throw new InterruptedIOException("Operation is cancelled");
342             }
343             if (nbAttempt > 2 && openTimeout < EnvironmentEdgeManager.currentTime()) {
344               LOG.error("Can't open after " + nbAttempt + " attempts and "
345                   + (EnvironmentEdgeManager.currentTime() - startWaiting) + "ms " + " for " + path);
346             } else {
347               try {
348                 Thread.sleep(nbAttempt < 3 ? 500 : 1000);
349                 continue; // retry
350               } catch (InterruptedException ie) {
351                 InterruptedIOException iioe = new InterruptedIOException();
352                 iioe.initCause(ie);
353                 throw iioe;
354               }
355             }
356             throw new LeaseNotRecoveredException(e);
357           } else {
358             throw e;
359           }
360         }
361       }
362     } catch (IOException ie) {
363       throw ie;
364     } catch (Exception e) {
365       throw new IOException("Cannot get log reader", e);
366     }
367   }
368 
369   /**
370    * Create a writer for the WAL.
371    * should be package-private. public only for tests and
372    * {@link org.apache.hadoop.hbase.regionserver.wal.Compressor}
373    * @return A WAL writer.  Close when done with it.
374    * @throws IOException
375    */
376   public Writer createWALWriter(final FileSystem fs, final Path path) throws IOException {
377     return DefaultWALProvider.createWriter(conf, fs, path, false);
378   }
379 
380   /**
381    * should be package-private, visible for recovery testing.
382    * @return an overwritable writer for recovered edits. caller should close.
383    */
384   @VisibleForTesting
385   public Writer createRecoveredEditsWriter(final FileSystem fs, final Path path)
386       throws IOException {
387     return DefaultWALProvider.createWriter(conf, fs, path, true);
388   }
389 
390   // These static methods are currently used where it's impractical to
391   // untangle the reliance on state in the filesystem. They rely on singleton
392   // WALFactory that just provides Reader / Writers.
393   // For now, first Configuration object wins. Practically this just impacts the reader/writer class
394   private static final AtomicReference<WALFactory> singleton = new AtomicReference<WALFactory>();
395   private static final String SINGLETON_ID = WALFactory.class.getName();
396   
397   // public only for FSHLog and UpgradeTo96
398   public static WALFactory getInstance(Configuration configuration) {
399     WALFactory factory = singleton.get();
400     if (null == factory) {
401       WALFactory temp = new WALFactory(configuration);
402       if (singleton.compareAndSet(null, temp)) {
403         factory = temp;
404       } else {
405         // someone else beat us to initializing
406         try {
407           temp.close();
408         } catch (IOException exception) {
409           LOG.debug("failed to close temporary singleton. ignoring.", exception);
410         }
411         factory = singleton.get();
412       }
413     }
414     return factory;
415   }
416 
417   /**
418    * Create a reader for the given path, accept custom reader classes from conf.
419    * If you already have a WALFactory, you should favor the instance method.
420    * @return a WAL Reader, caller must close.
421    */
422   public static Reader createReader(final FileSystem fs, final Path path,
423       final Configuration configuration) throws IOException {
424     return getInstance(configuration).createReader(fs, path);
425   }
426 
427   /**
428    * Create a reader for the given path, accept custom reader classes from conf.
429    * If you already have a WALFactory, you should favor the instance method.
430    * @return a WAL Reader, caller must close.
431    */
432   static Reader createReader(final FileSystem fs, final Path path,
433       final Configuration configuration, final CancelableProgressable reporter) throws IOException {
434     return getInstance(configuration).createReader(fs, path, reporter);
435   }
436 
437   /**
438    * Create a reader for the given path, ignore custom reader classes from conf.
439    * If you already have a WALFactory, you should favor the instance method.
440    * only public pending move of {@link org.apache.hadoop.hbase.regionserver.wal.Compressor}
441    * @return a WAL Reader, caller must close.
442    */
443   public static Reader createReaderIgnoreCustomClass(final FileSystem fs, final Path path,
444       final Configuration configuration) throws IOException {
445     return getInstance(configuration).createReader(fs, path, null, false);
446   }
447 
448   /**
449    * If you already have a WALFactory, you should favor the instance method.
450    * @return a Writer that will overwrite files. Caller must close.
451    */
452   static Writer createRecoveredEditsWriter(final FileSystem fs, final Path path,
453       final Configuration configuration)
454       throws IOException {
455     return DefaultWALProvider.createWriter(configuration, fs, path, true);
456   }
457 
458   /**
459    * If you already have a WALFactory, you should favor the instance method.
460    * @return a writer that won't overwrite files. Caller must close.
461    */
462   @VisibleForTesting
463   public static Writer createWALWriter(final FileSystem fs, final Path path,
464       final Configuration configuration)
465       throws IOException {
466     return DefaultWALProvider.createWriter(configuration, fs, path, false);
467   }
468 
469   public final WALProvider getWALProvider() {
470     return this.provider;
471   }
472 
473   public final WALProvider getMetaWALProvider() {
474     return this.metaProvider.get();
475   }
476 }