001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.wal;
020
021import static org.apache.hadoop.hbase.wal.AbstractFSWALProvider.META_WAL_PROVIDER_ID;
022import static org.apache.hadoop.hbase.wal.AbstractFSWALProvider.WAL_FILE_NAME_DELIMITER;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.List;
027import java.util.concurrent.ConcurrentHashMap;
028import java.util.concurrent.ConcurrentMap;
029import java.util.concurrent.locks.Lock;
030import java.util.stream.Collectors;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.hbase.HConstants;
033import org.apache.hadoop.hbase.client.RegionInfo;
034// imports for classes still in regionserver.wal
035import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
036import org.apache.hadoop.hbase.util.Bytes;
037import org.apache.hadoop.hbase.util.KeyLocker;
038import org.apache.yetus.audience.InterfaceAudience;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042/**
043 * A WAL Provider that returns a WAL per group of regions.
044 *
045 * This provider follows the decorator pattern and mainly holds the logic for WAL grouping.
046 * WAL creation/roll/close is delegated to {@link #DELEGATE_PROVIDER}
047 *
048 * Region grouping is handled via {@link RegionGroupingStrategy} and can be configured via the
049 * property "hbase.wal.regiongrouping.strategy". Current strategy choices are
050 * <ul>
051 *   <li><em>defaultStrategy</em> : Whatever strategy this version of HBase picks. currently
052 *                                  "bounded".</li>
053 *   <li><em>identity</em> : each region belongs to its own group.</li>
054 *   <li><em>bounded</em> : bounded number of groups and region evenly assigned to each group.</li>
055 * </ul>
056 * Optionally, a FQCN to a custom implementation may be given.
057 */
058@InterfaceAudience.Private
059public class RegionGroupingProvider implements WALProvider {
060  private static final Logger LOG = LoggerFactory.getLogger(RegionGroupingProvider.class);
061
062  /**
063   * Map identifiers to a group number.
064   */
065  public static interface RegionGroupingStrategy {
066    String GROUP_NAME_DELIMITER = ".";
067
068    /**
069     * Given an identifier and a namespace, pick a group.
070     */
071    String group(final byte[] identifier, byte[] namespace);
072    void init(Configuration config, String providerId);
073  }
074
075  /**
076   * Maps between configuration names for strategies and implementation classes.
077   */
078  static enum Strategies {
079    defaultStrategy(BoundedGroupingStrategy.class),
080    identity(IdentityGroupingStrategy.class),
081    bounded(BoundedGroupingStrategy.class),
082    namespace(NamespaceGroupingStrategy.class);
083
084    final Class<? extends RegionGroupingStrategy> clazz;
085    Strategies(Class<? extends RegionGroupingStrategy> clazz) {
086      this.clazz = clazz;
087    }
088  }
089
090  /**
091   * instantiate a strategy from a config property.
092   * requires conf to have already been set (as well as anything the provider might need to read).
093   */
094  RegionGroupingStrategy getStrategy(final Configuration conf, final String key,
095      final String defaultValue) throws IOException {
096    Class<? extends RegionGroupingStrategy> clazz;
097    try {
098      clazz = Strategies.valueOf(conf.get(key, defaultValue)).clazz;
099    } catch (IllegalArgumentException exception) {
100      // Fall back to them specifying a class name
101      // Note that the passed default class shouldn't actually be used, since the above only fails
102      // when there is a config value present.
103      clazz = conf.getClass(key, IdentityGroupingStrategy.class, RegionGroupingStrategy.class);
104    }
105    LOG.info("Instantiating RegionGroupingStrategy of type " + clazz);
106    try {
107      final RegionGroupingStrategy result = clazz.getDeclaredConstructor().newInstance();
108      result.init(conf, providerId);
109      return result;
110    } catch (Exception e) {
111      LOG.error("couldn't set up region grouping strategy, check config key " +
112          REGION_GROUPING_STRATEGY);
113      LOG.debug("Exception details for failure to load region grouping strategy.", e);
114      throw new IOException("couldn't set up region grouping strategy", e);
115    }
116  }
117
118  public static final String REGION_GROUPING_STRATEGY = "hbase.wal.regiongrouping.strategy";
119  public static final String DEFAULT_REGION_GROUPING_STRATEGY = Strategies.defaultStrategy.name();
120
121  /** delegate provider for WAL creation/roll/close */
122  public static final String DELEGATE_PROVIDER = "hbase.wal.regiongrouping.delegate.provider";
123  public static final String DEFAULT_DELEGATE_PROVIDER = WALFactory.Providers.defaultProvider
124      .name();
125
126  private static final String META_WAL_GROUP_NAME = "meta";
127
128  /** A group-provider mapping, make sure one-one rather than many-one mapping */
129  private final ConcurrentMap<String, WALProvider> cached = new ConcurrentHashMap<>();
130
131  private final KeyLocker<String> createLock = new KeyLocker<>();
132
133  private RegionGroupingStrategy strategy;
134  private WALFactory factory;
135  private List<WALActionsListener> listeners = new ArrayList<>();
136  private String providerId;
137  private Class<? extends WALProvider> providerClass;
138
139  @Override
140  public void init(WALFactory factory, Configuration conf, String providerId) throws IOException {
141    if (null != strategy) {
142      throw new IllegalStateException("WALProvider.init should only be called once.");
143    }
144    this.factory = factory;
145    StringBuilder sb = new StringBuilder().append(factory.factoryId);
146    if (providerId != null) {
147      if (providerId.startsWith(WAL_FILE_NAME_DELIMITER)) {
148        sb.append(providerId);
149      } else {
150        sb.append(WAL_FILE_NAME_DELIMITER).append(providerId);
151      }
152    }
153    this.providerId = sb.toString();
154    this.strategy = getStrategy(conf, REGION_GROUPING_STRATEGY, DEFAULT_REGION_GROUPING_STRATEGY);
155    this.providerClass = factory.getProviderClass(DELEGATE_PROVIDER, DEFAULT_DELEGATE_PROVIDER);
156  }
157
158  private WALProvider createProvider(String group) throws IOException {
159    if (META_WAL_PROVIDER_ID.equals(providerId)) {
160      return factory.createProvider(providerClass, META_WAL_PROVIDER_ID);
161    } else {
162      return factory.createProvider(providerClass, group);
163    }
164  }
165
166  @Override
167  public List<WAL> getWALs() {
168    return cached.values().stream().flatMap(p -> p.getWALs().stream()).collect(Collectors.toList());
169  }
170
171  private WAL getWAL(String group) throws IOException {
172    WALProvider provider = cached.get(group);
173    if (provider == null) {
174      Lock lock = createLock.acquireLock(group);
175      try {
176        provider = cached.get(group);
177        if (provider == null) {
178          provider = createProvider(group);
179          listeners.forEach(provider::addWALActionsListener);
180          cached.put(group, provider);
181        }
182      } finally {
183        lock.unlock();
184      }
185    }
186    return provider.getWAL(null);
187  }
188
189  @Override
190  public WAL getWAL(RegionInfo region) throws IOException {
191    String group;
192    if (META_WAL_PROVIDER_ID.equals(this.providerId)) {
193      group = META_WAL_GROUP_NAME;
194    } else {
195      byte[] id;
196      byte[] namespace;
197      if (region != null) {
198        id = region.getEncodedNameAsBytes();
199        namespace = region.getTable().getNamespace();
200      } else {
201        id = HConstants.EMPTY_BYTE_ARRAY;
202        namespace = null;
203      }
204      group = strategy.group(id, namespace);
205    }
206    return getWAL(group);
207  }
208
209  @Override
210  public void shutdown() throws IOException {
211    // save the last exception and rethrow
212    IOException failure = null;
213    for (WALProvider provider: cached.values()) {
214      try {
215        provider.shutdown();
216      } catch (IOException e) {
217        LOG.error("Problem shutting down wal provider '" + provider + "': " + e.getMessage());
218        if (LOG.isDebugEnabled()) {
219          LOG.debug("Details of problem shutting down wal provider '" + provider + "'", e);
220        }
221        failure = e;
222      }
223    }
224    if (failure != null) {
225      throw failure;
226    }
227  }
228
229  @Override
230  public void close() throws IOException {
231    // save the last exception and rethrow
232    IOException failure = null;
233    for (WALProvider provider : cached.values()) {
234      try {
235        provider.close();
236      } catch (IOException e) {
237        LOG.error("Problem closing wal provider '" + provider + "': " + e.getMessage());
238        if (LOG.isDebugEnabled()) {
239          LOG.debug("Details of problem closing wal provider '" + provider + "'", e);
240        }
241        failure = e;
242      }
243    }
244    if (failure != null) {
245      throw failure;
246    }
247  }
248
249  static class IdentityGroupingStrategy implements RegionGroupingStrategy {
250    @Override
251    public void init(Configuration config, String providerId) {}
252    @Override
253    public String group(final byte[] identifier, final byte[] namespace) {
254      return Bytes.toString(identifier);
255    }
256  }
257
258  @Override
259  public long getNumLogFiles() {
260    long numLogFiles = 0;
261    for (WALProvider provider : cached.values()) {
262      numLogFiles += provider.getNumLogFiles();
263    }
264    return numLogFiles;
265  }
266
267  @Override
268  public long getLogFileSize() {
269    long logFileSize = 0;
270    for (WALProvider provider : cached.values()) {
271      logFileSize += provider.getLogFileSize();
272    }
273    return logFileSize;
274  }
275
276  @Override
277  public void addWALActionsListener(WALActionsListener listener) {
278    // Notice that there is an assumption that this method must be called before the getWAL above,
279    // so we can make sure there is no sub WALProvider yet, so we only add the listener to our
280    // listeners list without calling addWALActionListener for each WALProvider. Although it is no
281    // hurt to execute an extra loop to call addWALActionListener for each WALProvider, but if the
282    // extra code actually works, then we will have other big problems. So leave it as is.
283    listeners.add(listener);
284  }
285}