MapDb学习笔记

2022-06-22 00:00:00 数据 的是 内存 性能 分段

文章目录
MapDb学习笔记
一、介绍
二、快速开始
Hello World
快速提示
DB 和 DBMaker
数据结构
HtreeMap
简介
重要特性和参数
Expiration
Shadrder Store
原理
BtreeMap
简介
重要参数
serializers
MapDB数据结构和JDK Collection性能比较
环境:
测试
JDK ConcurrentHashMap
MapDb学习笔记
一、介绍
MapDb是一个开源的,内嵌的Java数据引擎和集合框架。提供了Map,Set,List,Queue,BitMap,支持范围查询,数据过期,压缩,堆外存储的特性。MapDb很有可能是性能好的Java数据库,它的性能可以和Java.util包下面的集合相媲美。MapDB还提供了一些的特性,比如说ACID事务,数据快照和增量备份等等。

二、快速开始
MapDb是非常灵活的,因为它有许多的可配置选项。但是在绝大数数的例子中,只需要几行代码就可以配置。

依赖如下:

<dependency>
<groupId>org.mapdb</groupId>
<artifactId>mapdb</artifactId>
<version>VERSION</version>
</dependency>
1
2
3
4
5
Hello World
基于内存

DB db = DBMaker.memoryDB().make();
HTreeMap<String, String> map = db.hashMap("map")
.keySerializer(Serializer.STRING)
.valueSerializer(Serializer.STRING)
.createOrOpen();
//类似于JDK中Map的操作
map.put("name","bob");

基于文件

@Test
public void test02(){
DB db = DBMaker.fileDB("cache")
.make();
HTreeMap<String, byte[]> cache = db.hashMap("cache")
.keySerializer(Serializer.STRING)
.valueSerializer(Serializer.BYTE_ARRAY)
.createOrOpen();
Person person = new Person();
person.setId(1);
person.setName("Josiah");
person.setAddress("四川省成都市");
cache.put("name", object2Bytes(person));
byte[] bytes = cache.get("name");
Object o = bytes2Object(bytes);
if(o instanceof Person){
Person p = (Person)o;
System.out.println(p);
}
db.close();
}

快速提示
在64位操作系统上,把内存数据映射到文件的时性能会更好。
MapDB针对批量导入提供了 Pump。这个比Map.put()操作性能更好
事务虽然存在一些性能开销,但是如果没有事务当程序没有正常关闭时,存储就会损坏。
存储在MapDb上的(键和值)都应该是不变的。
MapDB有时候需要压缩。
DB 和 DBMaker
DbMaker的DB mode

fileDB将数据写入到磁盘中,并从磁盘中读取数据
heapDB,创建一个新的内存数据库,将数不经过序列化存储在堆内存上。这种模式读取速度非常的快,但是数据会影响到GC,这种方式和JDK自带的集合框架十分相似。
meroryDB,创建一个新的内存数据库。数据将会在JVM退出时候丢失。这种模式下将会把数据序列化到 byte[] 中,所以它们不会被GC所影响。PS: 这种模式数据被序列存储到1MB大小的的 byte[]中。从技术上而言这些数据依然是 on-heap,但是已经被不影响到GC了,因为这些数据对GC不可见。这种模式也是官方默认推荐使用的,因为它不需要添加额外的JVM配置。仅仅使用调整大堆内存 -Xmx10G JVM参数就足够了。
meroryDirectDB,这种模式下数据存在DirectByteBuffer中,而且是完全是 off-heap的。使用者应该通过JVM参数调整大的 direct memory。这种模式下允许用户降低 heap内存到非常小的大小
tempFileDB,基于一个临时的文件夹创建一个新的数据库。当数据库关闭以后文件将会被删除。
DB
事务。DB提供了很多的方法去处理一个事务的,比如说提供了:rommit(),rollback(),close()。一个DB 对象代表了单个事务

