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