1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.hadoop.hbase.client;
22
23
24 import com.google.protobuf.ServiceException;
25
26 import java.io.IOException;
27 import java.io.InterruptedIOException;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.concurrent.CancellationException;
31 import java.util.concurrent.ExecutionException;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.Future;
34 import java.util.concurrent.TimeUnit;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.hadoop.conf.Configuration;
39 import org.apache.hadoop.hbase.DoNotRetryIOException;
40 import org.apache.hadoop.hbase.HBaseIOException;
41 import org.apache.hadoop.hbase.HConstants;
42 import org.apache.hadoop.hbase.HRegionLocation;
43 import org.apache.hadoop.hbase.RegionLocations;
44 import org.apache.hadoop.hbase.ServerName;
45 import org.apache.hadoop.hbase.TableName;
46 import org.apache.hadoop.hbase.classification.InterfaceAudience;
47 import org.apache.hadoop.hbase.ipc.HBaseRpcController;
48 import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
49 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
50 import org.apache.hadoop.hbase.protobuf.RequestConverter;
51 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos;
52 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
53
54
55
56
57
58
59
60 @InterfaceAudience.Private
61 public class RpcRetryingCallerWithReadReplicas {
62 private static final Log LOG = LogFactory.getLog(RpcRetryingCallerWithReadReplicas.class);
63
64 protected final ExecutorService pool;
65 protected final ClusterConnection cConnection;
66 protected final Configuration conf;
67 protected final Get get;
68 protected final TableName tableName;
69 protected final int timeBeforeReplicas;
70 private final int callTimeout;
71 private final int retries;
72 private final RpcControllerFactory rpcControllerFactory;
73 private final RpcRetryingCallerFactory rpcRetryingCallerFactory;
74
75 public RpcRetryingCallerWithReadReplicas(
76 RpcControllerFactory rpcControllerFactory, TableName tableName,
77 ClusterConnection cConnection, final Get get,
78 ExecutorService pool, int retries, int callTimeout,
79 int timeBeforeReplicas) {
80 this.rpcControllerFactory = rpcControllerFactory;
81 this.tableName = tableName;
82 this.cConnection = cConnection;
83 this.conf = cConnection.getConfiguration();
84 this.get = get;
85 this.pool = pool;
86 this.retries = retries;
87 this.callTimeout = callTimeout;
88 this.timeBeforeReplicas = timeBeforeReplicas;
89 this.rpcRetryingCallerFactory = new RpcRetryingCallerFactory(conf);
90 }
91
92
93
94
95
96
97
98 class ReplicaRegionServerCallable extends RegionServerCallable<Result> implements Cancellable {
99 final int id;
100 private final HBaseRpcController controller;
101
102 public ReplicaRegionServerCallable(int id, HRegionLocation location) {
103 super(RpcRetryingCallerWithReadReplicas.this.cConnection,
104 RpcRetryingCallerWithReadReplicas.this.tableName, get.getRow(), HConstants.PRIORITY_UNSET);
105 this.id = id;
106 this.location = location;
107 this.controller = rpcControllerFactory.newController();
108 controller.setPriority(tableName);
109 }
110
111 @Override
112 public void cancel() {
113 controller.startCancel();
114 }
115
116
117
118
119
120
121 @Override
122 public void prepare(final boolean reload) throws IOException {
123 if (controller.isCanceled()) return;
124
125 if (Thread.interrupted()) {
126 throw new InterruptedIOException();
127 }
128
129 if (reload || location == null) {
130 RegionLocations rl = getRegionLocations(false, id, cConnection, tableName, get.getRow());
131 location = id < rl.size() ? rl.getRegionLocation(id) : null;
132 }
133
134 if (location == null || location.getServerName() == null) {
135
136
137 throw new HBaseIOException("There is no location for replica id #" + id);
138 }
139
140 ServerName dest = location.getServerName();
141
142 setStub(cConnection.getClient(dest));
143 }
144
145 @Override
146 public Result call(int callTimeout) throws Exception {
147 if (controller.isCanceled()) return null;
148
149 if (Thread.interrupted()) {
150 throw new InterruptedIOException();
151 }
152
153 byte[] reg = location.getRegionInfo().getRegionName();
154
155 ClientProtos.GetRequest request =
156 RequestConverter.buildGetRequest(reg, get);
157 controller.reset();
158 controller.setCallTimeout(callTimeout);
159
160 try {
161 ClientProtos.GetResponse response = getStub().get(controller, request);
162 if (response == null) {
163 return null;
164 }
165 return ProtobufUtil.toResult(response.getResult(), controller.cellScanner());
166 } catch (ServiceException se) {
167 throw ProtobufUtil.getRemoteException(se);
168 }
169 }
170
171 @Override
172 public boolean isCancelled() {
173 return controller.isCanceled();
174 }
175 }
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195 public Result call(int operationTimeout)
196 throws DoNotRetryIOException, InterruptedIOException, RetriesExhaustedException {
197 boolean isTargetReplicaSpecified = (get.getReplicaId() >= 0);
198
199 RegionLocations rl = null;
200 boolean skipPrimary = false;
201 try {
202 rl = getRegionLocations(true,
203 (isTargetReplicaSpecified ? get.getReplicaId() : RegionReplicaUtil.DEFAULT_REPLICA_ID),
204 cConnection, tableName, get.getRow());
205 } catch (RetriesExhaustedException | DoNotRetryIOException e) {
206
207 if (isTargetReplicaSpecified) {
208 throw e;
209 } else {
210
211
212 if (cConnection instanceof ConnectionManager.HConnectionImplementation) {
213 rl = ((ConnectionManager.HConnectionImplementation)cConnection).getCachedLocation(
214 tableName, get.getRow());
215 if (rl == null) {
216
217 throw e;
218 }
219
220
221 skipPrimary = true;
222 } else {
223
224 throw e;
225 }
226 }
227 }
228
229 final ResultBoundedCompletionService<Result> cs =
230 new ResultBoundedCompletionService<>(this.rpcRetryingCallerFactory, pool, rl.size());
231
232 int startIndex = 0;
233 int endIndex = rl.size();
234
235 if(isTargetReplicaSpecified) {
236 addCallsForReplica(cs, rl, get.getReplicaId(), get.getReplicaId());
237 endIndex = 1;
238 } else {
239 if (!skipPrimary) {
240 addCallsForReplica(cs, rl, 0, 0);
241 try {
242
243 Future<Result> f = cs.poll(timeBeforeReplicas, TimeUnit.MICROSECONDS);
244 if (f != null) {
245 return f.get();
246 }
247 if (cConnection.getConnectionMetrics() != null) {
248 cConnection.getConnectionMetrics().incrHedgedReadOps();
249 }
250 } catch (ExecutionException e) {
251
252 if (LOG.isDebugEnabled()) {
253 LOG.debug("Primary replica returns " + e.getCause());
254 }
255
256
257 startIndex = 1;
258 } catch (CancellationException e) {
259 throw new InterruptedIOException();
260 } catch (InterruptedException e) {
261 throw new InterruptedIOException();
262 }
263 } else {
264
265 endIndex --;
266 }
267
268
269 addCallsForReplica(cs, rl, 1, rl.size() - 1);
270 }
271
272 try {
273 ResultBoundedCompletionService<Result>.QueueingFuture<Result> f =
274 cs.pollForFirstSuccessfullyCompletedTask(operationTimeout, TimeUnit.MILLISECONDS, startIndex, endIndex);
275 if (f == null) {
276 throw new RetriesExhaustedException("Timed out after " + operationTimeout +
277 "ms. Get is sent to replicas with startIndex: " + startIndex +
278 ", endIndex: " + endIndex + ", Locations: " + rl);
279 }
280 if (cConnection.getConnectionMetrics() != null && !isTargetReplicaSpecified &&
281 !skipPrimary && f.getReplicaId() != RegionReplicaUtil.DEFAULT_REPLICA_ID) {
282 cConnection.getConnectionMetrics().incrHedgedReadWin();
283 }
284 return f.get();
285 } catch (ExecutionException e) {
286 throwEnrichedException(e, retries);
287 } catch (CancellationException e) {
288 throw new InterruptedIOException();
289 } catch (InterruptedException e) {
290 throw new InterruptedIOException();
291 } finally {
292
293
294 cs.cancelAll();
295 }
296
297 LOG.error("Imposible? Arrive at an unreachable line...");
298 return null;
299 }
300
301
302
303
304
305 static void throwEnrichedException(ExecutionException e, int retries)
306 throws RetriesExhaustedException, DoNotRetryIOException {
307 Throwable t = e.getCause();
308 assert t != null;
309
310 if (t instanceof RetriesExhaustedException) {
311 throw (RetriesExhaustedException) t;
312 }
313
314 if (t instanceof DoNotRetryIOException) {
315 throw (DoNotRetryIOException) t;
316 }
317
318 if (t instanceof NeedUnmanagedConnectionException) {
319 throw new DoNotRetryIOException(t);
320 }
321
322 RetriesExhaustedException.ThrowableWithExtraContext qt =
323 new RetriesExhaustedException.ThrowableWithExtraContext(t,
324 EnvironmentEdgeManager.currentTime(), null);
325
326 List<RetriesExhaustedException.ThrowableWithExtraContext> exceptions =
327 Collections.singletonList(qt);
328
329 throw new RetriesExhaustedException(retries, exceptions);
330 }
331
332
333
334
335
336
337
338
339
340 private void addCallsForReplica(ResultBoundedCompletionService<Result> cs,
341 RegionLocations rl, int min, int max) {
342 for (int id = min; id <= max; id++) {
343 HRegionLocation hrl = rl.getRegionLocation(id);
344 ReplicaRegionServerCallable callOnReplica = new ReplicaRegionServerCallable(id, hrl);
345 cs.submit(callOnReplica, callTimeout, id);
346 }
347 }
348
349 static RegionLocations getRegionLocations(boolean useCache, int replicaId,
350 ClusterConnection cConnection, TableName tableName, byte[] row)
351 throws RetriesExhaustedException, DoNotRetryIOException, InterruptedIOException {
352
353 RegionLocations rl;
354 try {
355 if (useCache) {
356 rl = cConnection.locateRegion(tableName, row, true, true, replicaId);
357 } else {
358 rl = cConnection.relocateRegion(tableName, row, replicaId);
359 }
360 } catch (DoNotRetryIOException e) {
361 throw e;
362 } catch (NeedUnmanagedConnectionException e) {
363 throw new DoNotRetryIOException(e);
364 } catch (RetriesExhaustedException e) {
365 throw e;
366 } catch (InterruptedIOException e) {
367 throw e;
368 } catch (IOException e) {
369 throw new RetriesExhaustedException("Can't get the location for replica " + replicaId, e);
370 }
371 if (rl == null) {
372 throw new RetriesExhaustedException("Can't get the location for replica " + replicaId);
373 }
374
375 return rl;
376 }
377 }