View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements. See the NOTICE file distributed with this
6    * work for additional information regarding copyright ownership. The ASF
7    * licenses this file to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16   * License for the specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.hadoop.hbase.client;
20  
21  import static org.apache.hadoop.hbase.client.ConnectionUtils.createCloseRowBefore;
22  import static org.apache.hadoop.hbase.client.ConnectionUtils.isEmptyStartRow;
23  
24  import java.io.IOException;
25  import java.io.InterruptedIOException;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.hadoop.hbase.DoNotRetryIOException;
30  import org.apache.hadoop.hbase.HConstants;
31  import org.apache.hadoop.hbase.HRegionLocation;
32  import org.apache.hadoop.hbase.RegionLocations;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.classification.InterfaceAudience;
35  import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
36  import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
37  import org.apache.hadoop.hbase.util.Bytes;
38  
39  
40  /**
41   * A reversed ScannerCallable which supports backward scanning.
42   */
43  @InterfaceAudience.Private
44  public class ReversedScannerCallable extends ScannerCallable {
45  
46    /**
47     * @param connection
48     * @param tableName
49     * @param scan
50     * @param scanMetrics
51     * @param rpcFactory to create an {@link com.google.protobuf.RpcController} to talk to the
52     *          regionserver
53     */
54    public ReversedScannerCallable(ClusterConnection connection, TableName tableName, Scan scan,
55        ScanMetrics scanMetrics, RpcControllerFactory rpcFactory) {
56      super(connection, tableName, scan, scanMetrics, rpcFactory);
57    }
58  
59    /**
60     * @param connection
61     * @param tableName
62     * @param scan
63     * @param scanMetrics
64     * @param rpcFactory to create an {@link com.google.protobuf.RpcController} to talk to the
65     *          regionserver
66     * @param replicaId the replica id
67     */
68    public ReversedScannerCallable(ClusterConnection connection, TableName tableName, Scan scan,
69        ScanMetrics scanMetrics, RpcControllerFactory rpcFactory, int replicaId) {
70      super(connection, tableName, scan, scanMetrics, rpcFactory, replicaId);
71    }
72  
73    /**
74     * @param reload force reload of server location
75     * @throws IOException
76     */
77    @Override
78    public void prepare(boolean reload) throws IOException {
79      if (Thread.interrupted()) {
80        throw new InterruptedIOException();
81      }
82      if (!instantiated || reload) {
83        // we should use range locate if
84        // 1. we do not want the start row
85        // 2. the start row is empty which means we need to locate to the last region.
86        if (scan.includeStartRow() && !isEmptyStartRow(getRow())) {
87          // Just locate the region with the row
88          RegionLocations rl = RpcRetryingCallerWithReadReplicas.getRegionLocations(!reload, id,
89              getConnection(), getTableName(), getRow());
90          this.location = id < rl.size() ? rl.getRegionLocation(id) : null;
91          if (location == null || location.getServerName() == null) {
92            throw new IOException("Failed to find location, tableName="
93                + tableName + ", row=" + Bytes.toStringBinary(row) + ", reload="
94                + reload);
95          }
96        } else {
97          // Need to locate the regions with the range, and the target location is
98          // the last one which is the previous region of last region scanner
99          byte[] locateStartRow = createCloseRowBefore(getRow());
100         List<HRegionLocation> locatedRegions = locateRegionsInRange(
101             locateStartRow, row, reload);
102         if (locatedRegions.isEmpty()) {
103           throw new DoNotRetryIOException(
104               "Does hbase:meta exist hole? Couldn't get regions for the range from "
105                   + Bytes.toStringBinary(locateStartRow) + " to "
106                   + Bytes.toStringBinary(row));
107         }
108         this.location = locatedRegions.get(locatedRegions.size() - 1);
109       }
110       setStub(getConnection().getClient(getLocation().getServerName()));
111       checkIfRegionServerIsRemote();
112       instantiated = true;
113     }
114 
115     // check how often we retry.
116     // HConnectionManager will call instantiateServer with reload==true
117     // if and only if for retries.
118     if (reload && this.scanMetrics != null) {
119       this.scanMetrics.countOfRPCRetries.incrementAndGet();
120       if (isRegionServerRemote) {
121         this.scanMetrics.countOfRemoteRPCRetries.incrementAndGet();
122       }
123     }
124   }
125 
126   /**
127    * Get the corresponding regions for an arbitrary range of keys.
128    * @param startKey Starting row in range, inclusive
129    * @param endKey Ending row in range, exclusive
130    * @param reload force reload of server location
131    * @return A list of HRegionLocation corresponding to the regions that contain
132    *         the specified range
133    * @throws IOException
134    */
135   @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NP_NULL_ON_SOME_PATH",
136       justification="I thought I'd fixed it but FB still complains; see below")
137   private List<HRegionLocation> locateRegionsInRange(byte[] startKey,
138       byte[] endKey, boolean reload) throws IOException {
139     final boolean endKeyIsEndOfTable = Bytes.equals(endKey,
140         HConstants.EMPTY_END_ROW);
141     if ((Bytes.compareTo(startKey, endKey) > 0) && !endKeyIsEndOfTable) {
142       throw new IllegalArgumentException("Invalid range: "
143           + Bytes.toStringBinary(startKey) + " > "
144           + Bytes.toStringBinary(endKey));
145     }
146     List<HRegionLocation> regionList = new ArrayList<HRegionLocation>();
147     byte[] currentKey = startKey;
148     do {
149       RegionLocations rl = RpcRetryingCallerWithReadReplicas.getRegionLocations(!reload, id,
150           getConnection(), getTableName(), currentKey);
151       HRegionLocation regionLocation = id < rl.size() ? rl.getRegionLocation(id) : null;
152       if (regionLocation != null && regionLocation.getRegionInfo().containsRow(currentKey)) {
153         regionList.add(regionLocation);
154       } else {
155         // FindBugs: NP_NULL_ON_SOME_PATH Complaining about regionLocation
156         throw new DoNotRetryIOException("Does hbase:meta exist hole? Locating row "
157             + Bytes.toStringBinary(currentKey) + " returns incorrect region "
158             + (regionLocation != null? regionLocation.getRegionInfo(): null));
159       }
160       currentKey = regionLocation.getRegionInfo().getEndKey();
161     } while (!Bytes.equals(currentKey, HConstants.EMPTY_END_ROW)
162         && (endKeyIsEndOfTable || Bytes.compareTo(currentKey, endKey) < 0));
163     return regionList;
164   }
165 
166   @Override
167   public ScannerCallable getScannerCallableForReplica(int id) {
168     ReversedScannerCallable r = new ReversedScannerCallable(getConnection(), getTableName(),
169         this.getScan(), this.scanMetrics, controllerFactory, id);
170     r.setCaching(this.getCaching());
171     return r;
172   }
173 }