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