1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.mob.mapreduce;
20
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.UUID;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.hadoop.classification.InterfaceAudience;
33 import org.apache.hadoop.conf.Configuration;
34 import org.apache.hadoop.fs.FSDataOutputStream;
35 import org.apache.hadoop.fs.FileStatus;
36 import org.apache.hadoop.fs.FileSystem;
37 import org.apache.hadoop.fs.Path;
38 import org.apache.hadoop.fs.PathFilter;
39 import org.apache.hadoop.hbase.Cell;
40 import org.apache.hadoop.hbase.HColumnDescriptor;
41 import org.apache.hadoop.hbase.HConstants;
42 import org.apache.hadoop.hbase.InvalidFamilyOperationException;
43 import org.apache.hadoop.hbase.KeyValue;
44 import org.apache.hadoop.hbase.KeyValueUtil;
45 import org.apache.hadoop.hbase.TableName;
46 import org.apache.hadoop.hbase.client.HBaseAdmin;
47 import org.apache.hadoop.hbase.client.HTable;
48 import org.apache.hadoop.hbase.io.HFileLink;
49 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
50 import org.apache.hadoop.hbase.mapreduce.TableInputFormat;
51 import org.apache.hadoop.hbase.mob.MobConstants;
52 import org.apache.hadoop.hbase.mob.MobFile;
53 import org.apache.hadoop.hbase.mob.MobFileName;
54 import org.apache.hadoop.hbase.mob.MobUtils;
55 import org.apache.hadoop.hbase.mob.mapreduce.SweepJob.DummyMobAbortable;
56 import org.apache.hadoop.hbase.mob.mapreduce.SweepJob.SweepCounter;
57 import org.apache.hadoop.hbase.regionserver.BloomType;
58 import org.apache.hadoop.hbase.regionserver.DefaultMemStore;
59 import org.apache.hadoop.hbase.regionserver.StoreFile;
60 import org.apache.hadoop.hbase.regionserver.StoreFileScanner;
61 import org.apache.hadoop.hbase.util.Bytes;
62 import org.apache.hadoop.hbase.util.FSUtils;
63 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
64 import org.apache.hadoop.io.IOUtils;
65 import org.apache.hadoop.io.SequenceFile;
66 import org.apache.hadoop.io.SequenceFile.CompressionType;
67 import org.apache.hadoop.io.Text;
68 import org.apache.hadoop.io.Writable;
69 import org.apache.hadoop.mapreduce.Reducer;
70 import org.apache.zookeeper.KeeperException;
71
72
73
74
75
76
77
78
79
80
81
82
83 @InterfaceAudience.Private
84 public class SweepReducer extends Reducer<Text, KeyValue, Writable, Writable> {
85
86 private static final Log LOG = LogFactory.getLog(SweepReducer.class);
87
88 private SequenceFile.Writer writer = null;
89 private MemStoreWrapper memstore;
90 private Configuration conf;
91 private FileSystem fs;
92
93 private Path familyDir;
94 private CacheConfig cacheConfig;
95 private long compactionBegin;
96 private HTable table;
97 private HColumnDescriptor family;
98 private long mobCompactionDelay;
99 private Path mobTableDir;
100
101 @Override
102 protected void setup(Context context) throws IOException, InterruptedException {
103 this.conf = context.getConfiguration();
104 this.fs = FileSystem.get(conf);
105
106 mobCompactionDelay = conf.getLong(SweepJob.MOB_SWEEP_JOB_DELAY, SweepJob.ONE_DAY);
107 String tableName = conf.get(TableInputFormat.INPUT_TABLE);
108 String familyName = conf.get(TableInputFormat.SCAN_COLUMN_FAMILY);
109 TableName tn = TableName.valueOf(tableName);
110 this.familyDir = MobUtils.getMobFamilyPath(conf, tn, familyName);
111 HBaseAdmin admin = new HBaseAdmin(this.conf);
112 try {
113 family = admin.getTableDescriptor(tn).getFamily(Bytes.toBytes(familyName));
114 if (family == null) {
115
116 throw new InvalidFamilyOperationException("Column family '" + familyName
117 + "' does not exist. It might be removed.");
118 }
119 } finally {
120 try {
121 admin.close();
122 } catch (IOException e) {
123 LOG.warn("Fail to close the HBaseAdmin", e);
124 }
125 }
126
127 Configuration copyOfConf = new Configuration(conf);
128 copyOfConf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0f);
129 this.cacheConfig = new CacheConfig(copyOfConf);
130
131 table = new HTable(this.conf, Bytes.toBytes(tableName));
132 table.setAutoFlush(false, false);
133
134 table.setWriteBufferSize(1 * 1024 * 1024);
135 memstore = new MemStoreWrapper(context, fs, table, family, new DefaultMemStore(), cacheConfig);
136
137
138
139
140 this.compactionBegin = conf.getLong(MobConstants.MOB_SWEEP_TOOL_COMPACTION_START_DATE, 0);
141 mobTableDir = FSUtils.getTableDir(MobUtils.getMobHome(conf), tn);
142 }
143
144 private SweepPartition createPartition(SweepPartitionId id, Context context) throws IOException {
145 return new SweepPartition(id, context);
146 }
147
148 @Override
149 public void run(Context context) throws IOException, InterruptedException {
150 String jobId = context.getConfiguration().get(SweepJob.SWEEP_JOB_ID);
151 String owner = context.getConfiguration().get(SweepJob.SWEEP_JOB_SERVERNAME);
152 String sweeperNode = context.getConfiguration().get(SweepJob.SWEEP_JOB_TABLE_NODE);
153 ZooKeeperWatcher zkw = new ZooKeeperWatcher(context.getConfiguration(), jobId,
154 new DummyMobAbortable());
155 FSDataOutputStream fout = null;
156 try {
157 SweepJobNodeTracker tracker = new SweepJobNodeTracker(zkw, sweeperNode, owner);
158 tracker.start();
159 setup(context);
160
161 String dir = this.conf.get(SweepJob.WORKING_VISITED_DIR_KEY);
162 Path nameFilePath = new Path(dir, UUID.randomUUID().toString()
163 .replace("-", MobConstants.EMPTY_STRING));
164 fout = fs.create(nameFilePath, true);
165 writer = SequenceFile.createWriter(context.getConfiguration(), fout, String.class,
166 String.class, CompressionType.NONE, null);
167 SweepPartitionId id;
168 SweepPartition partition = null;
169
170 while (context.nextKey()) {
171 Text key = context.getCurrentKey();
172 String keyString = key.toString();
173 id = SweepPartitionId.create(keyString);
174 if (null == partition || !id.equals(partition.getId())) {
175
176 if (null != partition) {
177
178
179 partition.close();
180 }
181
182 partition = createPartition(id, context);
183 }
184 if (partition != null) {
185
186 partition.execute(key, context.getValues());
187 }
188 }
189 if (null != partition) {
190 partition.close();
191 }
192 writer.hflush();
193 } catch (KeeperException e) {
194 throw new IOException(e);
195 } finally {
196 cleanup(context);
197 zkw.close();
198 if (writer != null) {
199 IOUtils.closeStream(writer);
200 }
201 if (fout != null) {
202 IOUtils.closeStream(fout);
203 }
204 if (table != null) {
205 try {
206 table.close();
207 } catch (IOException e) {
208 LOG.warn(e);
209 }
210 }
211 }
212
213 }
214
215
216
217
218
219 public class SweepPartition {
220
221 private final SweepPartitionId id;
222 private final Context context;
223 private boolean memstoreUpdated = false;
224 private boolean mergeSmall = false;
225 private final Map<String, MobFileStatus> fileStatusMap = new HashMap<String, MobFileStatus>();
226 private final List<Path> toBeDeleted = new ArrayList<Path>();
227
228 public SweepPartition(SweepPartitionId id, Context context) throws IOException {
229 this.id = id;
230 this.context = context;
231 memstore.setPartitionId(id);
232 init();
233 }
234
235 public SweepPartitionId getId() {
236 return this.id;
237 }
238
239
240
241
242
243
244 private void init() throws IOException {
245 FileStatus[] fileStats = listStatus(familyDir, id.getStartKey());
246 if (null == fileStats) {
247 return;
248 }
249
250 int smallFileCount = 0;
251 float compactionRatio = conf.getFloat(MobConstants.MOB_SWEEP_TOOL_COMPACTION_RATIO,
252 MobConstants.DEFAULT_SWEEP_TOOL_MOB_COMPACTION_RATIO);
253 long compactionMergeableSize = conf.getLong(
254 MobConstants.MOB_SWEEP_TOOL_COMPACTION_MERGEABLE_SIZE,
255 MobConstants.DEFAULT_SWEEP_TOOL_MOB_COMPACTION_MERGEABLE_SIZE);
256
257
258 for (FileStatus fileStat : fileStats) {
259 MobFileStatus mobFileStatus = null;
260 if (!HFileLink.isHFileLink(fileStat.getPath())) {
261 mobFileStatus = new MobFileStatus(fileStat, compactionRatio, compactionMergeableSize);
262 if (mobFileStatus.needMerge()) {
263 smallFileCount++;
264 }
265
266 fileStatusMap.put(fileStat.getPath().getName(), mobFileStatus);
267 }
268 }
269 if (smallFileCount >= 2) {
270
271 this.mergeSmall = true;
272 }
273 }
274
275
276
277
278
279
280 public void close() throws IOException {
281 if (null == id) {
282 return;
283 }
284
285 if (memstoreUpdated) {
286 memstore.flushMemStore();
287 }
288 List<StoreFile> storeFiles = new ArrayList<StoreFile>(toBeDeleted.size());
289
290 for (Path path : toBeDeleted) {
291 LOG.info("[In Partition close] Delete the file " + path + " in partition close");
292 storeFiles.add(new StoreFile(fs, path, conf, cacheConfig, BloomType.NONE));
293 }
294 if (!storeFiles.isEmpty()) {
295 try {
296 MobUtils.removeMobFiles(conf, fs, table.getName(), mobTableDir, family.getName(),
297 storeFiles);
298 context.getCounter(SweepCounter.FILE_TO_BE_MERGE_OR_CLEAN).increment(storeFiles.size());
299 } catch (IOException e) {
300 LOG.error("Fail to archive the store files " + storeFiles, e);
301 }
302 storeFiles.clear();
303 }
304 fileStatusMap.clear();
305 }
306
307
308
309
310
311
312
313 public void execute(Text fileName, Iterable<KeyValue> values) throws IOException {
314 if (null == values) {
315 return;
316 }
317 MobFileName mobFileName = MobFileName.create(fileName.toString());
318 LOG.info("[In reducer] The file name: " + fileName.toString());
319 MobFileStatus mobFileStat = fileStatusMap.get(mobFileName.getFileName());
320 if (null == mobFileStat) {
321 LOG.info("[In reducer] Cannot find the file, probably this record is obsolete");
322 return;
323 }
324
325 if (compactionBegin - mobFileStat.getFileStatus().getModificationTime()
326 <= mobCompactionDelay) {
327 return;
328 }
329
330 writer.append(mobFileName.getFileName(), MobConstants.EMPTY_STRING);
331 Set<KeyValue> kvs = new HashSet<KeyValue>();
332 for (KeyValue kv : values) {
333 if (kv.getValueLength() > Bytes.SIZEOF_INT) {
334 mobFileStat.addValidSize(Bytes.toInt(kv.getValueArray(), kv.getValueOffset(),
335 Bytes.SIZEOF_INT));
336 }
337 kvs.add(kv.createKeyOnly(false));
338 }
339
340 if (mobFileStat.needClean() || (mergeSmall && mobFileStat.needMerge())) {
341 context.getCounter(SweepCounter.INPUT_FILE_COUNT).increment(1);
342 MobFile file = MobFile.create(fs,
343 new Path(familyDir, mobFileName.getFileName()), conf, cacheConfig);
344 StoreFileScanner scanner = null;
345 file.open();
346 try {
347 scanner = file.getScanner();
348 scanner.seek(KeyValueUtil.createFirstOnRow(HConstants.EMPTY_BYTE_ARRAY));
349 Cell cell;
350 while (null != (cell = scanner.next())) {
351 KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
352 KeyValue keyOnly = kv.createKeyOnly(false);
353 if (kvs.contains(keyOnly)) {
354
355 memstore.addToMemstore(kv);
356 memstoreUpdated = true;
357 }
358 }
359 } finally {
360 if (scanner != null) {
361 scanner.close();
362 }
363 file.close();
364 }
365 toBeDeleted.add(mobFileStat.getFileStatus().getPath());
366 }
367 }
368
369
370
371
372
373
374
375
376 private FileStatus[] listStatus(Path p, String prefix) throws IOException {
377 return fs.listStatus(p, new PathPrefixFilter(prefix));
378 }
379 }
380
381 static class PathPrefixFilter implements PathFilter {
382
383 private final String prefix;
384
385 public PathPrefixFilter(String prefix) {
386 this.prefix = prefix;
387 }
388
389 public boolean accept(Path path) {
390 return path.getName().startsWith(prefix, 0);
391 }
392
393 }
394
395
396
397
398
399
400
401 public static class SweepPartitionId {
402 private String date;
403 private String startKey;
404
405 public SweepPartitionId(MobFileName fileName) {
406 this.date = fileName.getDate();
407 this.startKey = fileName.getStartKey();
408 }
409
410 public SweepPartitionId(String date, String startKey) {
411 this.date = date;
412 this.startKey = startKey;
413 }
414
415 public static SweepPartitionId create(String key) {
416 return new SweepPartitionId(MobFileName.create(key));
417 }
418
419 @Override
420 public boolean equals(Object anObject) {
421 if (this == anObject) {
422 return true;
423 }
424 if (anObject instanceof SweepPartitionId) {
425 SweepPartitionId another = (SweepPartitionId) anObject;
426 if (this.date.equals(another.getDate()) && this.startKey.equals(another.getStartKey())) {
427 return true;
428 }
429 }
430 return false;
431 }
432
433 public String getDate() {
434 return this.date;
435 }
436
437 public String getStartKey() {
438 return this.startKey;
439 }
440
441 public void setDate(String date) {
442 this.date = date;
443 }
444
445 public void setStartKey(String startKey) {
446 this.startKey = startKey;
447 }
448 }
449
450
451
452
453 private static class MobFileStatus {
454 private FileStatus fileStatus;
455 private int validSize;
456 private long size;
457
458 private float compactionRatio = MobConstants.DEFAULT_SWEEP_TOOL_MOB_COMPACTION_RATIO;
459 private long compactionMergeableSize =
460 MobConstants.DEFAULT_SWEEP_TOOL_MOB_COMPACTION_MERGEABLE_SIZE;
461
462
463
464
465
466
467
468
469
470
471 public MobFileStatus(FileStatus fileStatus, float compactionRatio,
472 long compactionMergeableSize) {
473 this.fileStatus = fileStatus;
474 this.size = fileStatus.getLen();
475 validSize = 0;
476 this.compactionRatio = compactionRatio;
477 this.compactionMergeableSize = compactionMergeableSize;
478 }
479
480
481
482
483
484 public void addValidSize(int size) {
485 this.validSize += size;
486 }
487
488
489
490
491
492
493 public boolean needClean() {
494 return validSize < compactionRatio * size;
495 }
496
497
498
499
500
501
502 public boolean needMerge() {
503 return this.size < compactionMergeableSize;
504 }
505
506
507
508
509
510 public FileStatus getFileStatus() {
511 return fileStatus;
512 }
513 }
514 }