1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.replication.regionserver;
21
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Random;
29 import java.util.SortedMap;
30 import java.util.SortedSet;
31 import java.util.TreeSet;
32 import java.util.UUID;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.CopyOnWriteArrayList;
35 import java.util.concurrent.LinkedBlockingQueue;
36 import java.util.concurrent.RejectedExecutionException;
37 import java.util.concurrent.ThreadPoolExecutor;
38 import java.util.concurrent.TimeUnit;
39
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.FileSystem;
44 import org.apache.hadoop.fs.Path;
45 import org.apache.hadoop.hbase.Server;
46 import org.apache.hadoop.hbase.TableDescriptors;
47 import org.apache.hadoop.hbase.classification.InterfaceAudience;
48 import org.apache.hadoop.hbase.regionserver.HRegionServer;
49 import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost;
50 import org.apache.hadoop.hbase.replication.ReplicationEndpoint;
51 import org.apache.hadoop.hbase.replication.ReplicationException;
52 import org.apache.hadoop.hbase.replication.ReplicationListener;
53 import org.apache.hadoop.hbase.replication.ReplicationPeer;
54 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
55 import org.apache.hadoop.hbase.replication.ReplicationPeers;
56 import org.apache.hadoop.hbase.replication.ReplicationQueueInfo;
57 import org.apache.hadoop.hbase.replication.ReplicationQueues;
58 import org.apache.hadoop.hbase.replication.ReplicationTracker;
59
60 import com.google.common.util.concurrent.ThreadFactoryBuilder;
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 @InterfaceAudience.Private
77 public class ReplicationSourceManager implements ReplicationListener {
78 private static final Log LOG =
79 LogFactory.getLog(ReplicationSourceManager.class);
80
81 private final List<ReplicationSourceInterface> sources;
82
83 private final List<ReplicationSourceInterface> oldsources;
84 private final ReplicationQueues replicationQueues;
85 private final ReplicationTracker replicationTracker;
86 private final ReplicationPeers replicationPeers;
87
88 private final UUID clusterId;
89
90 private final Server server;
91
92 private final Map<String, SortedSet<String>> walsById;
93
94 private final Map<String, SortedSet<String>> walsByIdRecoveredQueues;
95 private final Configuration conf;
96 private final FileSystem fs;
97
98 private Path latestPath;
99
100 private final Path logDir;
101
102 private final Path oldLogDir;
103
104 private final long sleepBeforeFailover;
105
106 private final ThreadPoolExecutor executor;
107
108 private final Random rand;
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 public ReplicationSourceManager(final ReplicationQueues replicationQueues,
124 final ReplicationPeers replicationPeers, final ReplicationTracker replicationTracker,
125 final Configuration conf, final Server server, final FileSystem fs, final Path logDir,
126 final Path oldLogDir, final UUID clusterId) {
127
128
129 this.sources = new CopyOnWriteArrayList<ReplicationSourceInterface>();
130 this.replicationQueues = replicationQueues;
131 this.replicationPeers = replicationPeers;
132 this.replicationTracker = replicationTracker;
133 this.server = server;
134 this.walsById = new HashMap<String, SortedSet<String>>();
135 this.walsByIdRecoveredQueues = new ConcurrentHashMap<String, SortedSet<String>>();
136 this.oldsources = new CopyOnWriteArrayList<ReplicationSourceInterface>();
137 this.conf = conf;
138 this.fs = fs;
139 this.logDir = logDir;
140 this.oldLogDir = oldLogDir;
141 this.sleepBeforeFailover =
142 conf.getLong("replication.sleep.before.failover", 30000);
143 this.clusterId = clusterId;
144 this.replicationTracker.registerListener(this);
145 this.replicationPeers.getAllPeerIds();
146
147
148 int nbWorkers = conf.getInt("replication.executor.workers", 1);
149
150
151 this.executor = new ThreadPoolExecutor(nbWorkers, nbWorkers,
152 100, TimeUnit.MILLISECONDS,
153 new LinkedBlockingQueue<Runnable>());
154 ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
155 tfb.setNameFormat("ReplicationExecutor-%d");
156 tfb.setDaemon(true);
157 this.executor.setThreadFactory(tfb.build());
158 this.rand = new Random();
159 }
160
161
162
163
164
165
166
167
168
169
170
171
172 public void logPositionAndCleanOldLogs(Path log, String id, long position,
173 boolean queueRecovered, boolean holdLogInZK) {
174 String fileName = log.getName();
175 this.replicationQueues.setLogPosition(id, fileName, position);
176 if (holdLogInZK) {
177 return;
178 }
179 cleanOldLogs(fileName, id, queueRecovered);
180 }
181
182
183
184
185
186
187
188
189 public void cleanOldLogs(String key, String id, boolean queueRecovered) {
190 if (queueRecovered) {
191 SortedSet<String> wals = walsByIdRecoveredQueues.get(id);
192 if (wals != null && !wals.first().equals(key)) {
193 cleanOldLogs(wals, key, id);
194 }
195 } else {
196 synchronized (this.walsById) {
197 SortedSet<String> wals = walsById.get(id);
198 if (!wals.first().equals(key)) {
199 cleanOldLogs(wals, key, id);
200 }
201 }
202 }
203 }
204
205 private void cleanOldLogs(SortedSet<String> wals, String key, String id) {
206 SortedSet<String> walSet = wals.headSet(key);
207 LOG.debug("Removing " + walSet.size() + " logs in the list: " + walSet);
208 for (String wal : walSet) {
209 this.replicationQueues.removeLog(id, wal);
210 }
211 walSet.clear();
212 }
213
214
215
216
217
218 protected void init() throws IOException, ReplicationException {
219 for (String id : this.replicationPeers.getPeerIds()) {
220 addSource(id);
221 }
222 List<String> currentReplicators = this.replicationQueues.getListOfReplicators();
223 if (currentReplicators == null || currentReplicators.size() == 0) {
224 return;
225 }
226 List<String> otherRegionServers = replicationTracker.getListOfRegionServers();
227 LOG.info("Current list of replicators: " + currentReplicators + " other RSs: "
228 + otherRegionServers);
229
230
231 for (String rs : currentReplicators) {
232 if (!otherRegionServers.contains(rs)) {
233 transferQueues(rs);
234 }
235 }
236 }
237
238
239
240
241
242
243
244 protected ReplicationSourceInterface addSource(String id) throws IOException,
245 ReplicationException {
246 ReplicationPeerConfig peerConfig
247 = replicationPeers.getReplicationPeerConfig(id);
248 ReplicationPeer peer = replicationPeers.getPeer(id);
249 ReplicationSourceInterface src =
250 getReplicationSource(this.conf, this.fs, this, this.replicationQueues,
251 this.replicationPeers, server, id, this.clusterId, peerConfig, peer);
252 synchronized (this.walsById) {
253 this.sources.add(src);
254 this.walsById.put(id, new TreeSet<String>());
255
256 if (this.latestPath != null) {
257 String name = this.latestPath.getName();
258 this.walsById.get(id).add(name);
259 try {
260 this.replicationQueues.addLog(src.getPeerClusterZnode(), name);
261 } catch (ReplicationException e) {
262 String message =
263 "Cannot add log to queue when creating a new source, queueId="
264 + src.getPeerClusterZnode() + ", filename=" + name;
265 server.stop(message);
266 throw e;
267 }
268 src.enqueueLog(this.latestPath);
269 }
270 }
271 src.startup();
272 return src;
273 }
274
275
276
277
278
279 public void deleteSource(String peerId, boolean closeConnection) {
280 this.replicationQueues.removeQueue(peerId);
281 if (closeConnection) {
282 this.replicationPeers.peerRemoved(peerId);
283 }
284 }
285
286
287
288
289 public void join() {
290 this.executor.shutdown();
291 if (this.sources.size() == 0) {
292 this.replicationQueues.removeAllQueues();
293 }
294 for (ReplicationSourceInterface source : this.sources) {
295 source.terminate("Region server is closing");
296 }
297 }
298
299
300
301
302
303 protected Map<String, SortedSet<String>> getWALs() {
304 return Collections.unmodifiableMap(walsById);
305 }
306
307
308
309
310
311 protected Map<String, SortedSet<String>> getWalsByIdRecoveredQueues() {
312 return Collections.unmodifiableMap(walsByIdRecoveredQueues);
313 }
314
315
316
317
318
319 public List<ReplicationSourceInterface> getSources() {
320 return this.sources;
321 }
322
323
324
325
326
327 public List<ReplicationSourceInterface> getOldSources() {
328 return this.oldsources;
329 }
330
331 void preLogRoll(Path newLog) throws IOException {
332 synchronized (this.walsById) {
333 String name = newLog.getName();
334 for (ReplicationSourceInterface source : this.sources) {
335 try {
336 this.replicationQueues.addLog(source.getPeerClusterZnode(), name);
337 } catch (ReplicationException e) {
338 throw new IOException("Cannot add log to replication queue with id="
339 + source.getPeerClusterZnode() + ", filename=" + name, e);
340 }
341 }
342 for (SortedSet<String> wals : this.walsById.values()) {
343 if (this.sources.isEmpty()) {
344
345
346 wals.clear();
347 }
348 wals.add(name);
349 }
350 }
351
352 this.latestPath = newLog;
353 }
354
355 void postLogRoll(Path newLog) throws IOException {
356
357 for (ReplicationSourceInterface source : this.sources) {
358 source.enqueueLog(newLog);
359 }
360 }
361
362
363
364
365
366
367
368
369
370
371
372 protected ReplicationSourceInterface getReplicationSource(final Configuration conf,
373 final FileSystem fs, final ReplicationSourceManager manager,
374 final ReplicationQueues replicationQueues, final ReplicationPeers replicationPeers,
375 final Server server, final String peerId, final UUID clusterId,
376 final ReplicationPeerConfig peerConfig, final ReplicationPeer replicationPeer)
377 throws IOException {
378 RegionServerCoprocessorHost rsServerHost = null;
379 TableDescriptors tableDescriptors = null;
380 if (server instanceof HRegionServer) {
381 rsServerHost = ((HRegionServer) server).getRegionServerCoprocessorHost();
382 tableDescriptors = ((HRegionServer) server).getTableDescriptors();
383 }
384 ReplicationSourceInterface src;
385 try {
386 @SuppressWarnings("rawtypes")
387 Class c = Class.forName(conf.get("replication.replicationsource.implementation",
388 ReplicationSource.class.getCanonicalName()));
389 src = (ReplicationSourceInterface) c.newInstance();
390 } catch (Exception e) {
391 LOG.warn("Passed replication source implementation throws errors, " +
392 "defaulting to ReplicationSource", e);
393 src = new ReplicationSource();
394 }
395
396 ReplicationEndpoint replicationEndpoint = null;
397 try {
398 String replicationEndpointImpl = peerConfig.getReplicationEndpointImpl();
399 if (replicationEndpointImpl == null) {
400
401 replicationEndpointImpl = HBaseInterClusterReplicationEndpoint.class.getName();
402 }
403 @SuppressWarnings("rawtypes")
404 Class c = Class.forName(replicationEndpointImpl);
405 replicationEndpoint = (ReplicationEndpoint) c.newInstance();
406 if(rsServerHost != null) {
407 ReplicationEndpoint newReplicationEndPoint = rsServerHost
408 .postCreateReplicationEndPoint(replicationEndpoint);
409 if(newReplicationEndPoint != null) {
410
411 replicationEndpoint = newReplicationEndPoint;
412 }
413 }
414 } catch (Exception e) {
415 LOG.warn("Passed replication endpoint implementation throws errors", e);
416 throw new IOException(e);
417 }
418
419 MetricsSource metrics = new MetricsSource(peerId);
420
421 src.init(conf, fs, manager, replicationQueues, replicationPeers, server, peerId,
422 clusterId, replicationEndpoint, metrics);
423
424
425 replicationEndpoint.init(new ReplicationEndpoint.Context(replicationPeer.getConfiguration(),
426 fs, peerConfig, peerId, clusterId, replicationPeer, metrics, tableDescriptors));
427
428 return src;
429 }
430
431
432
433
434
435
436
437
438
439 private void transferQueues(String rsZnode) {
440 NodeFailoverWorker transfer =
441 new NodeFailoverWorker(rsZnode, this.replicationQueues, this.replicationPeers,
442 this.clusterId);
443 try {
444 this.executor.execute(transfer);
445 } catch (RejectedExecutionException ex) {
446 LOG.info("Cancelling the transfer of " + rsZnode + " because of " + ex.getMessage());
447 }
448 }
449
450
451
452
453
454 public void closeRecoveredQueue(ReplicationSourceInterface src) {
455 LOG.info("Done with the recovered queue " + src.getPeerClusterZnode());
456 this.oldsources.remove(src);
457 deleteSource(src.getPeerClusterZnode(), false);
458 this.walsByIdRecoveredQueues.remove(src.getPeerClusterZnode());
459 }
460
461
462
463
464
465
466 public void removePeer(String id) {
467 LOG.info("Closing the following queue " + id + ", currently have "
468 + sources.size() + " and another "
469 + oldsources.size() + " that were recovered");
470 String terminateMessage = "Replication stream was removed by a user";
471 ReplicationSourceInterface srcToRemove = null;
472 List<ReplicationSourceInterface> oldSourcesToDelete =
473 new ArrayList<ReplicationSourceInterface>();
474
475 for (ReplicationSourceInterface src : oldsources) {
476 if (id.equals(src.getPeerClusterId())) {
477 oldSourcesToDelete.add(src);
478 }
479 }
480 for (ReplicationSourceInterface src : oldSourcesToDelete) {
481 src.terminate(terminateMessage);
482 closeRecoveredQueue((src));
483 }
484 LOG.info("Number of deleted recovered sources for " + id + ": "
485 + oldSourcesToDelete.size());
486
487 for (ReplicationSourceInterface src : this.sources) {
488 if (id.equals(src.getPeerClusterId())) {
489 srcToRemove = src;
490 break;
491 }
492 }
493 if (srcToRemove == null) {
494 LOG.error("The queue we wanted to close is missing " + id);
495 return;
496 }
497 srcToRemove.terminate(terminateMessage);
498 this.sources.remove(srcToRemove);
499 deleteSource(id, true);
500 }
501
502 @Override
503 public void regionServerRemoved(String regionserver) {
504 transferQueues(regionserver);
505 }
506
507 @Override
508 public void peerRemoved(String peerId) {
509 removePeer(peerId);
510 }
511
512 @Override
513 public void peerListChanged(List<String> peerIds) {
514 for (String id : peerIds) {
515 try {
516 boolean added = this.replicationPeers.peerAdded(id);
517 if (added) {
518 addSource(id);
519 }
520 } catch (Exception e) {
521 LOG.error("Error while adding a new peer", e);
522 }
523 }
524 }
525
526
527
528
529
530 class NodeFailoverWorker extends Thread {
531
532 private String rsZnode;
533 private final ReplicationQueues rq;
534 private final ReplicationPeers rp;
535 private final UUID clusterId;
536
537
538
539
540
541 public NodeFailoverWorker(String rsZnode, final ReplicationQueues replicationQueues,
542 final ReplicationPeers replicationPeers, final UUID clusterId) {
543 super("Failover-for-"+rsZnode);
544 this.rsZnode = rsZnode;
545 this.rq = replicationQueues;
546 this.rp = replicationPeers;
547 this.clusterId = clusterId;
548 }
549
550 @Override
551 public void run() {
552 if (this.rq.isThisOurZnode(rsZnode)) {
553 return;
554 }
555
556
557 try {
558 Thread.sleep(sleepBeforeFailover + (long) (rand.nextFloat() * sleepBeforeFailover));
559 } catch (InterruptedException e) {
560 LOG.warn("Interrupted while waiting before transferring a queue.");
561 Thread.currentThread().interrupt();
562 }
563
564 if (server.isStopped()) {
565 LOG.info("Not transferring queue since we are shutting down");
566 return;
567 }
568 SortedMap<String, SortedSet<String>> newQueues = null;
569
570 newQueues = this.rq.claimQueues(rsZnode);
571
572
573 if (newQueues.isEmpty()) {
574
575
576 return;
577 }
578
579 for (Map.Entry<String, SortedSet<String>> entry : newQueues.entrySet()) {
580 String peerId = entry.getKey();
581 try {
582
583 ReplicationQueueInfo replicationQueueInfo = new ReplicationQueueInfo(peerId);
584 String actualPeerId = replicationQueueInfo.getPeerId();
585 ReplicationPeer peer = replicationPeers.getPeer(actualPeerId);
586 ReplicationPeerConfig peerConfig = null;
587 try {
588 peerConfig = replicationPeers.getReplicationPeerConfig(actualPeerId);
589 } catch (ReplicationException ex) {
590 LOG.warn("Received exception while getting replication peer config, skipping replay"
591 + ex);
592 }
593 if (peer == null || peerConfig == null) {
594 LOG.warn("Skipping failover for peer:" + actualPeerId + " of node" + rsZnode);
595 continue;
596 }
597
598 ReplicationSourceInterface src =
599 getReplicationSource(conf, fs, ReplicationSourceManager.this, this.rq, this.rp,
600 server, peerId, this.clusterId, peerConfig, peer);
601 if (!this.rp.getPeerIds().contains((src.getPeerClusterId()))) {
602 src.terminate("Recovered queue doesn't belong to any current peer");
603 break;
604 }
605 oldsources.add(src);
606 SortedSet<String> walsSet = entry.getValue();
607 for (String wal : walsSet) {
608 src.enqueueLog(new Path(oldLogDir, wal));
609 }
610 src.startup();
611 walsByIdRecoveredQueues.put(peerId, walsSet);
612 } catch (IOException e) {
613
614 LOG.error("Failed creating a source", e);
615 }
616 }
617 }
618 }
619
620
621
622
623
624 public Path getOldLogDir() {
625 return this.oldLogDir;
626 }
627
628
629
630
631
632 public Path getLogDir() {
633 return this.logDir;
634 }
635
636
637
638
639
640 public FileSystem getFs() {
641 return this.fs;
642 }
643
644
645
646
647 public String getStats() {
648 StringBuffer stats = new StringBuffer();
649 for (ReplicationSourceInterface source : sources) {
650 stats.append("Normal source for cluster " + source.getPeerClusterId() + ": ");
651 stats.append(source.getStats() + "\n");
652 }
653 for (ReplicationSourceInterface oldSource : oldsources) {
654 stats.append("Recovered source for cluster/machine(s) " + oldSource.getPeerClusterId()+": ");
655 stats.append(oldSource.getStats()+ "\n");
656 }
657 return stats.toString();
658 }
659 }