全球矢量、影像数据(亿个小文件)入库经验总结

一、业务场景

客户有一批数据,里面大约有1万7千个mbtiles文件(可以理解为紧凑型数据),本质就是sqlitedb数据库。现在需要去访问mbtiles数据库中的pbf瓦片(二进制),以流的形式返回给前端进行渲染。

二、实现方案

场景如上,可得出三种解决方案

1、直接访问mbtiles,根据一定的规则找到对应的pbf瓦片并返回给前端

优点:不需要将内部的数据还原,直接操作即可

缺点:

  • sqlitedb这种嵌入型数据库存在一定的并发问题。
  • 更换服务地址需要迁移数据,数据量大迁移慢

因为客户对并发量有一定的要求 于是果断放弃方案1

2、将mbtiles中的文件还原并上传到分布式存储中

基于该方案,我试过操作两种方式,但都存在不同程度的问题

  1. 先将文件写到服务器磁盘上并将文件上传到分布式存储上

    写到磁盘上很快,但是写着写着发现 服务器的inode数满了!
    原来这个mbtiles中存在大量的小文件,全部还原后预计有两亿个,使用 df -i 命令查看服务器的inode数发现只有一亿个左右,没办法再继续写入了。

    inode译成中文就是索引节点,每个存储设备(例如硬盘)或存储设备的分区被格式化为文件系统后,应该有两部份,一部份是inode,另一部份是Block,Block是用来存储数据用的。而inode呢,就是用来存储这些数据的信息,这些信息包括文件大小、属主、归属的用户组、读写权限等。inode为每个文件进行信息索引,所以就有了inode的数值。操作系统根据指令,能通过inode值最快的找到相对应的文件。
    这种情况的原因通常是:尽管那个分区的磁盘占用率未满,但是inode已经用完,应该是该磁盘的某些目录下存在大量的小文件导致。尽管小文件占用的磁盘空间并不大,但是数量太多,inode用尽。

  2. 将文件直接写到分布式存储的挂载目录上

    竟然直接写到磁盘上不行,那试试写到挂载目录上呢,我们选型的分布式存储是 seaweed ,df -i 查看seaweed fuse挂载发现inode数几乎为无限。那就试试吧,但是发现速度非常的慢,可能是seaweed上传的时候还需要对数据进行备份,且小文件让seaweed的速度提升不上来。加上fuse每写入一个文件就要向temp文件夹中写入一个日志,一下子日志就满了,导致seaweed无法再写入文件了。

3、将数据写到mongodb中。

竟然写到磁盘上和挂载上都不行,那能不能写到数据库中呢?答案是当然可以。

那为什么选mongodb呢?分析一下业务场景:

1)读>写:全球矢量基本只会入库一次,而读则是每次加载场景都会产生大量的并发
2)支持二进制类型的数据存储
3)不存在复杂的业务逻辑,只做简单的查询

而MongoDB具有良好的写入性能和读取性能,可以处理大量的数据和高并发请求。这使得MongoDB适合处理需要高速数据访问和响应的应用程序。同时Mongodb也能很好的支持二进制类型的数据,虽然在复杂业务上语句不好写,但是恰恰我们这次只用做简单的查询。

于是就选择了mongodb:

得益于瓦片数据的文件目录的固定规则,我们可以简单的将z层级作为集合,文件夹的名称作为列x,文件的名称作为字段y,而文件的二进制则是单独一个字段 data。

存在数据库中就不用担心inode节点的问题啦,测试发现速度起码提升10倍以上!不过由于文件过多 再快也是需要时间的。

4、选定了方案后,还做了以下的优化

  • 还原文件的脚本使用了多线程去处理,向数据库中提交数据的时候使用批量提交 减少与数据库之间的连接消耗
  • 将一万七千个Mbtiles 分组 1000个文件一组 同时启动多个脚本进行写入。
  • 调整mongodb的内存,让他足以支持并发写入。
  • 12-16级的文件较多,其实可以再细分,比如不单按z来划分集合,还可以按 z 和 x % 100 将数据更加分散一点,减少一个集合中的数据量。

解决全球矢量的数据入库问题后,正打算以同样的方式将全球影像一起入库。然而发现影像的大小是矢量的20倍(未解压时 70T)

查看磁盘,发现单节点mongodb所在的磁盘存储完全不足,怎么办呢?

查看mongodb官网文档,mongodb有一个拓展机制,叫做分片集群( Sharding — MongoDB Manual)

当时我采取的部署分布 参考如下:

节点 \ 组件 mongos 路由组件 configServer 配置组件 shard 分片集群1 shard 分片集群2 shard 分片集群3
10.0.2.30 27017 27018 27019 主 27021
10.0.2.31 27017 27018 27019 27020 主
10.0.2.34 27017 27018 27020 27021 主

通过合理的设置shard key 将数据分布在不同的服务器上,以达到横向拓展的目的。

同时使用nginx对mongos做负载均衡以提高连接数的上限,方便脚本入库。

5、将文件写入到内存再读取,代替seek

然而全球影像和全球矢量的数据的体量完全不是同一个等级,且由于脚本原本使用seek对pyr文件进行读取,在大文件随机寻址时非常的慢。后面采用mmap代替seek优化脚本,将文件映射到内存中访问,以提高读取性能。

#优化前
 def getData(filename,index):
     file_path = fileMap.get(filename)
     fp = open(file_path, "rb")
     fp.seek(28 + index * 12)   # 将文件指针移动到第k个字节
     buf = fp.read(12)
     arr = struct.unpack("qi", buf)
     offset = arr[0]
     size = arr[1]
      if offset < 0 or size <= 0 or offset + size > file_length:
          return None
     fp.seek(offset)  # 将文件指针移动到指定偏移量
     data = fp.read(size)
     fp.close()
     return data if data != b'' else None

#优化后
def getData(fp, index):
    with mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) as mm:
        start_offset = 28 + index * 12
        buf = mm[start_offset:start_offset + 12]
        arr = struct.unpack("qi", buf)

        offset = arr[0]
        size = arr[1]
        # Check for invalid offset or size
        if offset < 0 or size <= 0 or offset + size > mm.size():
            return None
        data = mm[offset:offset + size]
        return data if data != b'' else None

完毕

你可能感兴趣的:(mongodb,python,postgresql,java)