1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.replication.regionserver;
20
21 import java.io.IOException;
22 import java.net.ConnectException;
23 import java.net.SocketTimeoutException;
24 import java.net.UnknownHostException;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.Callable;
31 import java.util.concurrent.CompletionService;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.ExecutorCompletionService;
34 import java.util.concurrent.Future;
35 import java.util.concurrent.LinkedBlockingQueue;
36 import java.util.concurrent.ThreadPoolExecutor;
37 import java.util.concurrent.TimeUnit;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40
41 import org.apache.commons.lang.StringUtils;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.apache.hadoop.conf.Configuration;
45 import org.apache.hadoop.fs.Path;
46 import org.apache.hadoop.hbase.Abortable;
47 import org.apache.hadoop.hbase.HBaseConfiguration;
48 import org.apache.hadoop.hbase.HConstants;
49 import org.apache.hadoop.hbase.TableName;
50 import org.apache.hadoop.hbase.TableNotFoundException;
51 import org.apache.hadoop.hbase.classification.InterfaceAudience;
52 import org.apache.hadoop.hbase.client.Connection;
53 import org.apache.hadoop.hbase.client.ConnectionFactory;
54 import org.apache.hadoop.hbase.client.HBaseAdmin;
55 import org.apache.hadoop.hbase.client.HConnection;
56 import org.apache.hadoop.hbase.client.HConnectionManager;
57 import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException;
58 import org.apache.hadoop.hbase.ipc.RpcServer;
59 import org.apache.hadoop.hbase.protobuf.ReplicationProtbufUtil;
60 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.AdminService.BlockingInterface;
61 import org.apache.hadoop.hbase.replication.HBaseReplicationEndpoint;
62 import org.apache.hadoop.hbase.replication.ReplicationPeer.PeerState;
63 import org.apache.hadoop.hbase.replication.regionserver.ReplicationSinkManager.SinkPeer;
64 import org.apache.hadoop.hbase.util.Bytes;
65 import org.apache.hadoop.hbase.util.FSUtils;
66 import org.apache.hadoop.hbase.wal.WAL.Entry;
67 import org.apache.hadoop.ipc.RemoteException;
68
69 import com.google.common.annotations.VisibleForTesting;
70
71
72
73
74
75
76
77
78
79
80
81
82 @InterfaceAudience.Private
83 public class HBaseInterClusterReplicationEndpoint extends HBaseReplicationEndpoint {
84
85 private static final Log LOG = LogFactory.getLog(HBaseInterClusterReplicationEndpoint.class);
86
87 private static final long DEFAULT_MAX_TERMINATION_WAIT_MULTIPLIER = 2;
88
89 private HConnection conn;
90 private Configuration localConf;
91 private Configuration conf;
92
93 private long sleepForRetries;
94
95 private int maxRetriesMultiplier;
96
97 private int socketTimeoutMultiplier;
98
99 private long maxTerminationWait;
100
101 private int replicationRpcLimit;
102
103 private MetricsSource metrics;
104
105 private ReplicationSinkManager replicationSinkMgr;
106 private boolean peersSelected = false;
107 private String replicationClusterId = "";
108 private ThreadPoolExecutor exec;
109 private int maxThreads;
110 private Path baseNamespaceDir;
111 private Path hfileArchiveDir;
112 private boolean replicationBulkLoadDataEnabled;
113 private Abortable abortable;
114 private boolean dropOnDeletedTables;
115
116 @Override
117 public void init(Context context) throws IOException {
118 super.init(context);
119 this.conf = HBaseConfiguration.create(ctx.getConfiguration());
120 this.localConf = HBaseConfiguration.create(ctx.getLocalConfiguration());
121 decorateConf();
122 this.maxRetriesMultiplier = this.conf.getInt("replication.source.maxretriesmultiplier", 300);
123 this.socketTimeoutMultiplier = this.conf.getInt("replication.source.socketTimeoutMultiplier",
124 maxRetriesMultiplier);
125
126
127 long maxTerminationWaitMultiplier = this.conf.getLong(
128 "replication.source.maxterminationmultiplier",
129 DEFAULT_MAX_TERMINATION_WAIT_MULTIPLIER);
130 this.maxTerminationWait = maxTerminationWaitMultiplier *
131 this.conf.getLong(HConstants.HBASE_RPC_TIMEOUT_KEY, HConstants.DEFAULT_HBASE_RPC_TIMEOUT);
132
133
134
135 this.conn = HConnectionManager.createConnection(this.conf);
136 this.sleepForRetries =
137 this.conf.getLong("replication.source.sleepforretries", 1000);
138 this.metrics = context.getMetrics();
139
140 this.replicationSinkMgr = new ReplicationSinkManager(conn, ctx.getPeerId(), this, this.conf);
141
142 this.maxThreads = this.conf.getInt(HConstants.REPLICATION_SOURCE_MAXTHREADS_KEY,
143 HConstants.REPLICATION_SOURCE_MAXTHREADS_DEFAULT);
144 this.exec = new ThreadPoolExecutor(maxThreads, maxThreads, 60, TimeUnit.SECONDS,
145 new LinkedBlockingQueue<Runnable>());
146 this.exec.allowCoreThreadTimeOut(true);
147 this.abortable = ctx.getAbortable();
148
149
150
151 this.replicationRpcLimit = (int)(0.95 * (double)conf.getLong(RpcServer.MAX_REQUEST_SIZE,
152 RpcServer.DEFAULT_MAX_REQUEST_SIZE));
153 this.dropOnDeletedTables =
154 this.conf.getBoolean(HConstants.REPLICATION_DROP_ON_DELETED_TABLE_KEY, false);
155
156 this.replicationBulkLoadDataEnabled =
157 conf.getBoolean(HConstants.REPLICATION_BULKLOAD_ENABLE_KEY,
158 HConstants.REPLICATION_BULKLOAD_ENABLE_DEFAULT);
159 if (this.replicationBulkLoadDataEnabled) {
160 replicationClusterId = this.conf.get(HConstants.REPLICATION_CLUSTER_ID);
161 }
162
163 Path rootDir = FSUtils.getRootDir(conf);
164 Path baseNSDir = new Path(HConstants.BASE_NAMESPACE_DIR);
165 baseNamespaceDir = new Path(rootDir, baseNSDir);
166 hfileArchiveDir = new Path(rootDir, new Path(HConstants.HFILE_ARCHIVE_DIRECTORY, baseNSDir));
167 }
168
169 private void decorateConf() {
170 String replicationCodec = this.conf.get(HConstants.REPLICATION_CODEC_CONF_KEY);
171 if (StringUtils.isNotEmpty(replicationCodec)) {
172 this.conf.set(HConstants.RPC_CODEC_CONF_KEY, replicationCodec);
173 }
174 }
175
176 private void connectToPeers() {
177 getRegionServers();
178
179 int sleepMultiplier = 1;
180
181
182 while (this.isRunning() && replicationSinkMgr.getNumSinks() == 0) {
183 replicationSinkMgr.chooseSinks();
184 if (this.isRunning() && replicationSinkMgr.getNumSinks() == 0) {
185 if (sleepForRetries("Waiting for peers", sleepMultiplier)) {
186 sleepMultiplier++;
187 }
188 }
189 }
190 }
191
192
193
194
195
196
197
198 protected boolean sleepForRetries(String msg, int sleepMultiplier) {
199 try {
200 if (LOG.isTraceEnabled()) {
201 LOG.trace(msg + ", sleeping " + sleepForRetries + " times " + sleepMultiplier);
202 }
203 Thread.sleep(this.sleepForRetries * sleepMultiplier);
204 } catch (InterruptedException e) {
205 LOG.debug("Interrupted while sleeping between retries");
206 }
207 return sleepMultiplier < maxRetriesMultiplier;
208 }
209
210 private List<List<Entry>> createBatches(final List<Entry> entries) {
211 int numSinks = Math.max(replicationSinkMgr.getNumSinks(), 1);
212 int n = Math.min(Math.min(this.maxThreads, entries.size()/100+1), numSinks);
213
214 Map<Integer, List<Entry>> entryMap = new HashMap<>(n);
215 List<List<Entry>> entryLists = new ArrayList<>();
216 int[] sizes = new int[n];
217
218 for (int i = 0; i < n; i++) {
219 entryMap.put(i, new ArrayList<Entry>(entries.size()/n+1));
220 }
221
222 for (Entry e: entries) {
223 int index = Math.abs(Bytes.hashCode(e.getKey().getEncodedRegionName())%n);
224 int entrySize = (int)e.getKey().estimatedSerializedSizeOf() +
225 (int)e.getEdit().estimatedSerializedSizeOf();
226
227 if (sizes[index] > 0
228 sizes[index] + entrySize > replicationRpcLimit) {
229 entryLists.add(entryMap.get(index));
230 entryMap.put(index, new ArrayList<Entry>());
231 sizes[index] = 0;
232 }
233 entryMap.get(index).add(e);
234 sizes[index] += entrySize;
235 }
236
237 entryLists.addAll(entryMap.values());
238 return entryLists;
239 }
240
241 private TableName parseTable(String msg) {
242
243 Pattern p = Pattern.compile("TableNotFoundException: \\'([\\S]*)\\'");
244 Matcher m = p.matcher(msg);
245 if (m.find()) {
246 String table = m.group(1);
247 try {
248
249 TableName.valueOf(TableName.isLegalFullyQualifiedTableName(Bytes.toBytes(table)));
250 return TableName.valueOf(table);
251 } catch (IllegalArgumentException ignore) {
252 }
253 }
254 return null;
255 }
256
257
258 private List<List<Entry>> filterBatches(final List<List<Entry>> oldEntryList, TableName table) {
259 List<List<Entry>> entryLists = new ArrayList<>();
260 for (List<Entry> entries : oldEntryList) {
261 ArrayList<Entry> thisList = new ArrayList<Entry>(entries.size());
262 entryLists.add(thisList);
263 for (Entry e : entries) {
264 if (!e.getKey().getTablename().equals(table)) {
265 thisList.add(e);
266 }
267 }
268 }
269 return entryLists;
270 }
271
272 private void reconnectToPeerCluster() {
273 HConnection connection = null;
274 try {
275 connection = HConnectionManager.createConnection(this.conf);
276 } catch (IOException ioe) {
277 LOG.warn("Failed to create connection for peer cluster", ioe);
278 }
279 if (connection != null) {
280 this.conn = connection;
281 }
282 }
283
284
285
286
287 @Override
288 public boolean replicate(ReplicateContext replicateContext) {
289 CompletionService<Integer> pool = new ExecutorCompletionService<Integer>(this.exec);
290 List<List<Entry>> batches;
291 String walGroupId = replicateContext.getWalGroupId();
292 int sleepMultiplier = 1;
293
294 if (!peersSelected && this.isRunning()) {
295 connectToPeers();
296 peersSelected = true;
297 }
298
299 int numSinks = replicationSinkMgr.getNumSinks();
300 if (numSinks == 0) {
301 LOG.warn("No replication sinks found, returning without replicating. The source should retry"
302 + " with the same set of edits.");
303 return false;
304 }
305
306 batches = createBatches(replicateContext.getEntries());
307
308 while (this.isRunning() && !exec.isShutdown()) {
309 if (!isPeerEnabled()) {
310 if (sleepForRetries("Replication is disabled", sleepMultiplier)) {
311 sleepMultiplier++;
312 }
313 continue;
314 }
315 if (this.conn == null || this.conn.isClosed()) {
316 reconnectToPeerCluster();
317 }
318 try {
319 int futures = 0;
320 for (int i=0; i<batches.size(); i++) {
321 List<Entry> entries = batches.get(i);
322 if (!entries.isEmpty()) {
323 if (LOG.isTraceEnabled()) {
324 LOG.trace("Submitting " + entries.size() +
325 " entries of total size " + replicateContext.getSize());
326 }
327
328 pool.submit(createReplicator(entries, i));
329 futures++;
330 }
331 }
332 IOException iox = null;
333
334 long lastWriteTime = 0;
335 for (int i=0; i<futures; i++) {
336 try {
337
338
339 Future<Integer> f = pool.take();
340 int index = f.get().intValue();
341 List<Entry> batch = batches.get(index);
342 batches.set(index, Collections.<Entry>emptyList());
343
344 long writeTime = batch.get(batch.size() - 1).getKey().getWriteTime();
345 if (writeTime > lastWriteTime) {
346 lastWriteTime = writeTime;
347 }
348 } catch (InterruptedException ie) {
349 iox = new IOException(ie);
350 } catch (ExecutionException ee) {
351
352 iox = (IOException)ee.getCause();
353 }
354 }
355 if (iox != null) {
356
357 throw iox;
358 }
359
360 if (lastWriteTime > 0) {
361 this.metrics.setAgeOfLastShippedOp(lastWriteTime, walGroupId);
362 }
363 return true;
364
365 } catch (IOException ioe) {
366
367 this.metrics.refreshAgeOfLastShippedOp(walGroupId);
368 if (ioe instanceof RemoteException) {
369 ioe = ((RemoteException) ioe).unwrapRemoteException();
370 LOG.warn("Can't replicate because of an error on the remote cluster: ", ioe);
371 if (ioe instanceof TableNotFoundException) {
372 if (dropOnDeletedTables) {
373
374
375 TableName table = parseTable(ioe.getMessage());
376 if (table != null) {
377 try (Connection localConn =
378 ConnectionFactory.createConnection(ctx.getLocalConfiguration())) {
379 if (!localConn.getAdmin().tableExists(table)) {
380
381
382
383 LOG.info("Missing table detected at sink, local table also does not exist, filtering edits for '"+table+"'");
384 batches = filterBatches(batches, table);
385 continue;
386 }
387 } catch (IOException iox) {
388 LOG.warn("Exception checking for local table: ", iox);
389 }
390 }
391 }
392
393 } else {
394 LOG.warn("Peer encountered RemoteException, rechecking all sinks: ", ioe);
395 replicationSinkMgr.chooseSinks();
396 }
397 } else {
398 if (ioe instanceof SocketTimeoutException) {
399
400
401
402 sleepForRetries("Encountered a SocketTimeoutException. Since the " +
403 "call to the remote cluster timed out, which is usually " +
404 "caused by a machine failure or a massive slowdown",
405 this.socketTimeoutMultiplier);
406 } else if (ioe instanceof ConnectException || ioe instanceof UnknownHostException) {
407 LOG.warn("Peer is unavailable, rechecking all sinks: ", ioe);
408 replicationSinkMgr.chooseSinks();
409 } else {
410 LOG.warn("Can't replicate because of a local or network error: ", ioe);
411 }
412 }
413 if (sleepForRetries("Since we are unable to replicate", sleepMultiplier)) {
414 sleepMultiplier++;
415 }
416 }
417 }
418 return false;
419 }
420
421 protected boolean isPeerEnabled() {
422 return ctx.getReplicationPeer().getPeerState() == PeerState.ENABLED;
423 }
424
425 @Override
426 protected void doStop() {
427 disconnect();
428 if (this.conn != null) {
429 try {
430 this.conn.close();
431 this.conn = null;
432 } catch (IOException e) {
433 LOG.warn("Failed to close the connection");
434 }
435 }
436
437 exec.shutdown();
438 try {
439 exec.awaitTermination(maxTerminationWait, TimeUnit.MILLISECONDS);
440 } catch (InterruptedException e) {
441 }
442
443 if (!exec.isTerminated()) {
444 String errMsg = "HBaseInterClusterReplicationEndpoint termination failed. The " +
445 "ThreadPoolExecutor failed to finish all tasks within " + maxTerminationWait + "ms. " +
446 "Aborting to prevent Replication from deadlocking. See HBASE-16081.";
447 abortable.abort(errMsg, new IOException(errMsg));
448 }
449 close();
450 notifyStopped();
451 }
452
453
454 @Override
455 public State stopAndWait() {
456 doStop();
457 return super.stopAndWait();
458 }
459
460 @VisibleForTesting
461 protected Replicator createReplicator(List<Entry> entries, int ordinal) {
462 return new Replicator(entries, ordinal);
463 }
464
465 @VisibleForTesting
466 protected class Replicator implements Callable<Integer> {
467 private List<Entry> entries;
468 private int ordinal;
469 public Replicator(List<Entry> entries, int ordinal) {
470 this.entries = entries;
471 this.ordinal = ordinal;
472 }
473
474 protected void replicateEntries(BlockingInterface rrs, final List<Entry> batch,
475 String replicationClusterId, Path baseNamespaceDir, Path hfileArchiveDir)
476 throws IOException {
477 if (LOG.isTraceEnabled()) {
478 long size = 0;
479 for (Entry e: entries) {
480 size += e.getKey().estimatedSerializedSizeOf();
481 size += e.getEdit().estimatedSerializedSizeOf();
482 }
483 LOG.trace("Replicating batch " + System.identityHashCode(entries) + " of " +
484 entries.size() + " entries with total size " + size + " bytes to " +
485 replicationClusterId);
486 }
487 try {
488 ReplicationProtbufUtil.replicateWALEntry(rrs, batch.toArray(new Entry[batch.size()]),
489 replicationClusterId, baseNamespaceDir, hfileArchiveDir);
490 if (LOG.isTraceEnabled()) {
491 LOG.trace("Completed replicating batch " + System.identityHashCode(entries));
492 }
493 } catch (IOException e) {
494 if (LOG.isTraceEnabled()) {
495 LOG.trace("Failed replicating batch " + System.identityHashCode(entries), e);
496 }
497 throw e;
498 }
499 }
500
501 @Override
502 public Integer call() throws IOException {
503 SinkPeer sinkPeer = null;
504 try {
505 sinkPeer = replicationSinkMgr.getReplicationSink();
506 BlockingInterface rrs = sinkPeer.getRegionServer();
507 replicateEntries(rrs, entries, replicationClusterId, baseNamespaceDir, hfileArchiveDir);
508 replicationSinkMgr.reportSinkSuccess(sinkPeer);
509 return ordinal;
510 } catch (IOException ioe) {
511 if (sinkPeer != null) {
512 replicationSinkMgr.reportBadSink(sinkPeer);
513 }
514 throw ioe;
515 }
516 }
517 }
518 }