LevelDB 简介
一、LevelDB入门
LevelDB是Google开源的持久化KV单机数据库,具有很高的随机写,顺序读/写性能,但是随机读的性能很一般,也就是说,LevelDB很适合应用在查询较少,而写很多的场景。LevelDB应用了LSM (Log Structured Merge) 策略,lsm_tree对索引变更进行延迟及批量处理,并通过一种类似于归并排序的方式高效地将更新迁移到磁盘,降低索引插入开销,关于LSM,本文在后面也会简单提及。
根据LevelDB官方网站的描述,LevelDB的特点和限制如下:
特点:
1、key和value都是任意长度的字节数组;
2、entry(即一条K-V记录)默认改并是按照key的字典顺序存储的,当然开发者也可以重载这个排序函数;
3、提供的基本 *** 作接口:Put()、Delete()、Get()、Batch();
4、支持批量 *** 作以原子 *** 作进行;
5、可以创建数据全景的snapshot(快照),并允许在快照中查找数据;
6、可以通过前向(或后向)迭代器遍历数据(迭代器会隐含的创建一个snapshot);
7、自动使用Snappy压缩数据;
8、可移植性;
限制:
1、非关系型数据模型(NoSQL),不支持sql语句,也不支持索引;
2、一次搏或只允许基歼伍一个进程访问一个特定的数据库;
3、没有内置的C/S架构,但开发者可以使用LevelDB库自己封装一个server
LevelDB本身只是一个lib库,在源码目录make编译即可,然后在我们的应用程序里面可以直接include leveldb/include/db.h头文件,该头文件有几个基本的数据库 *** 作接口,下面是一个测试例子:
#include <iostream>
#include <string>
#include <assert.h>
#include "leveldb/db.h"
using namespace std
int main(void)
{
leveldb::DB *db
leveldb::Options options
options.create_if_missing = true
// open
leveldb::Status status = leveldb::DB::Open(options,"/tmp/testdb", &db)
assert(status.ok())
string key = "name"
string value = "chenqi"
// write
status = db->Put(leveldb::WriteOptions(), key, value)
assert(status.ok())
// read
status = db->Get(leveldb::ReadOptions(), key, &value)
assert(status.ok())
cout<<value<<endl
// delete
status = db->Delete(leveldb::WriteOptions(), key)
assert(status.ok())
status = db->Get(leveldb::ReadOptions(),key, &value)
if(!status.ok()) {
cerr<<key<<""<<status.ToString()<<endl
} else {
cout<<key<<"==="<<value<<endl
}
// close
delete db
return 0
}
上面的例子演示了如何插入、获取、删除一条记录,编译代码:
g++ -o test test.cpp libleveldb.a -lpthread -Iinclude
执行./test后,会在/tmp下面生成一个目录testdb,里面包含若干文件:
------------------------------------------------------------
LevelDB是google开源的一个key-value存储引擎库,类似于开源的Lucene索引库一样。其他的软件开发者可以利用该库做二次开发,来满足定制需求。LevelDB采用日志式的写方式来提高写性能,但是牺牲了部分读性能。为了弥补牺牲了的读性能,一些人提议使用SSD作为存储介质。
对于本地化的Key-value存储引擎来说,简单的使用一般都分成三个基本的步骤:(1)打开一个数据库实例;(2)对这个数据库实例进行插入,修改和查询 *** 作;(3)最后在使用完成之后,关闭该数据库。下面将详细讨论该三个步骤:
一、打开一个数据库实例
一个leveldb数据库有一个对应一个文件系统目录的名字。该数据库的所有内容都存储在这个目录下。下面的代码描述了怎样打开一个数据库或者建立一个新的数据库。
#include <assert.h>
#include "leveldb/db.h"
leveldb::DB* db
leveldb::Options options
options.create_if_missing = true
leveldb::Status status = leveldb::DB::Open(options,"/tmp/testdb", &db)
assert(status.ok())
如果打开已存在数据库的时候,需要抛出错误。将以下代码插在leveldb::DB::Open方法前面:
options.error_if_exists = true
二、对数据库的简单读、写 *** 作
LevelDB提供了Put,Delete和Get三个方法对数据库进行修改和查询。例如,下面的代码片段描述了怎样将key1对应的value值,移到key2对应的值。
std::string value
leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value)
if(s.ok()) s = db->Put(leveldb::WriteOptions(), key2, value)
if(s.ok()) s = db->Delete(leveldb::WriteOptions(), key1)
三、关闭数据库
在对数据库进行了一系列的 *** 作之后,需要对数据库进行关闭。该 *** 作比较简单:
... open the db as described above...
... do something with db ...
delete db
上面对levelDB的简单使用做了基本的介绍,接下来就是如何自己写一个完成并且能运行的例子。
1、下载源码 git clone https://code.google.com/p/leveldb/
2、编译源码 cd leveldb &&make all
3、编写test.cpp
#include <assert.h>
#include <string.h>
#include <leveldb/db.h>
#include <iostream>
int main(){
leveldb::DB* db
leveldb::Options options
options.create_if_missing = true
leveldb::Status status = leveldb::DB::Open(options,"/tmp/testdb", &db)
assert(status.ok())
//write key1,value1
std::string key="key"
std::string value = "value"
status = db->Put(leveldb::WriteOptions(), key,value)
assert(status.ok())
status = db->Get(leveldb::ReadOptions(), key, &value)
assert(status.ok())
std::cout<<value<<std::endl
std::string key2 = "key2"
//move the value under key to key2
status = db->Put(leveldb::WriteOptions(),key2,value)
assert(status.ok())
status = db->Delete(leveldb::WriteOptions(), key)
assert(status.ok())
status = db->Get(leveldb::ReadOptions(),key2, &value)
assert(status.ok())
std::cout<<key2<<"==="<<value<<std::endl
status = db->Get(leveldb::ReadOptions(),key, &value)
if(!status.ok()) std::cerr<<key<<""<<status.ToString()<<std::endl
else std::cout<<key<<"==="<<value<<std::endl
delete db
return 0
}
4、编译链接 g++ -o test test.cpp ../leveldb/libleveldb.a -lpthread -I../leveldb/include
注意libleveldb.a 和leveldb include的路径。
5、运行结果./test:
value
key2===value
keyNotFound:
这一篇我们来解析leveldb的Compaction机制,要把这个讲清楚,需要回答下面的问题:
随着Put/Delete *** 作增多,sstable文件会越来越多,占用的磁盘空间也越来越大。删除无效的key( 被标记删除的key,被新版本覆盖的老版本的key ),然后重新生成sstable文件,可吵仔以有效减少sstable文件的数量,这个过程就是Compaction。
Compaction的入口函数是MaybeScheduleCompaction(),触发的时机:
leveldb是在后台线程执行Compact,目前只允许单线程,也就是说Compact只能串行执行。
入口函数是DBImpl::BackgroundCompaction(),流程如下:
leveldb通过VersionSet::PickCompaction()来选择要压缩的Layer和Table,怎么做到的呢?
原来,在每个Version生成的时候,会计算哪个Layer需要进行Compaction,方法是计算每个layer的compaction_score,选择得分最高的layer,这是由LogAndApply()函数完成的:
调用LogAndApply()生成Version的时机:肆游
选择Layer和Table的过程头绪比裂碰销较多,具体实施Compaction的过程相对比较好理解,用下面这张图表述Compaction的整个过程:
https://github.com/chrislusf/seaweedfs/releases
经典论文翻译导读之《Finding a needle in Haystack: Facebook’s photo storage》
http://www.importnew.com/3292.html
下面一张图总结下相互关系:
weed master 创建的是一个master服务器。
参数:
-defaultReplication string 备份策略(详细见 https://github.com/chrislusf/seaweedfs/wiki/Replication )
-ip string
-mdir string选项用于配置保存生搭碰成的序列文件id的文件夹
-port int (default 9333)
-volumeSizeLimitMB uint 自定义不能大于30000(default 30000)
-whiteList string 白名单,ip地址用逗号隔开
master服务器可以创建多个来实现故障转移主服务器,详细见 https://github.com/chrislusf/seaweedfs/wiki/Failover-Master-Server
参数:
-dir string 数据保存的路径,如果master的mdir没有指定会使用这个,如果filer的dir没有指定会新增并使用该目录下的filer目录
-ip string
-mserver string (default "localhost:9333")
-port
-dataCenter string
-rack string
-whiteList string
weed volume会创建一个 datanode ,可以指定所属的 datacenter rack和master ,会根据配置存储文件,默认一开始没有volume,当开始存储文件的时候才会创建一个volume,当这一个volume大小超过了volumeSizeLimitMB 就会新增一个volume,当volume个数超过了max则该datanode就不能新增数据了。那就需要在通过weed volume命令新增一个datanode。
weed filer
参数
-collection string 所有数敬枝唤据将存储在此集合中
-dataCenter string更倾向于在这个数据中心写入卷
-dirListLimit int limit sub dir listing size (default 100000)
-ip string
-master string
-port int(default 8888)
更详细的说明请见: https://mp.csdn.net/mdeditor/85049078#
或者访问官网wiki : https://github.com/chrislusf/seaweedfs/wiki
出现如下提示说明启动成功
执行下面的命令:
出现DataCenters是null的原因是没有执行weed volume创建DataCenter。
" 这里说一下抽象概念":
我们抽象的认为我们的图片服务器,一个master需要两个datacenter叫imgdatacenter1,imgdatacenter2;imgdatacenter1需要两个rack叫imgrack1,imgrack2;然后imgrack1需要两个亮凯datanode1,datanode2;
创建datanode时 ,统一设置每个datanode包含10个volume即可。当datanode里面的volume满了以后再创建 新的datanode即可,方便扩展,并且不同datanode可以在不同磁盘位置;
(imgdatacenter1的imgrack2和imgdatacenter2按照上面的方式创建即可,见附录 )
目前我们只是用imgdatacenter1->imgrack1->datanode1中的datanode1 :
创建datanode1的时候 master命令行会打印,提示leader新增child imgdatacenter1成功;imgdatacenter1新增child imgrack1成功;imgdatacenter1,imgrack1新增child 9991成功;volume server在9991端口。
此时再执行查看master状态的命名;
DataCenters Racks DataNodes都存在了;
但是名为localhost:9991的datanode中的volumes为0,明明我们设置了10啊;
因为没有上传文件之前不会创建volume,volume会在上传文件的时候根据实际情况创建。
这里注意下layouts,现在是null,当上传文件的时候会出现一个名为""的collection,里面的writables就是volume 的id数组,如果你自定义了collection,name你自定义的collection也会出现在这里,并且所有collection的volume个数之和小于等于我们设置的10;
collection删除后再新增,里面的volume的id会一直递增,不会使用原先删除的volume id。
此时我们可以上传文件了。
上传文件有多种方式,这里我们先说明两个
1.先向master申请文件id,然后用文件id向datanode上传文件:
修改只需要在fid上传别的文件即可
上传成功后访问,只需要拼接url即可: localhost:9991/1,015b7256d5
2.直接向master上传文件,master自己生成文件id,并向datanode上传文件,然后返回结果:
此时你再查看状态发现volume就创建了10个。
此时查看datanode的状态:
因为我1.jpg上传了两次,而且第一次在id为1的volume中,第二次在id为3的volume中,所有你会发现这两个id的volume的FileCount都为1
并发的上传文件:
一个卷服务器一次只写一个卷。如果需要增加并发性,可以预先分配大量卷。下面是例子。您还可以组合所有不同的选项。状态详情见附录
删除文件:
文件的删除不是实时的,因为weed默认有个阈值,超过这个阈值才会清理没使用的空间,如果你一时间内删除了大量文件,想立马生效,可以用这种方式清理未使用的空间:
此时文件通过url的增删改查都可以了,下面把服务映射成文件系统来 *** 作,可以方便的 *** 作本地的大量文件
filer是将文件以文件目录的方式上传到图片服务,然后你根据文件目录的方式访问
默认使用leveldb保存映射关系,打开filer.toml文件修改保存映射文件的文件夹为ftmp(自定义)
然后启动filer服务
master打印如下信息说明成功
自身的log
直接往weed filer中拷贝目录或者文件(-include是文件模式通配符前使用??)
weed filer.copy nginxdir http://localhost:8888/aaa 把nginxdir拷贝到aaa目录下
weed filer.copy -include *.go . http://localhost:8888/github/
详细请见 https://github.com/chrislusf/seaweedfs/wiki/Filer-Server-API
然而我们时长会有这样的需求,批量把照片保存成图片文件备份起来,而不是备份一个bat文件;
或者我们想以目录结构的方式通过本地访问,而不是通过web访问?
此时最简单有效的方法就是把filer服务器mount到本地,然后直接 *** 作文件系统:
weed mount 像访问本地目录一样访问文件系统,前提是开启了 master volume filer
(它使用bazil.org/FUSE,它允许在Linux和OSX上编写FUSE文件系统。在OSX上,它需要OSXFUSE)
可以指定 collection
关闭挂在需要关闭mount并且手动umont ~/mdir目录,如果一般用户失败请使用root用户
一个场景:
如果本地已经有很多文件了,如何快速的迁移到seaweedfs中呢?
1.启动master、volume、filer
2.启动mount
3.手动拷贝到mount目录中(单线程的)
4.使用weed filer.copy file_or_dir1 [file_or_dir2 file_or_dir3] http://localhost:8888/path/to/a/folder/ (多线程且绕过fuse层)
aws s3 兼容
Each bucket is stored in one collection, and mapped to folder /buckets/<bucket_name>by default
可以通过删除collection来快速删除一个bucket
异步复制
应该有两个SeawideFileSystems运行,可能跨数据中心运行。每个服务器都应该有自己的文件服务器、主服务器和卷服务器。
这是我执行了(curl " http://localhost:9333/vol/grow?collection=imgcoll&count=3 " )的结果
详细文档请见官方wiki
https://github.com/chrislusf/seaweedfs/wiki/Getting-Started
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)