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