@Test
public void testTransactional(){
DB db = DBMaker.fileDB("cache")
.transactionEnable() //开启事务后需要手动commit()和rollback(),clos()
.make();
BTreeMap<String, String> treemap = db.treeMap("treemap", Serializer.STRING, Serializer.STRING)
.createOrOpen();
treemap.put("1","one");
treemap.put("2","two");
db.commit();
treemap.put("4","four");
db.rollback();
db.close();
}


数据结构
HtreeMap
简介
MapDb的HtreeMap提供了 HashMap和HashSet集合。HTreeMap不仅支持了Entry过期,同时还可以当成缓存使用,同时还能保证线程安全。

HtreeMap线程安全的实现使用了类似于 在JDK7中ConcurrentHashMap那样的工作原理:在并行写入的时候使用多个分段,每个分段都具有各自的读写锁。

HtreeMap采用的是分段的哈希树。不像其他的HashMap一样采用的是可变大小的哈希表,所以不会当数据增长之后出现rehash所有数据的情况。HtreeMap使用的是自动扩展索引树,所以它不需要resiz而且也占用更少的空间。

HtreeMap支持四种过期的方式:(过期的Entry将会自动被删除。每个分段都使用各自的FIFO过期队列)

maximal map size
maximal storage size
time-to-live since last modification
time-to-live last access
重要特性和参数
Expiration
HtreeMap 有如下几种情况数据将会过期:

一个数据在Map中存在的时间比设置的有效期要长。这个有效期可以是从 创建、后一次修改、后一次读取这三个方面。
Map中的元素数量超过了设置的大元素数量
Map 使用的磁盘或者内存空间大于了空间限制。
Shadrder Store
HtreeMap是分割为了多个分段,每个分段都是独立的,分段之间不会共享自己的状态。但是它们任然共享存储底层,这会影响并发负载下的性能,通过对每个段使用单独的存储,可以使段真正的独立。


@Test
public void testShardMap() {
HTreeMap<String, String> map = DBMaker.memoryShardedHashMap(8)
.keySerializer(Serializer.STRING)
.valueSerializer(Serializer.STRING)
.createOrOpen();
// DB 不存在了,所以直接关闭Map即可
map.close();
}

原理
HtreeMap 使用的是索引树而不是可变的Hash表。索引树类似于那种使用树层次结构的稀疏数组的结构。虽然他是稀疏的所以不用考虑rehash的过程,但是它不能超过初始化的数据量大小。

三个重要的参数:

concurrency,并发度。分段的数量。默认值是8,可以自定义修改,不过这个数量总数会四舍五入为接近的2的整数次方大小。
大的node size 。默认值是16,可以自定义修改,不过这个数量总数会四舍五入为接近的2的整数次方大小。大值是128
Levels 树的深度。默认值是4
大数据量的计算公式为:concurrency * node size ^ level count

BtreeMap
简介
MapDb的BtreeMap提供了类似于TreeMap和ThreeSet的功能。它基于的是无锁的并发 B-Linked-Tree。针对小的键它提供了良好的性能和可扩展性。

重要参数
serializers
序列化器。可以在使用现成的一些序列化器,这些序列化器存在于 Serializer接口的静态字段中(STRING,BYTE_ARRY_,BYTE,INTEGER)等等。为了灵活性,用户也可以自定义序列化器。
自定义序列化器 MySerializer

public class MySerializer<T> implements Serializer<T> {
@Override
public void serialize(@NotNull DataOutput2 dataOutput2, @NotNull T t) throws IOException {
ObjectOutputStream outputStream = new ObjectOutputStream(dataOutput2);
outputStream.writeObject(t);
outputStream.flush();
}

@SneakyThrows
@Override
public T deserialize(@NotNull DataInput2 dataInput2, int i) throws IOException {
DataInput2.DataInputToStream dataInputToStream = new DataInput2.DataInputToStream(dataInput2);
ObjectInputStream objectInputStream = new ObjectInputStream(dataInputToStream);
Object o = objectInputStream.readObject();
return (T)o;
}
}

