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