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.io.InterruptedIOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.TreeMap;
30 import java.util.UUID;
31 import java.util.concurrent.atomic.AtomicLong;
32
33 import org.apache.commons.lang.StringUtils;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 import org.apache.hadoop.conf.Configuration;
37 import org.apache.hadoop.fs.Path;
38 import org.apache.hadoop.hbase.Cell;
39 import org.apache.hadoop.hbase.CellScanner;
40 import org.apache.hadoop.hbase.CellUtil;
41 import org.apache.hadoop.hbase.HBaseConfiguration;
42 import org.apache.hadoop.hbase.HConstants;
43 import org.apache.hadoop.hbase.Stoppable;
44 import org.apache.hadoop.hbase.TableName;
45 import org.apache.hadoop.hbase.TableNotFoundException;
46 import org.apache.hadoop.hbase.classification.InterfaceAudience;
47 import org.apache.hadoop.hbase.client.Connection;
48 import org.apache.hadoop.hbase.client.ConnectionFactory;
49 import org.apache.hadoop.hbase.client.Delete;
50 import org.apache.hadoop.hbase.client.Mutation;
51 import org.apache.hadoop.hbase.client.Put;
52 import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException;
53 import org.apache.hadoop.hbase.client.Row;
54 import org.apache.hadoop.hbase.client.Table;
55 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WALEntry;
56 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
57 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.BulkLoadDescriptor;
58 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.StoreDescriptor;
59 import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
60 import org.apache.hadoop.hbase.util.Bytes;
61 import org.apache.hadoop.hbase.util.Pair;
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 @InterfaceAudience.Private
79 public class ReplicationSink {
80
81 private static final Log LOG = LogFactory.getLog(ReplicationSink.class);
82 private final Configuration conf;
83
84
85 private volatile Connection sharedHtableCon;
86 private final MetricsSink metrics;
87 private final AtomicLong totalReplicatedEdits = new AtomicLong();
88 private final Object sharedHtableConLock = new Object();
89
90 private long hfilesReplicated = 0;
91 private SourceFSConfigurationProvider provider;
92
93
94
95
96
97
98
99
100 public ReplicationSink(Configuration conf, Stoppable stopper)
101 throws IOException {
102 this.conf = HBaseConfiguration.create(conf);
103 decorateConf();
104 this.metrics = new MetricsSink();
105
106 String className =
107 conf.get("hbase.replication.source.fs.conf.provider",
108 DefaultSourceFSConfigurationProvider.class.getCanonicalName());
109 try {
110 @SuppressWarnings("rawtypes")
111 Class c = Class.forName(className);
112 this.provider = (SourceFSConfigurationProvider) c.newInstance();
113 } catch (Exception e) {
114 throw new IllegalArgumentException("Configured source fs configuration provider class "
115 + className + " throws error.", e);
116 }
117 }
118
119
120
121
122
123 private void decorateConf() {
124 this.conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER,
125 this.conf.getInt("replication.sink.client.retries.number", 4));
126 this.conf.setInt(HConstants.HBASE_CLIENT_OPERATION_TIMEOUT,
127 this.conf.getInt("replication.sink.client.ops.timeout", 10000));
128 String replicationCodec = this.conf.get(HConstants.REPLICATION_CODEC_CONF_KEY);
129 if (StringUtils.isNotEmpty(replicationCodec)) {
130 this.conf.set(HConstants.RPC_CODEC_CONF_KEY, replicationCodec);
131 }
132 }
133
134
135
136
137
138
139
140
141
142
143
144
145
146 public void replicateEntries(List<WALEntry> entries, final CellScanner cells,
147 String replicationClusterId, String sourceBaseNamespaceDirPath,
148 String sourceHFileArchiveDirPath) throws IOException {
149 if (entries.isEmpty()) return;
150 if (cells == null) throw new NullPointerException("TODO: Add handling of null CellScanner");
151
152
153 try {
154 long totalReplicated = 0;
155
156
157 Map<TableName, Map<List<UUID>, List<Row>>> rowMap =
158 new TreeMap<TableName, Map<List<UUID>, List<Row>>>();
159
160
161 Map<String, List<Pair<byte[], List<String>>>> bulkLoadHFileMap = null;
162
163 for (WALEntry entry : entries) {
164 TableName table =
165 TableName.valueOf(entry.getKey().getTableName().toByteArray());
166 Cell previousCell = null;
167 Mutation m = null;
168 int count = entry.getAssociatedCellCount();
169 for (int i = 0; i < count; i++) {
170
171 if (!cells.advance()) {
172 throw new ArrayIndexOutOfBoundsException("Expected=" + count + ", index=" + i);
173 }
174 Cell cell = cells.current();
175
176 if (CellUtil.matchingQualifier(cell, WALEdit.BULK_LOAD)) {
177 if (bulkLoadHFileMap == null) {
178 bulkLoadHFileMap = new HashMap<String, List<Pair<byte[], List<String>>>>();
179 }
180 buildBulkLoadHFileMap(bulkLoadHFileMap, table, cell);
181 } else {
182
183 if (isNewRowOrType(previousCell, cell)) {
184
185 m =
186 CellUtil.isDelete(cell) ? new Delete(cell.getRowArray(), cell.getRowOffset(),
187 cell.getRowLength()) : new Put(cell.getRowArray(), cell.getRowOffset(),
188 cell.getRowLength());
189 List<UUID> clusterIds = new ArrayList<UUID>();
190 for (HBaseProtos.UUID clusterId : entry.getKey().getClusterIdsList()) {
191 clusterIds.add(toUUID(clusterId));
192 }
193 m.setClusterIds(clusterIds);
194 addToHashMultiMap(rowMap, table, clusterIds, m);
195 }
196 if (CellUtil.isDelete(cell)) {
197 ((Delete) m).addDeleteMarker(cell);
198 } else {
199 ((Put) m).add(cell);
200 }
201 previousCell = cell;
202 }
203 }
204 totalReplicated++;
205 }
206
207
208 if (!rowMap.isEmpty()) {
209 LOG.debug("Started replicating mutations.");
210 for (Entry<TableName, Map<List<UUID>, List<Row>>> entry : rowMap.entrySet()) {
211 batch(entry.getKey(), entry.getValue().values());
212 }
213 LOG.debug("Finished replicating mutations.");
214 }
215
216 if (bulkLoadHFileMap != null && !bulkLoadHFileMap.isEmpty()) {
217 LOG.debug("Started replicating bulk loaded data.");
218 HFileReplicator hFileReplicator =
219 new HFileReplicator(this.provider.getConf(this.conf, replicationClusterId),
220 sourceBaseNamespaceDirPath, sourceHFileArchiveDirPath, bulkLoadHFileMap, conf,
221 getConnection());
222 hFileReplicator.replicate();
223 LOG.debug("Finished replicating bulk loaded data.");
224 }
225
226 int size = entries.size();
227 this.metrics.setAgeOfLastAppliedOp(entries.get(size - 1).getKey().getWriteTime());
228 this.metrics.applyBatch(size + hfilesReplicated, hfilesReplicated);
229 this.totalReplicatedEdits.addAndGet(totalReplicated);
230 } catch (IOException ex) {
231 LOG.error("Unable to accept edit because:", ex);
232 throw ex;
233 }
234 }
235
236 private void buildBulkLoadHFileMap(
237 final Map<String, List<Pair<byte[], List<String>>>> bulkLoadHFileMap, TableName table,
238 Cell cell) throws IOException {
239 BulkLoadDescriptor bld = WALEdit.getBulkLoadDescriptor(cell);
240 List<StoreDescriptor> storesList = bld.getStoresList();
241 int storesSize = storesList.size();
242 for (int j = 0; j < storesSize; j++) {
243 StoreDescriptor storeDescriptor = storesList.get(j);
244 List<String> storeFileList = storeDescriptor.getStoreFileList();
245 int storeFilesSize = storeFileList.size();
246 hfilesReplicated += storeFilesSize;
247 for (int k = 0; k < storeFilesSize; k++) {
248 byte[] family = storeDescriptor.getFamilyName().toByteArray();
249
250
251 String pathToHfileFromNS = getHFilePath(table, bld, storeFileList.get(k), family);
252
253 String tableName = table.getNameWithNamespaceInclAsString();
254 if (bulkLoadHFileMap.containsKey(tableName)) {
255 List<Pair<byte[], List<String>>> familyHFilePathsList = bulkLoadHFileMap.get(tableName);
256 boolean foundFamily = false;
257 for (int i = 0; i < familyHFilePathsList.size(); i++) {
258 Pair<byte[], List<String>> familyHFilePathsPair = familyHFilePathsList.get(i);
259 if (Bytes.equals(familyHFilePathsPair.getFirst(), family)) {
260
261 familyHFilePathsPair.getSecond().add(pathToHfileFromNS);
262 foundFamily = true;
263 break;
264 }
265 }
266 if (!foundFamily) {
267
268 addFamilyAndItsHFilePathToTableInMap(family, pathToHfileFromNS, familyHFilePathsList);
269 }
270 } else {
271
272 addNewTableEntryInMap(bulkLoadHFileMap, family, pathToHfileFromNS, tableName);
273 }
274 }
275 }
276 }
277
278 private void addFamilyAndItsHFilePathToTableInMap(byte[] family, String pathToHfileFromNS,
279 List<Pair<byte[], List<String>>> familyHFilePathsList) {
280 List<String> hfilePaths = new ArrayList<String>();
281 hfilePaths.add(pathToHfileFromNS);
282 familyHFilePathsList.add(new Pair<byte[], List<String>>(family, hfilePaths));
283 }
284
285 private void addNewTableEntryInMap(
286 final Map<String, List<Pair<byte[], List<String>>>> bulkLoadHFileMap, byte[] family,
287 String pathToHfileFromNS, String tableName) {
288 List<String> hfilePaths = new ArrayList<String>();
289 hfilePaths.add(pathToHfileFromNS);
290 Pair<byte[], List<String>> newFamilyHFilePathsPair =
291 new Pair<byte[], List<String>>(family, hfilePaths);
292 List<Pair<byte[], List<String>>> newFamilyHFilePathsList =
293 new ArrayList<Pair<byte[], List<String>>>();
294 newFamilyHFilePathsList.add(newFamilyHFilePathsPair);
295 bulkLoadHFileMap.put(tableName, newFamilyHFilePathsList);
296 }
297
298 private String getHFilePath(TableName table, BulkLoadDescriptor bld, String storeFile,
299 byte[] family) {
300 return new StringBuilder(100).append(table.getNamespaceAsString()).append(Path.SEPARATOR)
301 .append(table.getQualifierAsString()).append(Path.SEPARATOR)
302 .append(Bytes.toString(bld.getEncodedRegionName().toByteArray())).append(Path.SEPARATOR)
303 .append(Bytes.toString(family)).append(Path.SEPARATOR).append(storeFile).toString();
304 }
305
306
307
308
309
310
311 private boolean isNewRowOrType(final Cell previousCell, final Cell cell) {
312 return previousCell == null || previousCell.getTypeByte() != cell.getTypeByte() ||
313 !CellUtil.matchingRow(previousCell, cell);
314 }
315
316 private java.util.UUID toUUID(final HBaseProtos.UUID uuid) {
317 return new java.util.UUID(uuid.getMostSigBits(), uuid.getLeastSigBits());
318 }
319
320
321
322
323
324
325
326
327
328
329 private <K1, K2, V> List<V> addToHashMultiMap(Map<K1, Map<K2,List<V>>> map, K1 key1, K2 key2, V value) {
330 Map<K2,List<V>> innerMap = map.get(key1);
331 if (innerMap == null) {
332 innerMap = new HashMap<K2, List<V>>();
333 map.put(key1, innerMap);
334 }
335 List<V> values = innerMap.get(key2);
336 if (values == null) {
337 values = new ArrayList<V>();
338 innerMap.put(key2, values);
339 }
340 values.add(value);
341 return values;
342 }
343
344
345
346
347 public void stopReplicationSinkServices() {
348 try {
349 if (this.sharedHtableCon != null) {
350 synchronized (sharedHtableConLock) {
351 if (this.sharedHtableCon != null) {
352 this.sharedHtableCon.close();
353 this.sharedHtableCon = null;
354 }
355 }
356 }
357 } catch (IOException e) {
358 LOG.warn("IOException while closing the connection", e);
359 }
360 }
361
362
363
364
365
366
367
368
369 protected void batch(TableName tableName, Collection<List<Row>> allRows) throws IOException {
370 if (allRows.isEmpty()) {
371 return;
372 }
373 Table table = null;
374 try {
375 Connection connection = getConnection();
376 table = connection.getTable(tableName);
377 for (List<Row> rows : allRows) {
378 table.batch(rows);
379 }
380 } catch (RetriesExhaustedWithDetailsException rewde) {
381 for (Throwable ex : rewde.getCauses()) {
382 if (ex instanceof TableNotFoundException) {
383 throw new TableNotFoundException("'" + tableName + "'");
384 }
385 }
386 throw rewde;
387 } catch (InterruptedException ix) {
388 throw (InterruptedIOException) new InterruptedIOException().initCause(ix);
389 } finally {
390 if (table != null) {
391 table.close();
392 }
393 }
394 }
395
396 private Connection getConnection() throws IOException {
397
398 Connection connection = sharedHtableCon;
399 if (connection == null) {
400 synchronized (sharedHtableConLock) {
401 connection = sharedHtableCon;
402 if (connection == null) {
403 connection = sharedHtableCon = ConnectionFactory.createConnection(conf);
404 }
405 }
406 }
407 return connection;
408 }
409
410
411
412
413
414
415 public String getStats() {
416 return this.totalReplicatedEdits.get() == 0 ? "" : "Sink: " +
417 "age in ms of last applied edit: " + this.metrics.refreshAgeOfLastAppliedOp() +
418 ", total replicated edits: " + this.totalReplicatedEdits;
419 }
420
421
422
423
424
425 public MetricsSink getSinkMetrics() {
426 return this.metrics;
427 }
428 }