View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.replication.regionserver;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.apache.hadoop.conf.Configuration;
23  import org.apache.hadoop.conf.Configured;
24  import org.apache.hadoop.fs.FileStatus;
25  import org.apache.hadoop.fs.FileSystem;
26  import org.apache.hadoop.fs.Path;
27  import org.apache.hadoop.hbase.*;
28  import org.apache.hadoop.hbase.client.ClusterConnection;
29  import org.apache.hadoop.hbase.client.ConnectionFactory;
30  import org.apache.hadoop.hbase.client.HBaseAdmin;
31  import org.apache.hadoop.hbase.client.replication.ReplicationAdmin;
32  import org.apache.hadoop.hbase.io.WALLink;
33  import org.apache.hadoop.hbase.procedure2.util.StringUtils;
34  import org.apache.hadoop.hbase.replication.*;
35  import org.apache.hadoop.hbase.util.FSUtils;
36  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
37  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
38  import org.apache.hadoop.util.Tool;
39  import org.apache.hadoop.util.ToolRunner;
40  import org.apache.zookeeper.KeeperException;
41  
42  import com.google.common.util.concurrent.AtomicLongMap;
43  
44  import java.io.FileNotFoundException;
45  import java.io.IOException;
46  import java.util.*;
47  
48  /**
49   * Provides information about the existing states of replication, replication peers and queues.
50   *
51   * Usage: hbase org.apache.hadoop.hbase.replication.regionserver.DumpReplicationQueues [args]
52   * Arguments: --distributed    Polls each RS to dump information about the queue
53   *            --hdfs           Reports HDFS usage by the replication queues (note: can be overestimated).
54   */
55  
56  public class DumpReplicationQueues extends Configured implements Tool {
57  
58    private static final Log LOG = LogFactory.getLog(DumpReplicationQueues.class.getName());
59  
60    private List<String> deadRegionServers;
61    private List<String> deletedQueues;
62    private AtomicLongMap<String> peersQueueSize;
63    private long totalSizeOfWALs;
64    private long numWalsNotFound;
65  
66    public DumpReplicationQueues() {
67      deadRegionServers = new ArrayList<String>();
68      deletedQueues = new ArrayList<String>();
69      peersQueueSize = AtomicLongMap.create();
70      totalSizeOfWALs = 0;
71      numWalsNotFound = 0;
72    }
73  
74    static class DumpOptions {
75      boolean hdfs = false;
76      boolean distributed = false;
77  
78      public DumpOptions() {
79      }
80  
81      public DumpOptions(DumpOptions that) {
82        this.hdfs = that.hdfs;
83        this.distributed = that.distributed;
84      }
85  
86      boolean isHdfs () {
87        return hdfs;
88      }
89  
90      boolean isDistributed() {
91        return distributed;
92      }
93  
94      void setHdfs (boolean hdfs) {
95        this.hdfs = hdfs;
96      }
97  
98      void setDistributed(boolean distributed) {
99        this.distributed = distributed;
100     }
101   }
102 
103   static DumpOptions parseOpts(Queue<String> args) {
104     DumpOptions opts = new DumpOptions();
105 
106     String cmd = null;
107     while ((cmd = args.poll()) != null) {
108       if (cmd.equals("-h") || cmd.equals("--h") || cmd.equals("--help")) {
109         // place item back onto queue so that caller knows parsing was incomplete
110         args.add(cmd);
111         break;
112       }
113       final String hdfs = "--hdfs";
114       if (cmd.equals(hdfs)) {
115         opts.setHdfs(true);
116         continue;
117       }
118       final String distributed = "--distributed";
119       if (cmd.equals(distributed)) {
120         opts.setDistributed(true);
121         continue;
122       } else {
123         printUsageAndExit("ERROR: Unrecognized option/command: " + cmd, -1);
124       }
125       // check that --distributed is present when --hdfs is in the arguments
126       if (!opts.isDistributed()  && opts.isHdfs()) {
127         printUsageAndExit("ERROR: --hdfs option can only be used with --distributed: " + cmd, -1);
128       }
129     }
130     return opts;
131   }
132 
133   /**
134    * Main
135    *
136    * @param args
137    * @throws Exception
138    */
139   public static void main(String[] args) throws Exception {
140     Configuration conf = HBaseConfiguration.create();
141     int ret = ToolRunner.run(conf, new DumpReplicationQueues(), args);
142     System.exit(ret);
143   }
144 
145   @Override
146   public int run(String[] args) throws Exception {
147 
148     int errCode = -1;
149     LinkedList<String> argv = new LinkedList<String>();
150     argv.addAll(Arrays.asList(args));
151     DumpOptions opts = parseOpts(argv);
152 
153     // args remaining, print help and exit
154     if (!argv.isEmpty()) {
155       errCode = 0;
156       printUsage();
157       return errCode;
158     }
159     return dumpReplicationQueues(opts);
160   }
161 
162   protected void printUsage() {
163     printUsage(this.getClass().getName(), null);
164   }
165 
166   protected static void printUsage(final String message) {
167     printUsage(DumpReplicationQueues.class.getName(), message);
168   }
169 
170   protected static void printUsage(final String className, final String message) {
171     if (message != null && message.length() > 0) {
172       System.err.println(message);
173     }
174     System.err.println("Usage: bin/hbase " + className + " \\");
175     System.err.println("  <OPTIONS> [-D<property=value>]*");
176     System.err.println();
177     System.err.println("General Options:");
178     System.err.println(" -h|--h|--help  Show this help and exit.");
179     System.err.println(" --distributed  Poll each RS and print its own replication queue. "
180         + "Default only polls ZooKeeper");
181     System.err.println(" --hdfs         Use HDFS to calculate usage of WALs by replication. It could be overestimated"
182         + " if replicating to multiple peers. --distributed flag is also needed.");
183   }
184 
185   protected static void printUsageAndExit(final String message, final int exitCode) {
186     printUsage(message);
187     System.exit(exitCode);
188   }
189 
190   private int dumpReplicationQueues(DumpOptions opts) throws Exception {
191 
192     Configuration conf = getConf();
193     HBaseAdmin.checkHBaseAvailable(conf);
194     ReplicationAdmin replicationAdmin = new ReplicationAdmin(conf);
195     ClusterConnection connection = (ClusterConnection) ConnectionFactory.createConnection(conf);
196 
197     ZooKeeperWatcher zkw = new ZooKeeperWatcher(conf, "DumpReplicationQueues" + System.currentTimeMillis(),
198         new WarnOnlyAbortable(), true);
199 
200     try {
201       // Our zk watcher
202       LOG.info("Our Quorum: " + zkw.getQuorum());
203       List<HashMap<String, String>> replicatedTables = replicationAdmin.listReplicated();
204       if (replicatedTables.isEmpty()) {
205         LOG.info("No tables with a configured replication peer were found.");
206         return(0);
207       } else {
208         LOG.info("Replicated Tables: " + replicatedTables);
209       }
210 
211       Map<String, ReplicationPeerConfig> peerConfigs = replicationAdmin.listPeerConfigs();
212 
213       if (peerConfigs.isEmpty()) {
214         LOG.info("Replication is enabled but no peer configuration was found.");
215       }
216 
217       System.out.println("Dumping replication peers and configurations:");
218       System.out.println(dumpPeersState(replicationAdmin, peerConfigs));
219 
220       if (opts.isDistributed()) {
221         LOG.info("Found [--distributed], will poll each RegionServer.");
222         System.out.println(dumpQueues(connection, peerConfigs.keySet(), zkw, opts.isHdfs()));
223         System.out.println(dumpReplicationSummary());
224       } else {
225         // use ZK instead
226         System.out.print("Dumping replication znodes via ZooKeeper:");
227         System.out.println(ZKUtil.getReplicationZnodesDump(zkw));
228       }
229       return (0);
230     } catch (IOException e) {
231       return (-1);
232     } finally {
233       zkw.close();
234     }
235   }
236 
237   public String dumpReplicationSummary() {
238     StringBuilder sb = new StringBuilder();
239     if (!deletedQueues.isEmpty()) {
240       sb.append("Found " + deletedQueues.size() + " deleted queues"
241           + ", run hbck -fixReplication in order to remove the deleted replication queues\n");
242       for (String deletedQueue : deletedQueues) {
243         sb.append("    " + deletedQueue + "\n");
244       }
245     }
246     if (!deadRegionServers.isEmpty()) {
247       sb.append("Found " + deadRegionServers.size() + " dead regionservers"
248           + ", restart one regionserver to transfer the queues of dead regionservers\n");
249       for (String deadRs : deadRegionServers) {
250         sb.append("    " + deadRs + "\n");
251       }
252     }
253     if (!peersQueueSize.isEmpty()) {
254       sb.append("Dumping all peers's number of WALs in replication queue\n");
255       for (Map.Entry<String, Long> entry : peersQueueSize.asMap().entrySet()) {
256         sb.append("    PeerId: " + entry.getKey() + " , sizeOfLogQueue: " + entry.getValue() + "\n");
257       }
258     }
259     sb.append("    Total size of WALs on HDFS: " + StringUtils.humanSize(totalSizeOfWALs) + "\n");
260     if (numWalsNotFound > 0) {
261       sb.append("    ERROR: There are " + numWalsNotFound + " WALs not found!!!\n");
262     }
263     return sb.toString();
264   }
265 
266   public String dumpPeersState(ReplicationAdmin replicationAdmin,
267       Map<String, ReplicationPeerConfig> peerConfigs) throws Exception {
268     Map<String, String> currentConf;
269     StringBuilder sb = new StringBuilder();
270     for (Map.Entry<String, ReplicationPeerConfig> peer : peerConfigs.entrySet()) {
271       try {
272         ReplicationPeerConfig peerConfig = peer.getValue();
273         sb.append("Peer: " + peer.getKey() + "\n");
274         sb.append("    " + "State: "
275             + (replicationAdmin.getPeerState(peer.getKey()) ? "ENABLED" : "DISABLED") + "\n");
276         sb.append("    " + "Cluster Name: " + peerConfig.getClusterKey() + "\n");
277         currentConf = peerConfig.getConfiguration();
278         // Only show when we have a custom configuration for the peer
279         if (currentConf.size() > 1) {
280           sb.append("    " + "Peer Configuration: " + currentConf + "\n");
281         }
282       } catch (ReplicationException re) {
283         sb.append("Got an exception while invoking ReplicationAdmin: " + re + "\n");
284       }
285     }
286     return sb.toString();
287   }
288 
289   public String dumpQueues(ClusterConnection connection, Set<String> peerIds, ZooKeeperWatcher zkw,
290       boolean hdfs) throws Exception {
291     ReplicationQueuesClient queuesClient;
292     ReplicationPeers replicationPeers;
293     ReplicationQueues replicationQueues;
294     ReplicationTracker replicationTracker;
295     StringBuilder sb = new StringBuilder();
296 
297     queuesClient = ReplicationFactory.getReplicationQueuesClient(zkw, getConf(), connection);
298     queuesClient.init();
299     replicationQueues = ReplicationFactory.getReplicationQueues(zkw, getConf(), connection);
300     replicationPeers = ReplicationFactory.getReplicationPeers(zkw, getConf(), queuesClient, connection);
301     replicationTracker = ReplicationFactory.getReplicationTracker(zkw, replicationPeers, getConf(),
302       new WarnOnlyAbortable(), new WarnOnlyStoppable());
303     List<String> liveRegionServers = replicationTracker.getListOfRegionServers();
304 
305     // Loops each peer on each RS and dumps the queues
306     try {
307       List<String> regionservers = queuesClient.getListOfReplicators();
308       if (regionservers == null || regionservers.isEmpty()) {
309         return sb.toString();
310       }
311       for (String regionserver : regionservers) {
312         List<String> queueIds = queuesClient.getAllQueues(regionserver);
313         replicationQueues.init(regionserver);
314         if (!liveRegionServers.contains(regionserver)) {
315           deadRegionServers.add(regionserver);
316         }
317         for (String queueId : queueIds) {
318           ReplicationQueueInfo queueInfo = new ReplicationQueueInfo(queueId);
319           List<String> wals = queuesClient.getLogsInQueue(regionserver, queueId);
320           Collections.sort(wals);
321           if (!peerIds.contains(queueInfo.getPeerId())) {
322             deletedQueues.add(regionserver + "/" + queueId);
323             sb.append(formatQueue(regionserver, replicationQueues, queueInfo, queueId, wals, true, hdfs));
324           } else {
325             sb.append(formatQueue(regionserver, replicationQueues, queueInfo, queueId, wals, false, hdfs));
326           }
327         }
328       }
329     } catch (KeeperException ke) {
330       throw new IOException(ke);
331     }
332     return sb.toString();
333   }
334 
335   private String formatQueue(String regionserver, ReplicationQueues replicationQueues,
336       ReplicationQueueInfo queueInfo, String queueId, List<String> wals, boolean isDeleted,
337       boolean hdfs) throws Exception {
338     StringBuilder sb = new StringBuilder();
339     List<String> deadServers ;
340 
341     sb.append("Dumping replication queue info for RegionServer: [" + regionserver + "]" + "\n");
342     sb.append("    Queue znode: " + queueId + "\n");
343     sb.append("    PeerID: " + queueInfo.getPeerId() + "\n");
344     sb.append("    Recovered: " + queueInfo.isQueueRecovered() + "\n");
345     deadServers = queueInfo.getDeadRegionServers();
346     if (deadServers.isEmpty()) {
347       sb.append("    No dead RegionServers found in this queue." + "\n");
348     } else {
349       sb.append("    Dead RegionServers: " + deadServers + "\n");
350     }
351     sb.append("    Was deleted: " + isDeleted + "\n");
352     sb.append("    Number of WALs in replication queue: " + wals.size() + "\n");
353     peersQueueSize.addAndGet(queueInfo.getPeerId(), wals.size());
354 
355     for (String wal : wals) {
356       long position = replicationQueues.getLogPosition(queueInfo.getPeerId(), wal);
357       sb.append("    Replication position for " + wal + ": " + (position > 0 ? position : "0"
358           + " (not started or nothing to replicate)") + "\n");
359     }
360 
361     if (hdfs) {
362       FileSystem fs = FileSystem.get(getConf());
363       sb.append("    Total size of WALs on HDFS for this queue: "
364           + StringUtils.humanSize(getTotalWALSize(fs, wals, regionserver)) + "\n");
365     }
366     return sb.toString();
367   }
368   /**
369    *  return total size in bytes from a list of WALs
370    */
371   private long getTotalWALSize(FileSystem fs, List<String> wals, String server) throws IOException {
372     long size = 0;
373     FileStatus fileStatus;
374 
375     for (String wal : wals) {
376       try {
377         fileStatus = (new WALLink(getConf(), server, wal)).getFileStatus(fs);
378       } catch (IOException e) {
379         if (e instanceof FileNotFoundException) {
380           numWalsNotFound++;
381           LOG.warn("WAL " + wal + " couldn't be found, skipping", e);
382         } else {
383           LOG.warn("Can't get file status of WAL " + wal + ", skipping", e);
384         }
385         continue;
386       }
387       size += fileStatus.getLen();
388     }
389 
390     totalSizeOfWALs += size;
391     return size;
392   }
393 
394   private static class WarnOnlyAbortable implements Abortable {
395     @Override
396     public void abort(String why, Throwable e) {
397       LOG.warn("DumpReplicationQueue received abort, ignoring.  Reason: " + why);
398       if (LOG.isDebugEnabled()) {
399         LOG.debug(e);
400       }
401     }
402 
403     @Override
404     public boolean isAborted() {
405       return false;
406     }
407   }
408 
409   private static class WarnOnlyStoppable implements Stoppable {
410     @Override
411     public void stop(String why) {
412       LOG.warn("DumpReplicationQueue received stop, ignoring.  Reason: " + why);
413     }
414 
415     @Override
416     public boolean isStopped() {
417       return false;
418     }
419   }
420 }