@Test
public void test02(){
DB db = DBMaker.fileDB("cache")
.checksumHeaderBypass()
.make();
db.defaultSerializerRegisterClass(Person.class);

HTreeMap<String, Person> cache = db.hashMap("cache")
.keySerializer(Serializer.STRING)
.valueSerializer(new MySerializer<Person>())
.createOrOpen();
Person person = new Person();
person.setId(1);
person.setName("Josiah");
person.setAddress("USA");
cache.put("name", person);
Person name = cache.get("name");
System.out.println(name);
db.close();
}

MapDB数据结构和JDK Collection性能比较
环境:
Windows 10
JDK1.8
内存:8G
CPU:i5 8代
测试
JDK ConcurrentHashMap<Integer,String>
@Test
public void testConcurrentHashMap(){
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>(16);
Random random = new Random();
String now = DateUtil.now();
System.out.println("开始时间:"+now);
DateTime date = DateUtil.date();
for(int i=0;;i++){
//使用随机数可以增大Hash碰撞的几率,更加接近实际生产环境
map.put(random.nextInt(),random.nextInt()+"");
if (i%1000000 == 0){
System.out.println("====================================");
System.out.println(i);
System.out.println("------------------------------------");
System.out.println("耗时多少:"+DateUtil.between(DateUtil.date(),date, DateUnit.SECOND));
date = DateUtil.date();
}
}
}

可以看到ConcurrentHashMap当数据量到4500W左右时候,每次PUT 100W数据,耗时上升了一个指数级。猜测原因:GC垃圾回收造成了性能开销,其次ReHash数据拷贝的过程造成性能开销。


MapDb Meromry模式下的HTreeMap<Integer,String>
@Test
public void testMapDBHtreeMapInMemoryMode(){
DB db = DBMaker.memoryDB()
.make();
HTreeMap<Integer, String> cache = db.hashMap("cache", Serializer.INTEGER, Serializer.STRING)
.createOrOpen();
Random random = new Random();
String now = DateUtil.now();
System.out.println("开始时间:"+now);
DateTime date = DateUtil.date();
for(int i=0;;i++){
//使用随机数可以增大Hash碰撞的几率,更加接近实际生产环境
cache.put(random.nextInt(),random.nextInt()+"");
if (i%1000000 == 0){
System.out.println("====================================");
System.out.println(i);
System.out.println("------------------------------------");
System.out.println("耗时多少:"+DateUtil.between(DateUtil.date(),date, DateUnit.SECOND));
date = DateUtil.date();
}
}
}
1
当数据量到4500W后,性能明显下降,但是比



MapDb meroryDirectDB模式下的HTreeMap<Integer,String>
@Test
public void testMapDBHtreeMapInMemoryMode(){
DB db = DBMaker.memoryDirectDB()
.make();
HTreeMap<Integer, String> cache = db.hashMap("cache", Serializer.INTEGER, Serializer.STRING)
.createOrOpen();
Random random = new Random();
String now = DateUtil.now();
System.out.println("开始时间:"+now);
DateTime date = DateUtil.date();
for(int i=0;;i++){
//使用随机数可以增大Hash碰撞的几率,更加接近实际生产环境
cache.put(random.nextInt(),random.nextInt()+"");
if (i%1000000 == 0){
System.out.println("====================================");
System.out.println(i);
System.out.println("------------------------------------");
System.out.println("耗时多少:"+DateUtil.between(DateUtil.date(),date, DateUnit.SECOND));
date = DateUtil.date();
}
}
}

当数据量高达3800w的时候,平均每100W条数据Put操作时间为8秒钟。


总结
MapDB比较与Java Collection是的优点和缺点:

优点:
支持内存数据到文件的映射。
HtreepMap支持Expire的特性。
在大量数据的时候可以减少 JVM gc的压力。(基于堆外内存)
缺点:
读写性能不如Java Collection
当需要缓存的数据量在10GB左右,或者当缓存的数据占用了JVM 内存的 1/2的时候,可以考虑使用MapDb的堆外内存作为缓存工具使用。
————————————————
版权声明:本文为CSDN博主「SmileJosiah」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37024565/article/details/114282729

相关文章