topology 整个模块最核心的数据结构是三个:git
topology 是树状结构,DataNode 是树的叶子节点, DataCenter 和 Rack 是树的非叶子节点, DataCenter 是 Rack 的父母节点。 以下图github
DataCenter | | ------------------ | | | | Rack Rack | | ------------ | | | | DataNode DataNode
也就是在 MasterServer 维护的拓扑结构里, 是把 VolumeServer 的相关信息存储在 DataNode 里, 因此在代码里面能够看到以下:json
dc := t.GetOrCreateDataCenter(dcName) rack := dc.GetOrCreateRack(rackName) dn := rack.FindDataNode(*joinMessage.Ip, int(*joinMessage.Port))
每次查找对应的DataNode,都须要从 DataCenter -> Rack -> DataNode 依次找下去。api
curl -F "file=@/tmp/test.pdf" "127.0.0.1:9333/submit" {"fid":"1,01f96b93eb","fileName":"test.pdf","fileUrl":"localhost:8081/1,01f96b93eb","size":548840}
其中 "fid":"1,01f96b93eb"
就是 Fid,Fid 由三个部分组成 【VolumeId, NeedleId, Cookie】 组成。缓存
其中 VolumeId 是由 MasterServer 分配给 VolumeServer, 每一个 VolumeServer 都维护个 n 个 Volume , 每一个 Volume 都有一个专属 VolumeId,以后会细说。 Needle 属于 Volume 里面的一个单元,后续说。安全
type Volume struct { Id VolumeId dir string Collection string dataFile *os.File nm NeedleMapper readOnly bool SuperBlock accessLock sync.Mutex lastModifiedTime uint64 //unix time in seconds }
"fid":"3,01f9896771"
里面逗号前面的 3 就是 VolumeId 。以上最关键的两个点就是 SuperBlock 和 NeedleMapper , 这二者在文件中布局以下:服务器
+-------------+ |SuperBlock | +-------------+ |Needle1 | +-------------+ |Needle2 | +-------------+ |Needle3 | +-------------+ |Needle ... | +-------------+
Volume = 1 SuperBlock + n Needle
/* * Super block currently has 8 bytes allocated for each volume. * Byte 0: version, 1 or 2 * Byte 1: Replica Placement strategy, 000, 001, 002, 010, etc * Byte 2 and byte 3: Time to live. See TTL for definition * Rest bytes: Reserved */ type SuperBlock struct { version Version ReplicaPlacement *ReplicaPlacement Ttl *TTL }
SuperBlock 内维护的数据基本上就是该 Volume 的元数据。数据结构
【TTL】app
定时删除功能,这个感受很酷炫,可是在Seaweedfs里面的实现原理很简单, 按 Volume 来进行分块,当每次用户上传一个自带TTL的文件(须要定时删除的文件)时, 会把这个文件存储在合适的 Volume 里面(如何选出合适的 Volume 以后再说), 存储的时候每一个文件会带有 TTL 这个属性, 当读取出来以后发现该文件已通过期(超时时间到),则会返回一个 Not Found 结果, 而每一个 Volume 维护一个最大超时时间,当这个时间抵达时,说明整个 Volume 全部的文件都超时了, 而后 VolumeServer 通知 MasterServer 这个 Volume 已经被标识为 Dead 状态, 意味着 MasterServer 不会再为这个 Volume 分配新的 Fid。 而后再通过一段合适的时间后由 VolumeServer 将这个 Volume 从磁盘上安全的删除掉。 详细请看在 Seaweedfs 自带的文档 ttl ,dom
/* * A Needle means a uploaded and stored file. * Needle file size is limited to 4GB for now. */ type Needle struct { Cookie uint32 `comment:"random number to mitigate brute force lookups"` Id uint64 `comment:"needle id"` Size uint32 `comment:"sum of DataSize,Data,NameSize,Name,MimeSize,Mime"` Data []byte `comment:"The actual file data"` DataSize uint32 `comment:"Data size"` //version2 Flags byte `comment:"boolean flags"` //version2 NameSize uint8 //version2 Name []byte `comment:"maximum 256 characters"` //version2 MimeSize uint8 //version2 Mime []byte `comment:"maximum 256 characters"` //version2 LastModified uint64 //only store LastModifiedBytesLength bytes, which is 5 bytes to disk Ttl *TTL Checksum CRC `comment:"CRC32 to check integrity"` Padding []byte `comment:"Aligned to 8 bytes"` }
Needle 结构体里面的 Cookie 和 Id 就是上文提过的 Fid 里面的 Cookie 和 NeedleId, 其余就是一些存储相关的变量,没什么奇淫巧计。就是简单的存储结构而已。
Replication 和 Topology 严重相关, 在配置文件中能够配置多种备份模式,详见 seaweedfs-wiki 。
+-----+---------------------------------------------------------------------------+ |001 |replicate once on the same rack | +-----+---------------------------------------------------------------------------+ |010 |replicate once on a different rack in the same data center | +-----+---------------------------------------------------------------------------+ |100 |replicate once on a different data center | +-----+---------------------------------------------------------------------------+ |200 |replicate twice on two other different data center | +-----+---------------------------------------------------------------------------+
好比在 001 模式,即在同一个 rack 中的不一样 DataNode 中备份一份。 假设在 rack1 中含有 DataNode1, DataNode2, DataNode3 三个数据节点中【随机】选出两个数据节点, 好比选出 DataNode1, DataNode2 而后同时写入这两个数据节点。 假设 rack1 只有一个数据节点的时候,而备份模式是 001 模式, 则没法正常备份,服务会报错。
注意到,选择备份数据节点的方法是【随机】,因此就会出现从三个数据节点中随机选择两个的状况下,
curl -v -F "file=@/tmp/test.json" localhost:8081/5,1ce2111f1
topo.NextVolumeId 负责生成 VolumeId , 负责在 VolumeGrowth 里的时候分配 Volume 的时候, 生成一个全局惟一的新 VolumeId, 在 Weed-fs 中,是支持 多 MasterServer 集群的。 当有多个 MasterServer,生成一个全局惟一的新 VolumeId 是很重要, 在 Weed-fs 中是经过 goraft 来实现的。
【强一致性】
Seaweedfs 的备份实现是强一致性的。 当一个 VolumeServer 接受到上传文件的 POST 请求时, 将该文件做为一个 Needle 写入本地 Volume 以后, 会根据该文件所分配的 VolumeId 判断是否须要备份, 若是须要备份,则进行备份(须要请求另外其它的 VolumeServer 服务器)。 过程详见 ReplicatedWrite
(topology/store_replicate.go)。 当备份完毕后,再对该 POST 请求进行答复。 因此用户每次上传图片时,当收到了答复以后, 则能够认为此备份已完成。这个和最终一致性不一样,属于强一致性。
上述实现强一致性的过程当中, 有个必要条件就是【 VolumeServer 须要知道往其它那些 VolumeServer 备份】。 在 Seaweedfs 的实现中是借助 MasterServer 来实现, 由于备份的基本单位是 Volume, 在 MasterServer 中,对每一个 VolumeId 都维护对应的备份机器列表。 能够经过以下示例命令查看:
curl "localhost:9333/dir/lookup?volumeId=4&pretty=y" { "volumeId": "4", "locations": [ { "url": "127.0.0.1:8081", "publicUrl": "localhost:8081" }, { "url": "127.0.0.1:8080", "publicUrl": "localhost:8080" } ] }
如上示例中能够看出,对应的 volumeId=4 的 Volume, 能够看出对应的备份机器列表有两台,分别是 "127.0.0.1:8081" 和 "127.0.0.1:8080" 。
实际上对于每台 VolumeServer 查找其它备份机器的时候, 也是经过如上 HTTP api 向 MasterServer 询问。 只不过不是每次都询问,由于只要询问过了以后就会缓存下来,只有在缓存里面找不到才询问。
【Collection】
示例以下:
启动 MasterServer
weed master
启动 VolumeServer
weed volume -dir="/tmp/data1" -max=5 -mserver="localhost:9333" -port=8080
申请Fid
curl "http://127.0.0.1:9333/dir/assign?collection=pictures" {"fid":"4,01d50c6fbf","url":"127.0.0.1:8080","publicUrl":"localhost:8080","count":1}
curl "http://127.0.0.1:9333/dir/assign?collection=mp3" {"error":"No free volumes left!"}
curl "http://127.0.0.1:9333/dir/assign?collection=pictures" {"fid":"5,0147ed0fb7","url":"127.0.0.1:8080","publicUrl":"localhost:8080","count":1}
申请Fid的示例解释:
/dir/assign
的时候, 会分配 Volume,由于 weed volume
的参数 -max=5
因此一次性分配 5 个 Volume ,而且这 5 个 Volume 的 Collection 属性都是 pictures, 甚至能够看到在 ls /tmp/data1
的结果以下:/tmp/data1 pictures_1.dat pictures_1.idx pictures_2.dat pictures_2.idx pictures_3.dat pictures_3.idx pictures_4.dat pictures_4.idx pictures_5.dat pictures_5.idx
能够看出每一个卷的文件名以 Collection 来命名。
2. 由于已有的 5 个 Volume 的 Collection 属性都是 pictures, 因此此时若是须要 /dir/assign
一个非 pictures Collection 的 Fid 时失败,
3. 当申请一个属于 pictures Collection 的 Fid 成功。
也就是在每次申请 Fid 时,会针对 Collection 进行检查,来保证存入 Volume 的每一个 Needle 所属的 Collection 一致。 在实际应用中能够经过 Collection 来类别的分片。
【Volume 的大小限制】
在每次 VolumeServer 向 MasterServer 发送心跳信息的时候, 会在 storage.VolumeInfo.Size 里面注明当前 Volume 的大小信息(Size)。 因此能够以此来限制 Volume 的大小。 以下函数:
func (vl *VolumeLayout) isWritable(v *storage.VolumeInfo) bool { return uint64(v.Size) < vl.volumeSizeLimit && v.Version == storage.CurrentVersion && !v.ReadOnly }
当 VolumeInfo.Size 大于 VolumeLayout.volumeSizeLimit 时, 则将该 Volume 标记为不可写。 而 VolumeLayout.volumeSizeLimit 的值能够在启动 MasterServer 的时候配置。 由 weed help master
可知:
-volumeSizeLimitMB=30000: Master stops directing writes to oversized volumes.
每一个 Volume 的最大 Size 默认是 30G , 而每一个 VolumeServer 能够配置 n 个 Volume,根据所在机器不一样的硬盘大小配置不一样的 n . 由 weed help volume
可知:
-max="7": maximum numbers of volumes, count[,count]...
每一个 VolumeServer 默认的 Volume 大小是 7 。
因此默认状况下,当一个 VolumeServer 使用的磁盘超过 7 * 30G = 210G 以后, 该 VolumeServer 属于只读状态, MasterServer 不会再分配新的 Fid 给它。
可是其实这里会有漏洞,若是此时不经过请求 MasterServer 获取 Fid, 而是直接本身构造 Fid 向 VolumeServer POST 文件的话, VolumeServer 仍是会一直接受上传的文件, 直到大小超过在storage/needle.go
里面写死的一个常量:
MaxPossibleVolumeSize = 4 * 1024 * 1024 * 1024 * 8
其实在 VolumeServer 里面也有维护一个变量叫 volumeSizeLimit ,
type Store struct { ... volumeSizeLimit uint64 //read from the master ... }
此变量的值是从 MasterServer 获取的, 当每次 VolumeServer 写入 Needle 到 Volume 的时候, 都会检查 Volume Size 是否超过 volumeSizeLimit , 当超过的时候会打错误日志, 可是不会中止写入,也就是不会拒绝上传的文件。 有且只有 当大小超过 MaxPossibleVolumeSize 的时候才会拒绝写入磁盘。
【扩容】
对于 Seaweedfs 来讲,扩容很是简单,
启动 MasterServer:
./weed master -mdir="/tmp/weed_master_tmp"
启动 VolumeServer1:
weed volume -dir="/tmp/data1" -max=5 -mserver="localhost:9333" -port=8080
当 VolumeServer1 由于抵达 Volume 大小上限而没法继续接受上传数据时, 此时继续 submit 上传数据 MasterServer 则会返回错误(由于在 MasterServer 内已经将 VolumeServer1 标记为不可写)。
curl -F "file=@/tmp/test.pdf" "127.0.0.1:9333/submit" {"error":"No free volumes left!"}
此时直接再启动 VolumeServer2 便可
weed volume -dir="/tmp/data2" -max=5 -mserver="localhost:9333" -port=8081
此时 VolumeServer2 启动以后会自动向 MasterServer 的 Topology 结构注册新的 DataNode , 当 MasterServer 接受到新的 submit 请求的时候,会将该上传文件写入 VolumeServer2 (由于此时 VolumeServer1 已经不可写)。
也就是说若是当线上出现容量问题的时候,扩容只须要加机器便可,简单有效。