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.regionserver;
20  
21  import java.io.IOException;
22  import java.util.Map.Entry;
23  import java.util.concurrent.ConcurrentHashMap;
24  import java.util.concurrent.atomic.AtomicBoolean;
25  import java.util.concurrent.locks.ReentrantLock;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.HConstants;
31  import org.apache.hadoop.hbase.RemoteExceptionHandler;
32  import org.apache.hadoop.hbase.Server;
33  import org.apache.hadoop.hbase.regionserver.wal.FSHLog;
34  import org.apache.hadoop.hbase.regionserver.wal.FailedLogCloseException;
35  import org.apache.hadoop.hbase.wal.WAL;
36  import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
37  import org.apache.hadoop.hbase.util.Bytes;
38  import org.apache.hadoop.hbase.util.HasThread;
39  
40  import com.google.common.annotations.VisibleForTesting;
41  
42  /**
43   * Runs periodically to determine if the WAL should be rolled.
44   *
45   * NOTE: This class extends Thread rather than Chore because the sleep time
46   * can be interrupted when there is something to do, rather than the Chore
47   * sleep time which is invariant.
48   *
49   * TODO: change to a pool of threads
50   */
51  @InterfaceAudience.Private
52  @VisibleForTesting
53  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
54    justification="Use of an atomic type both as monitor and condition variable is intended")
55  public class LogRoller extends HasThread {
56    private static final Log LOG = LogFactory.getLog(LogRoller.class);
57    private final ReentrantLock rollLock = new ReentrantLock();
58    private final AtomicBoolean rollLog = new AtomicBoolean(false);
59    private final ConcurrentHashMap<WAL, Boolean> walNeedsRoll =
60        new ConcurrentHashMap<WAL, Boolean>();
61    private final Server server;
62    protected final RegionServerServices services;
63    private volatile long lastrolltime = System.currentTimeMillis();
64    // Period to roll log.
65    private final long rollperiod;
66    private final int threadWakeFrequency;
67    // The interval to check low replication on hlog's pipeline
68    private long checkLowReplicationInterval;
69  
70    public void addWAL(final WAL wal) {
71      if (null == walNeedsRoll.putIfAbsent(wal, Boolean.FALSE)) {
72        wal.registerWALActionsListener(new WALActionsListener.Base() {
73          @Override
74          public void logRollRequested(boolean lowReplicas) {
75            walNeedsRoll.put(wal, Boolean.TRUE);
76            // TODO logs will contend with each other here, replace with e.g. DelayedQueue
77            synchronized(rollLog) {
78              rollLog.set(true);
79              rollLog.notifyAll();
80            }
81          }
82        });
83      }
84    }
85  
86    public void requestRollAll() {
87      for (WAL wal : walNeedsRoll.keySet()) {
88        walNeedsRoll.put(wal, Boolean.TRUE);
89      }
90      synchronized(rollLog) {
91        rollLog.set(true);
92        rollLog.notifyAll();
93      }
94    }
95  
96    /** @param server */
97    public LogRoller(final Server server, final RegionServerServices services) {
98      super("LogRoller");
99      this.server = server;
100     this.services = services;
101     this.rollperiod = this.server.getConfiguration().
102       getLong("hbase.regionserver.logroll.period", 3600000);
103     this.threadWakeFrequency = this.server.getConfiguration().
104       getInt(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000);
105     this.checkLowReplicationInterval = this.server.getConfiguration().getLong(
106         "hbase.regionserver.hlog.check.lowreplication.interval", 30 * 1000);
107   }
108 
109   @Override
110   public void interrupt() {
111     // Wake up if we are waiting on rollLog. For tests.
112     synchronized (rollLog) {
113       this.rollLog.notify();
114     }
115     super.interrupt();
116   }
117 
118   /**
119    * we need to check low replication in period, see HBASE-18132
120    */
121   void checkLowReplication(long now) {
122     try {
123       for (Entry<WAL, Boolean> entry : walNeedsRoll.entrySet()) {
124         WAL wal = entry.getKey();
125         boolean neeRollAlready = entry.getValue();
126         if(wal instanceof FSHLog && !neeRollAlready) {
127           FSHLog hlog = (FSHLog)wal;
128           if ((now - hlog.getLastTimeCheckLowReplication())
129               > this.checkLowReplicationInterval) {
130             hlog.checkLogRoll();
131           }
132         }
133       }
134     } catch (Throwable e) {
135       LOG.warn("Failed checking low replication", e);
136     }
137   }
138 
139   @Override
140   public void run() {
141     while (!server.isStopped()) {
142       long now = System.currentTimeMillis();
143       checkLowReplication(now);
144       boolean periodic = false;
145       if (!rollLog.get()) {
146         periodic = (now - this.lastrolltime) > this.rollperiod;
147         if (!periodic) {
148           synchronized (rollLog) {
149             try {
150               if (!rollLog.get()) {
151                 rollLog.wait(this.threadWakeFrequency);
152               }
153             } catch (InterruptedException e) {
154               // Fall through
155             }
156           }
157           continue;
158         }
159         // Time for periodic roll
160         if (LOG.isDebugEnabled()) {
161           LOG.debug("Wal roll period " + this.rollperiod + "ms elapsed");
162         }
163       } else if (LOG.isDebugEnabled()) {
164         LOG.debug("WAL roll requested");
165       }
166       rollLock.lock(); // FindBugs UL_UNRELEASED_LOCK_EXCEPTION_PATH
167       try {
168         this.lastrolltime = now;
169         for (Entry<WAL, Boolean> entry : walNeedsRoll.entrySet()) {
170           final WAL wal = entry.getKey();
171           // Force the roll if the logroll.period is elapsed or if a roll was requested.
172           // The returned value is an array of actual region names.
173           final byte [][] regionsToFlush = wal.rollWriter(periodic ||
174               entry.getValue().booleanValue());
175           walNeedsRoll.put(wal, Boolean.FALSE);
176           if (regionsToFlush != null) {
177             for (byte [] r: regionsToFlush) scheduleFlush(r);
178           }
179         }
180       } catch (FailedLogCloseException e) {
181         server.abort("Failed log close in log roller", e);
182       } catch (java.net.ConnectException e) {
183         server.abort("Failed log close in log roller", e);
184       } catch (IOException ex) {
185         LOG.fatal("Aborting", ex);
186         // Abort if we get here.  We probably won't recover an IOE. HBASE-1132
187         server.abort("IOE in log roller",
188           RemoteExceptionHandler.checkIOException(ex));
189       } catch (Exception ex) {
190         final String msg = "Failed rolling WAL; aborting to recover edits!";
191         LOG.error(msg, ex);
192         server.abort(msg, ex);
193       } finally {
194         try {
195           rollLog.set(false);
196         } finally {
197           rollLock.unlock();
198         }
199       }
200     }
201     LOG.info("LogRoller exiting.");
202   }
203 
204   /**
205    * @param encodedRegionName Encoded name of region to flush.
206    */
207   private void scheduleFlush(final byte [] encodedRegionName) {
208     boolean scheduled = false;
209     Region r = this.services.getFromOnlineRegions(Bytes.toString(encodedRegionName));
210     FlushRequester requester = null;
211     if (r != null) {
212       requester = this.services.getFlushRequester();
213       if (requester != null) {
214         // force flushing all stores to clean old logs
215         requester.requestFlush(r, true);
216         scheduled = true;
217       }
218     }
219     if (!scheduled) {
220       LOG.warn("Failed to schedule flush of " +
221         Bytes.toString(encodedRegionName) + ", region=" + r + ", requester=" +
222         requester);
223     }
224   }
225 
226   /**
227    * For testing only
228    * @return true if all WAL roll finished
229    */
230   @VisibleForTesting
231   public boolean walRollFinished() {
232     for (boolean needRoll : walNeedsRoll.values()) {
233       if (needRoll) {
234         return false;
235       }
236     }
237     return true;
238   }
239 }