001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.backup.example; 019 020import java.io.IOException; 021import java.util.List; 022 023import org.apache.hadoop.conf.Configuration; 024import org.apache.hadoop.hbase.ZooKeeperConnectionException; 025import org.apache.hadoop.hbase.zookeeper.ZKListener; 026import org.apache.hadoop.hbase.zookeeper.ZKUtil; 027import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 028import org.apache.yetus.audience.InterfaceAudience; 029import org.apache.zookeeper.KeeperException; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Track HFile archiving state changes in ZooKeeper. Keeps track of the tables whose HFiles should 035 * be kept in the archive. 036 * <p> 037 * {@link TableHFileArchiveTracker#start()} needs to be called to start monitoring for tables to 038 * archive. 039 */ 040@InterfaceAudience.Private 041public final class TableHFileArchiveTracker extends ZKListener { 042 private static final Logger LOG = LoggerFactory.getLogger(TableHFileArchiveTracker.class); 043 public static final String HFILE_ARCHIVE_ZNODE_PARENT = "hfilearchive"; 044 private HFileArchiveTableMonitor monitor; 045 private String archiveHFileZNode; 046 private boolean stopped = false; 047 048 private TableHFileArchiveTracker(ZKWatcher watcher, HFileArchiveTableMonitor monitor) { 049 super(watcher); 050 watcher.registerListener(this); 051 this.monitor = monitor; 052 this.archiveHFileZNode = ZKTableArchiveClient.getArchiveZNode(watcher.getConfiguration(), 053 watcher); 054 } 055 056 /** 057 * Start monitoring for archive updates 058 * @throws KeeperException on failure to find/create nodes 059 */ 060 public void start() throws KeeperException { 061 // if archiving is enabled, then read in the list of tables to archive 062 LOG.debug("Starting hfile archive tracker..."); 063 this.checkEnabledAndUpdate(); 064 LOG.debug("Finished starting hfile archive tracker!"); 065 } 066 067 @Override 068 public void nodeCreated(String path) { 069 // if it is the archive path 070 if (!path.startsWith(archiveHFileZNode)) { 071 return; 072 } 073 074 LOG.debug("Archive node: " + path + " created"); 075 // since we are already enabled, just update a single table 076 String table = path.substring(archiveHFileZNode.length()); 077 078 // the top level node has come up, so read in all the tables 079 if (table.length() == 0) { 080 checkEnabledAndUpdate(); 081 return; 082 } 083 // find the table that needs to be archived 084 try { 085 addAndReWatchTable(path); 086 } catch (KeeperException e) { 087 LOG.warn("Couldn't read zookeeper data for table for path:" + path 088 + ", not preserving a table.", e); 089 } 090 } 091 092 @Override 093 public void nodeChildrenChanged(String path) { 094 if (!path.startsWith(archiveHFileZNode)) { 095 return; 096 } 097 098 LOG.debug("Archive node: " + path + " children changed."); 099 // a table was added to the archive 100 try { 101 updateWatchedTables(); 102 } catch (KeeperException e) { 103 LOG.error("Failed to update tables to archive", e); 104 } 105 } 106 107 /** 108 * Add this table to the tracker and then read a watch on that node. 109 * <p> 110 * Handles situation where table is deleted in the time between the update and resetting the watch 111 * by deleting the table via {@link #safeStopTrackingTable(String)} 112 * @param tableZnode full zookeeper path to the table to be added 113 * @throws KeeperException if an unexpected zk exception occurs 114 */ 115 private void addAndReWatchTable(String tableZnode) throws KeeperException { 116 getMonitor().addTable(ZKUtil.getNodeName(tableZnode)); 117 // re-add a watch to the table created 118 // and check to make sure it wasn't deleted 119 if (!ZKUtil.watchAndCheckExists(watcher, tableZnode)) { 120 safeStopTrackingTable(tableZnode); 121 } 122 } 123 124 /** 125 * Stop tracking a table. Ensures that the table doesn't exist, but if it does, it attempts to add 126 * the table back via {@link #addAndReWatchTable(String)} - its a 'safe' removal. 127 * @param tableZnode full zookeeper path to the table to be added 128 * @throws KeeperException if an unexpected zk exception occurs 129 */ 130 private void safeStopTrackingTable(String tableZnode) throws KeeperException { 131 getMonitor().removeTable(ZKUtil.getNodeName(tableZnode)); 132 // if the table exists, then add and rewatch it 133 if (ZKUtil.checkExists(watcher, tableZnode) >= 0) { 134 addAndReWatchTable(tableZnode); 135 } 136 } 137 138 @Override 139 public void nodeDeleted(String path) { 140 if (!path.startsWith(archiveHFileZNode)) { 141 return; 142 } 143 144 LOG.debug("Archive node: " + path + " deleted"); 145 String table = path.substring(archiveHFileZNode.length()); 146 // if we stop archiving all tables 147 if (table.length() == 0) { 148 // make sure we have the tracker before deleting the archive 149 // but if we don't, we don't care about delete 150 clearTables(); 151 // watches are one-time events, so we need to renew our subscription to 152 // the archive node and might as well check to make sure archiving 153 // didn't come back on at the same time 154 checkEnabledAndUpdate(); 155 return; 156 } 157 // just stop archiving one table 158 // note that we don't attempt to add another watch for that table into zk. 159 // We have no assurances that the table will be archived again (or even 160 // exists for that matter), so its better not to add unnecessary load to 161 // zk for watches. If the table is created again, then we will get the 162 // notification in childrenChanaged. 163 getMonitor().removeTable(ZKUtil.getNodeName(path)); 164 } 165 166 /** 167 * Sets the watch on the top-level archive znode, and then updates the monitor with the current 168 * tables that should be archived (and ensures that those nodes are watched as well). 169 */ 170 private void checkEnabledAndUpdate() { 171 try { 172 if (ZKUtil.watchAndCheckExists(watcher, archiveHFileZNode)) { 173 LOG.debug(archiveHFileZNode + " znode does exist, checking for tables to archive"); 174 175 // update the tables we should backup, to get the most recent state. 176 // This is safer than also watching for children and then hoping we get 177 // all the updates as it makes sure we get and watch all the children 178 updateWatchedTables(); 179 } else { 180 LOG.debug("Archiving not currently enabled, waiting"); 181 } 182 } catch (KeeperException e) { 183 LOG.warn("Failed to watch for archiving znode", e); 184 } 185 } 186 187 /** 188 * Read the list of children under the archive znode as table names and then sets those tables to 189 * the list of tables that we should archive 190 * @throws KeeperException if there is an unexpected zk exception 191 */ 192 private void updateWatchedTables() throws KeeperException { 193 // get the children and watch for new children 194 LOG.debug("Updating watches on tables to archive."); 195 // get the children and add watches for each of the children 196 List<String> tables = ZKUtil.listChildrenAndWatchThem(watcher, archiveHFileZNode); 197 LOG.debug("Starting archive for tables:" + tables); 198 // if archiving is still enabled 199 if (tables != null && tables.size() > 0) { 200 getMonitor().setArchiveTables(tables); 201 } else { 202 LOG.debug("No tables to archive."); 203 // only if we currently have a tracker, then clear the archive 204 clearTables(); 205 } 206 } 207 208 /** 209 * Remove the currently archived tables. 210 * <p> 211 * Does some intelligent checking to make sure we don't prematurely create an archive tracker. 212 */ 213 private void clearTables() { 214 getMonitor().clearArchive(); 215 } 216 217 /** 218 * Determine if the given table should or should not allow its hfiles to be deleted 219 * @param tableName name of the table to check 220 * @return <tt>true</tt> if its store files should be retained, <tt>false</tt> otherwise 221 */ 222 public boolean keepHFiles(String tableName) { 223 return getMonitor().shouldArchiveTable(tableName); 224 } 225 226 /** 227 * @return the tracker for which tables should be archived. 228 */ 229 public final HFileArchiveTableMonitor getMonitor() { 230 return this.monitor; 231 } 232 233 /** 234 * Create an archive tracker for the passed in server 235 * @param conf to read for zookeeper connection information 236 * @return ZooKeeper tracker to monitor for this server if this server should archive hfiles for a 237 * given table 238 * @throws IOException If a unexpected exception occurs 239 * @throws ZooKeeperConnectionException if we can't reach zookeeper 240 */ 241 public static TableHFileArchiveTracker create(Configuration conf) 242 throws ZooKeeperConnectionException, IOException { 243 ZKWatcher zkw = new ZKWatcher(conf, "hfileArchiveCleaner", null); 244 return create(zkw, new HFileArchiveTableMonitor()); 245 } 246 247 /** 248 * Create an archive tracker with the special passed in table monitor. Should only be used in 249 * special cases (e.g. testing) 250 * @param zkw Watcher for the ZooKeeper cluster that we should track 251 * @param monitor Monitor for which tables need hfile archiving 252 * @return ZooKeeper tracker to monitor for this server if this server should archive hfiles for a 253 * given table 254 */ 255 private static TableHFileArchiveTracker create(ZKWatcher zkw, 256 HFileArchiveTableMonitor monitor) { 257 return new TableHFileArchiveTracker(zkw, monitor); 258 } 259 260 public ZKWatcher getZooKeeperWatcher() { 261 return this.watcher; 262 } 263 264 /** 265 * Stop this tracker and the passed zookeeper 266 */ 267 public void stop() { 268 if (this.stopped) { 269 return; 270 } 271 272 this.stopped = true; 273 this.watcher.close(); 274 } 275}