Skip to content

Commit 5f266e0

Browse files
committed
HBASE-22890 Verify the file integrity in persistent IOEngine
1 parent 3f84591 commit 5f266e0

File tree

5 files changed

+560
-32
lines changed

5 files changed

+560
-32
lines changed

hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/bucket/BucketCache.java

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.io.ObjectOutputStream;
3030
import java.io.Serializable;
3131
import java.nio.ByteBuffer;
32+
import java.security.NoSuchAlgorithmException;
3233
import java.util.ArrayList;
3334
import java.util.Comparator;
3435
import java.util.HashSet;
@@ -69,6 +70,7 @@
6970
import org.apache.hadoop.hbase.io.hfile.CacheableDeserializerIdManager;
7071
import org.apache.hadoop.hbase.io.hfile.CachedBlock;
7172
import org.apache.hadoop.hbase.io.hfile.HFileBlock;
73+
import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
7274
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
7375
import org.apache.hadoop.hbase.util.HasThread;
7476
import org.apache.hadoop.hbase.util.IdReadWriteLock;
@@ -242,6 +244,17 @@ public int compare(BlockCacheKey a, BlockCacheKey b) {
242244
/** In-memory bucket size */
243245
private float memoryFactor;
244246

247+
private String ioEngineName;
248+
private static final String FILE_VERIFY_ALGORITHM =
249+
"hbase.bucketcache.persistent.file.integrity.check.algorithm";
250+
private static final String DEFAULT_FILE_VERIFY_ALGORITHM = "MD5";
251+
252+
/**
253+
* Use {@link java.security.MessageDigest} class's encryption algorithms to check
254+
* persistence file integrity, default algorithm is MD5
255+
* */
256+
private String algorithm;
257+
245258
public BucketCache(String ioEngineName, long capacity, int blockSize, int[] bucketSizes,
246259
int writerThreadNum, int writerQLen, String persistencePath) throws FileNotFoundException,
247260
IOException {
@@ -252,8 +265,7 @@ public BucketCache(String ioEngineName, long capacity, int blockSize, int[] buck
252265
public BucketCache(String ioEngineName, long capacity, int blockSize, int[] bucketSizes,
253266
int writerThreadNum, int writerQLen, String persistencePath, int ioErrorsTolerationDuration,
254267
Configuration conf)
255-
throws FileNotFoundException, IOException {
256-
this.ioEngine = getIOEngineFromName(ioEngineName, capacity);
268+
throws IOException {
257269
this.writerThreads = new WriterThread[writerThreadNum];
258270
long blockNumCapacity = capacity / blockSize;
259271
if (blockNumCapacity >= Integer.MAX_VALUE) {
@@ -275,6 +287,7 @@ public BucketCache(String ioEngineName, long capacity, int blockSize, int[] buck
275287
", memoryFactor: " + memoryFactor);
276288

277289
this.cacheCapacity = capacity;
290+
this.ioEngineName = ioEngineName;
278291
this.persistencePath = persistencePath;
279292
this.blockSize = blockSize;
280293
this.ioErrorsTolerationDuration = ioErrorsTolerationDuration;
@@ -288,14 +301,15 @@ public BucketCache(String ioEngineName, long capacity, int blockSize, int[] buck
288301
this.ramCache = new ConcurrentHashMap<BlockCacheKey, RAMQueueEntry>();
289302

290303
this.backingMap = new ConcurrentHashMap<BlockCacheKey, BucketEntry>((int) blockNumCapacity);
291-
304+
this.algorithm = conf.get(FILE_VERIFY_ALGORITHM, DEFAULT_FILE_VERIFY_ALGORITHM);
305+
ioEngine = getIOEngineFromName();
292306
if (ioEngine.isPersistent() && persistencePath != null) {
293307
try {
294308
retrieveFromFile(bucketSizes);
295309
} catch (IOException ioex) {
296310
LOG.error("Can't restore from file because of", ioex);
297311
} catch (ClassNotFoundException cnfe) {
298-
LOG.error("Can't restore from file in rebuild because can't deserialise",cnfe);
312+
LOG.error("Can't restore from file in rebuild because can't deserialise", cnfe);
299313
throw new RuntimeException(cnfe);
300314
}
301315
}
@@ -359,24 +373,22 @@ public String getIoEngine() {
359373

360374
/**
361375
* Get the IOEngine from the IO engine name
362-
* @param ioEngineName
363-
* @param capacity
364376
* @return the IOEngine
365377
* @throws IOException
366378
*/
367-
private IOEngine getIOEngineFromName(String ioEngineName, long capacity)
379+
private IOEngine getIOEngineFromName()
368380
throws IOException {
369381
if (ioEngineName.startsWith("file:") || ioEngineName.startsWith("files:")) {
370382
// In order to make the usage simple, we only need the prefix 'files:' in
371383
// document whether one or multiple file(s), but also support 'file:' for
372384
// the compatibility
373385
String[] filePaths =
374386
ioEngineName.substring(ioEngineName.indexOf(":") + 1).split(FileIOEngine.FILE_DELIMITER);
375-
return new FileIOEngine(capacity, filePaths);
387+
return new FileIOEngine(algorithm, persistencePath, cacheCapacity, filePaths);
376388
} else if (ioEngineName.startsWith("offheap"))
377-
return new ByteBufferIOEngine(capacity, true);
389+
return new ByteBufferIOEngine(cacheCapacity, true);
378390
else if (ioEngineName.startsWith("heap"))
379-
return new ByteBufferIOEngine(capacity, false);
391+
return new ByteBufferIOEngine(cacheCapacity, false);
380392
else
381393
throw new IllegalArgumentException(
382394
"Don't understand io engine name for cache - prefix with file:, heap or offheap");
@@ -1021,41 +1033,48 @@ static List<RAMQueueEntry> getRAMQueueEntries(final BlockingQueue<RAMQueueEntry>
10211033

10221034
private void persistToFile() throws IOException {
10231035
assert !cacheEnabled;
1024-
FileOutputStream fos = null;
1025-
ObjectOutputStream oos = null;
1026-
try {
1036+
try (ObjectOutputStream oos = new ObjectOutputStream(
1037+
new FileOutputStream(persistencePath, false))){
10271038
if (!ioEngine.isPersistent()) {
10281039
throw new IOException("Attempt to persist non-persistent cache mappings!");
10291040
}
1030-
fos = new FileOutputStream(persistencePath, false);
1031-
oos = new ObjectOutputStream(fos);
1041+
if (ioEngine instanceof PersistentIOEngine) {
1042+
oos.write(ProtobufUtil.PB_MAGIC);
1043+
byte[] checksum = ((PersistentIOEngine) ioEngine).calculateChecksum();
1044+
oos.writeInt(checksum.length);
1045+
oos.write(checksum);
1046+
}
10321047
oos.writeLong(cacheCapacity);
10331048
oos.writeUTF(ioEngine.getClass().getName());
10341049
oos.writeUTF(backingMap.getClass().getName());
10351050
oos.writeObject(deserialiserMap);
10361051
oos.writeObject(backingMap);
1037-
} finally {
1038-
if (oos != null) oos.close();
1039-
if (fos != null) fos.close();
1052+
} catch (NoSuchAlgorithmException e) {
1053+
LOG.error("No such algorithm : " + algorithm + "! Failed to persist data on exit",e);
10401054
}
10411055
}
10421056

10431057
@SuppressWarnings("unchecked")
1044-
private void retrieveFromFile(int[] bucketSizes) throws IOException, BucketAllocatorException,
1058+
private void retrieveFromFile(int[] bucketSizes) throws IOException,
10451059
ClassNotFoundException {
10461060
File persistenceFile = new File(persistencePath);
10471061
if (!persistenceFile.exists()) {
10481062
return;
10491063
}
10501064
assert !cacheEnabled;
1051-
FileInputStream fis = null;
1052-
ObjectInputStream ois = null;
1053-
try {
1065+
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(persistencePath))){
10541066
if (!ioEngine.isPersistent())
10551067
throw new IOException(
10561068
"Attempt to restore non-persistent cache mappings!");
1057-
fis = new FileInputStream(persistencePath);
1058-
ois = new ObjectInputStream(fis);
1069+
// for backward compatibility
1070+
if (ioEngine instanceof PersistentIOEngine &&
1071+
!((PersistentIOEngine) ioEngine).isOldVersion()) {
1072+
byte[] PBMagic = new byte[ProtobufUtil.PB_MAGIC.length];
1073+
ois.read(PBMagic);
1074+
int length = ois.readInt();
1075+
byte[] persistenceChecksum = new byte[length];
1076+
ois.read(persistenceChecksum);
1077+
}
10591078
long capacitySize = ois.readLong();
10601079
if (capacitySize != cacheCapacity)
10611080
throw new IOException("Mismatched cache capacity:"
@@ -1078,9 +1097,8 @@ private void retrieveFromFile(int[] bucketSizes) throws IOException, BucketAlloc
10781097
bucketAllocator = allocator;
10791098
deserialiserMap = deserMap;
10801099
backingMap = backingMapFromFile;
1100+
blockNumber.set(backingMap.size());
10811101
} finally {
1082-
if (ois != null) ois.close();
1083-
if (fis != null) fis.close();
10841102
if (!persistenceFile.delete()) {
10851103
throw new IOException("Failed deleting persistence file "
10861104
+ persistenceFile.getAbsolutePath());
@@ -1597,4 +1615,9 @@ float getMultiFactor() {
15971615
float getMemoryFactor() {
15981616
return memoryFactor;
15991617
}
1618+
1619+
@VisibleForTesting
1620+
public UniqueIndexMap<Integer> getDeserialiserMap() {
1621+
return deserialiserMap;
1622+
}
16001623
}

0 commit comments

Comments
 (0)