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.hbase.zookeeper.ZKWatcher; 024import org.apache.yetus.audience.InterfaceAudience; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.hbase.ZooKeeperConnectionException; 027import org.apache.hadoop.hbase.zookeeper.ZKUtil; 028import org.apache.hadoop.hbase.zookeeper.ZKListener; 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 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)) return; 071 072 LOG.debug("Archive node: " + path + " created"); 073 // since we are already enabled, just update a single table 074 String table = path.substring(archiveHFileZNode.length()); 075 076 // the top level node has come up, so read in all the tables 077 if (table.length() == 0) { 078 079 checkEnabledAndUpdate(); 080 return; 081 } 082 // find the table that needs to be archived 083 try { 084 addAndReWatchTable(path); 085 } catch (KeeperException e) { 086 LOG.warn("Couldn't read zookeeper data for table for path:" + path 087 + ", not preserving a table.", e); 088 } 089 } 090 091 @Override 092 public void nodeChildrenChanged(String path) { 093 if (!path.startsWith(archiveHFileZNode)) return; 094 095 LOG.debug("Archive node: " + path + " children changed."); 096 // a table was added to the archive 097 try { 098 updateWatchedTables(); 099 } catch (KeeperException e) { 100 LOG.error("Failed to update tables to archive", e); 101 } 102 } 103 104 /** 105 * Add this table to the tracker and then read a watch on that node. 106 * <p> 107 * Handles situation where table is deleted in the time between the update and resetting the watch 108 * by deleting the table via {@link #safeStopTrackingTable(String)} 109 * @param tableZnode full zookeeper path to the table to be added 110 * @throws KeeperException if an unexpected zk exception occurs 111 */ 112 private void addAndReWatchTable(String tableZnode) throws KeeperException { 113 getMonitor().addTable(ZKUtil.getNodeName(tableZnode)); 114 // re-add a watch to the table created 115 // and check to make sure it wasn't deleted 116 if (!ZKUtil.watchAndCheckExists(watcher, tableZnode)) { 117 safeStopTrackingTable(tableZnode); 118 } 119 } 120 121 /** 122 * Stop tracking a table. Ensures that the table doesn't exist, but if it does, it attempts to add 123 * the table back via {@link #addAndReWatchTable(String)} - its a 'safe' removal. 124 * @param tableZnode full zookeeper path to the table to be added 125 * @throws KeeperException if an unexpected zk exception occurs 126 */ 127 private void safeStopTrackingTable(String tableZnode) throws KeeperException { 128 getMonitor().removeTable(ZKUtil.getNodeName(tableZnode)); 129 // if the table exists, then add and rewatch it 130 if (ZKUtil.checkExists(watcher, tableZnode) >= 0) { 131 addAndReWatchTable(tableZnode); 132 } 133 } 134 135 @Override 136 public void nodeDeleted(String path) { 137 if (!path.startsWith(archiveHFileZNode)) return; 138 139 LOG.debug("Archive node: " + path + " deleted"); 140 String table = path.substring(archiveHFileZNode.length()); 141 // if we stop archiving all tables 142 if (table.length() == 0) { 143 // make sure we have the tracker before deleting the archive 144 // but if we don't, we don't care about delete 145 clearTables(); 146 // watches are one-time events, so we need to renew our subscription to 147 // the archive node and might as well check to make sure archiving 148 // didn't come back on at the same time 149 checkEnabledAndUpdate(); 150 return; 151 } 152 // just stop archiving one table 153 // note that we don't attempt to add another watch for that table into zk. 154 // We have no assurances that the table will be archived again (or even 155 // exists for that matter), so its better not to add unnecessary load to 156 // zk for watches. If the table is created again, then we will get the 157 // notification in childrenChanaged. 158 getMonitor().removeTable(ZKUtil.getNodeName(path)); 159 } 160 161 /** 162 * Sets the watch on the top-level archive znode, and then updates the monitor with the current 163 * tables that should be archived (and ensures that those nodes are watched as well). 164 */ 165 private void checkEnabledAndUpdate() { 166 try { 167 if (ZKUtil.watchAndCheckExists(watcher, archiveHFileZNode)) { 168 LOG.debug(archiveHFileZNode + " znode does exist, checking for tables to archive"); 169 170 // update the tables we should backup, to get the most recent state. 171 // This is safer than also watching for children and then hoping we get 172 // all the updates as it makes sure we get and watch all the children 173 updateWatchedTables(); 174 } else { 175 LOG.debug("Archiving not currently enabled, waiting"); 176 } 177 } catch (KeeperException e) { 178 LOG.warn("Failed to watch for archiving znode", e); 179 } 180 } 181 182 /** 183 * Read the list of children under the archive znode as table names and then sets those tables to 184 * the list of tables that we should archive 185 * @throws KeeperException if there is an unexpected zk exception 186 */ 187 private void updateWatchedTables() throws KeeperException { 188 // get the children and watch for new children 189 LOG.debug("Updating watches on tables to archive."); 190 // get the children and add watches for each of the children 191 List<String> tables = ZKUtil.listChildrenAndWatchThem(watcher, archiveHFileZNode); 192 LOG.debug("Starting archive for tables:" + tables); 193 // if archiving is still enabled 194 if (tables != null && tables.size() > 0) { 195 getMonitor().setArchiveTables(tables); 196 } else { 197 LOG.debug("No tables to archive."); 198 // only if we currently have a tracker, then clear the archive 199 clearTables(); 200 } 201 } 202 203 /** 204 * Remove the currently archived tables. 205 * <p> 206 * Does some intelligent checking to make sure we don't prematurely create an archive tracker. 207 */ 208 private void clearTables() { 209 getMonitor().clearArchive(); 210 } 211 212 /** 213 * Determine if the given table should or should not allow its hfiles to be deleted 214 * @param tableName name of the table to check 215 * @return <tt>true</tt> if its store files should be retained, <tt>false</tt> otherwise 216 */ 217 public boolean keepHFiles(String tableName) { 218 return getMonitor().shouldArchiveTable(tableName); 219 } 220 221 /** 222 * @return the tracker for which tables should be archived. 223 */ 224 public final HFileArchiveTableMonitor getMonitor() { 225 return this.monitor; 226 } 227 228 /** 229 * Create an archive tracker for the passed in server 230 * @param conf to read for zookeeper connection information 231 * @return ZooKeeper tracker to monitor for this server if this server should archive hfiles for a 232 * given table 233 * @throws IOException If a unexpected exception occurs 234 * @throws ZooKeeperConnectionException if we can't reach zookeeper 235 */ 236 public static TableHFileArchiveTracker create(Configuration conf) 237 throws ZooKeeperConnectionException, IOException { 238 ZKWatcher zkw = new ZKWatcher(conf, "hfileArchiveCleaner", null); 239 return create(zkw, new HFileArchiveTableMonitor()); 240 } 241 242 /** 243 * Create an archive tracker with the special passed in table monitor. Should only be used in 244 * special cases (e.g. testing) 245 * @param zkw Watcher for the ZooKeeper cluster that we should track 246 * @param monitor Monitor for which tables need hfile archiving 247 * @return ZooKeeper tracker to monitor for this server if this server should archive hfiles for a 248 * given table 249 */ 250 private static TableHFileArchiveTracker create(ZKWatcher zkw, 251 HFileArchiveTableMonitor monitor) { 252 return new TableHFileArchiveTracker(zkw, monitor); 253 } 254 255 public ZKWatcher getZooKeeperWatcher() { 256 return this.watcher; 257 } 258 259 /** 260 * Stop this tracker and the passed zookeeper 261 */ 262 public void stop() { 263 if (this.stopped) return; 264 this.stopped = true; 265 this.watcher.close(); 266 } 267}