java rmi 负载均衡_Java RMI 实现一个简单的GFS(谷歌文件系统)——演示与实现篇...

本文主要是使用Java RMI 实现一个简单的GFS(谷歌文件系统,google file system),这里提供演示运行视频、系统实现以及源代码相关。

大年初二,走亲访友

祝大家新年快乐!

ʰᵅᵖᵖʸ ⁿeᵚ ʸᵉᵅʳ

家人闲坐 灯火可亲

辞旧迎新 新年可期

系统整体介绍、背景以及设计信息:

演示运行视频

1. 系统组织结构

java rmi 负载均衡_Java RMI 实现一个简单的GFS(谷歌文件系统)——演示与实现篇..._第1张图片

如图所示,整个MyGFS分布式文件系统由SPI、Common API,Master,ChunkServer和Client五个模块组成:

SPI:定义Master与ChunkServer需要实现的接口,并实现存放Chunk及其信息的抽象类。MasterApi与ChunkServerApi均继承自Remote接口,标识着这是一个远程接口。

Master:实现远程接口实现类MasterEngine,继承自UnicastRemoteObject类并实现MasterApi接口,负责与Client的通信与对ChunkServer的管理。

ChunkServer:实现远程接口实现类ChunkServerEngine,继承自UnicastRemoteObject类并实现ChunkServerApi接口,接收Master的调度并负责对Chunk的管理。

Client:使用分布式文件系统的本地端,通过与Master直接通信来间接地对文件系统进行操作。

Common:该模块负责实现工具类与配置文件,例如生成UUID,将文件读入内存等操作。

其具体的三方通讯流程如下图所示:

java rmi 负载均衡_Java RMI 实现一个简单的GFS(谷歌文件系统)——演示与实现篇..._第2张图片

2. Master模块

2.1 心跳机制

​使用Java RMI方式,在Master端检测每个ChunkServer是否在线。具体操作如下:

通过RMI方式来检测Chunk服务器的心跳,直接以try-catch方式判断。若服务器宕机则加入failedChunkServerList中。

检查正常ChunkServer上的所有Chunk的Hash值,若不一致则加入到Chunk失败列表中。

最后进行错误处理。

public synchronized void heartbeatScan() {

System.out.println("heartbeat checking...");

// 错误Chunk列表

Map> failedChunkMap = new LinkedHashMap<>();

// 错误Server列表

List failedChunkServerList = new ArrayList<>();

ChunkServerApi chunkServerApi;

Map hashMap;

int index = 0;

for(String chunkServer : chunkServerList) {

// 使用RMI检测心跳

try{

chunkServerApi = (ChunkServerApi) Naming.lookup("rmi://" + chunkServer + "/chunkServer");

// 获取Hash,用来检测Chunk错误

hashMap = chunkServerApi.getHashMap();

} catch (Exception e) {

// 服务器宕机

System.out.println("ChunkServer: " + chunkServer + " is down!");

failedChunkServerList.add(chunkServer);

}

try {

List failedList = new ArrayList<>();

for (ChunkInfo chunkInfo : serverInfoMap.get(chunkServer)) {

String hash = hashMap.get(chunkInfo.getChunk().getChunkId());

if (hash == null || !hash.equals(chunkInfo.getHash())) {

System.out.println("chunk:" + chunkInfo.getChunk().getChunkFileName() + " ERROR!");

chunkInfo.removeReplicaServerName(chunkServer);

int idx = nameNodeList.indexOf(chunkInfo.getNameNode());

nameNodeList.get(idx).setChunkInfo(chunkInfo, chunkInfo.getSeq());

serverInfoMap.get(chunkServer).set(index, chunkInfo);

failedList.add(chunkInfo);

}

index++;

}

failedChunkMap.put(chunkServer, failedList);

}catch (Exception e) {

System.out.println("检测chunk失败...");

}

}//for

// 错误处理

handleFaults(failedChunkMap, failedChunkServerList);

System.out.println("heartbeat check end...");

}

2.2 故障恢复和容错机制

​若ChunkServer掉线,则需分配新的服务器负载均衡,并将取出该ChunkServer上对应的Chunk文件,对其进行复制。

System.out.println("正在处理宕机的服务器:" + serverName + "...");

// 当宕机服务器没有Chunk时,直接去除

if(serverInfoMap.get(serverName).size() == 0) {

// 去除此服务器

chunkServerList.remove(serverName);

System.out.println("处理宕机服务器成功");

}

for(ChunkInfo chunkInfo : serverInfoMap.get(serverName)) {

System.out.println("备份failed chunkServer" + serverName + "中的Chunk "

+ chunkInfo.getChunk().getChunkFileName());

try {

chunkServerList.remove(serverName);

chunkInfo.removeReplicaServerName(serverName);

// 服务器节点分配

allocateNode(chunkInfo, chunkInfo.getFirstReplicaServerName());

// 处理NameNode

int idx = nameNodeList.indexOf(chunkInfo.getNameNode());

nameNodeList.get(idx).setChunkInfo(chunkInfo, chunkInfo.getSeq());

if(chunkInfo.getFirstReplicaServerName() == null) {

continue;

}

chunkServerApi = (ChunkServerApi) Naming.lookup(

"rmi://" + chunkInfo.getFirstReplicaServerName() + "/chunkServer");

chunkServerApi.backupChunk(chunkInfo.getChunk(), chunkInfo.getLastReplicaServerName());

System.out.println("处理宕机服务器成功");

}catch (Exception e) {

System.out.println("处理宕机服务器失败!");

e.printStackTrace();

}

}

​若该ChunkServer上的Chunk文件的Hash数据与Master上不一致则使用该Chunk文件的副本对其进行替换。

// chunk failed! 本地文件恢复

for(Map.Entry> failedChunk : failedChunkMap.entrySet()) {

String serverName = failedChunk.getKey();

List chunkInfos = failedChunk.getValue();

for(ChunkInfo chunkInfo : chunkInfos) {

System.out.println("从服务器" + serverName + "上正在恢复错误的Chunk:" + chunkInfo.getChunk().getChunkFileName());

try {

if(chunkInfo.getFirstReplicaServerName() == null ||

chunkInfo.getFirstReplicaServerName().equals(serverName)){

System.out.println("没有备份,恢复失败!");

continue;

}

chunkInfo.setLastReplicaServerName(serverName);

int idx = nameNodeList.indexOf(chunkInfo.getNameNode());

nameNodeList.get(idx).setChunkInfo(chunkInfo, chunkInfo.getSeq());

chunkServerApi = (ChunkServerApi) Naming.lookup(

"rmi://" + chunkInfo.getFirstReplicaServerName() + "/chunkServer");

chunkServerApi.backupChunk(chunkInfo.getChunk(), serverName);

System.out.println(chunkInfo.getChunk().getChunkFileName() + "恢复成功!");

}catch (Exception e) {

System.out.println("恢复失败!");

e.printStackTrace();

}

}

}

​运行截图如图所示:

java rmi 负载均衡_Java RMI 实现一个简单的GFS(谷歌文件系统)——演示与实现篇..._第3张图片

3. ChunkServer模块

3.1 内存命中机制

public class ChunkServerMemory {

private final LinkedList memoryList;

private final int maxContain;

public ChunkServerMemory(int maxContain) {

this.memoryList = new LinkedList<>();

this.maxContain = maxContain;

}

public void push(Chunk chunk,byte[] data) {

if(memoryList.size()>maxContain){

memoryList.removeLast();

}

memoryList.push(new ChunkMemory(chunk,data));

}

public ChunkMemory search(Chunk chunk){

ChunkMemory res=null;

for (int i = 0; i < memoryList.size(); i++) {

if(memoryList.get(i).isMatch(chunk)){

res=memoryList.get(i);

moveToHead(i);

System.out.println(chunk.getChunkFileName()+"内存命中");

}

}

return res;

}

private void moveToHead(int i){

ChunkMemory tmp=memoryList.get(i);

memoryList.remove(i);

memoryList.push(tmp);

}

public void remove(long chunkId){

for (int i = 0; i < memoryList.size(); i++) {

if(memoryList.get(i).isMatch(chunkId)){

memoryList.remove(i);

return;

}

}

}

}

3.2 状态维护

​一分钟更新一次本地Chunk的Hash值。

try{

Thread.sleep(60000);

System.out.println("开始检查Chunk信息");

for(Long chunkId : chunkIdList) {

String md5Str = SecurityUtil.getMd5(filePath + getChunkName(chunkId));

if(md5Str == null) {

md5Str = "check error: no file!";

}

chunkHash.put(chunkId, md5Str);

}

System.out.println("检查Chunk信息结束");

} catch (Exception e) {

e.printStackTrace();

break;

}

3.3副本管理

​GFS默认Chunk主副本三个,但为了实际演示方便,这里设置为主副本各一个,下图为windows服务器和Linux服务器上的存储。

java rmi 负载均衡_Java RMI 实现一个简单的GFS(谷歌文件系统)——演示与实现篇..._第4张图片

4. Client模块

4.1 上传

​在Client端上传文件时,会先将文件相关信息添加到Master中,同时Master会分配服务器到各个Chunk文件,然后Client通过分配的信息向指定的ChunkServer进行传送数据流。

public void upLoadFile(String fileAddr) {

System.out.println("文件正在上传...");

try{

int length, seq = 0;

byte[] buffer = new byte[CHUNK_SIZE];

File file = new File(fileAddr);

// 向Master添加该Name结点

masterApi.addNameNode(file.getName());

InputStream input = new FileInputStream(file);

input.skip(0);

while ((length = input.read(buffer, 0, CHUNK_SIZE)) > 0) {

byte[] upLoadBytes = new byte[length];

System.arraycopy(buffer, 0, upLoadBytes, 0, length);

String hash = SecurityUtil.getMd5(upLoadBytes);

uploadChunk(file.getName(), seq, length, upLoadBytes, hash);

seq++;

}

input.close();

System.out.println("文件已上传!");

} catch (Exception e) {

System.out.println("文件上传失败");

System.out.println(e.getLocalizedMessage());

}

}

​演示效果如图所示,分别为Client端和ChunkServer端的情况。

java rmi 负载均衡_Java RMI 实现一个简单的GFS(谷歌文件系统)——演示与实现篇..._第5张图片

4.2 下载

​用户在Client端下载文件时,会先向Master请求所下载文件的信息,然后通过Master返回的Chunk所在ChunkServer信息进行数据请求获取。

public String downloadFile(String fileName) throws Exception {

System.out.println("文件正在下载...");

String fileAddr = prefixPath + "new_" + fileName;

File localFile = new File(fileAddr);

OutputStream output = new FileOutputStream(localFile);

List chunkInfoList = masterApi.getChunkInfos(fileName);

for(ChunkInfo chunkInfo : chunkInfoList) {

output.write(downloadChunk(chunkInfo.getChunk(), chunkInfo.getFirstReplicaServerName()));

}

output.close();

return fileAddr;

}

4.3 追加

​每一个Chunk默认最大为64Mb,追加操作需要对最后一个Chunk的剩余空间进行判断:

若最后一个Chunk剩余空间 > 所追加文件大小,则直接添加最后一个即可。

若最后一个Chunk剩余空间 < 所追加文件大小,则首先将最后一个Chunk空间加满,然后再新建Chunk直到 > 所追加文件大小

public void appendFile(String fileName, String appendFileAddr) throws Exception {

List chunkInfoList = masterApi.getChunkInfos(fileName);

if(chunkInfoList.isEmpty()) {

System.out.println("Master找不到该文件!");

return;

}

System.out.println("文件正在进行修改...");

byte[] bytes = ConvertUtil.file2Byte(appendFileAddr);

// 获取最后一个Chunk信息

int num = chunkInfoList.size();

ChunkInfo chunkInfo = chunkInfoList.get(num-1);

int chunkLen = (int)chunkInfo.getChunk().getByteSize();

int appendLen = bytes.length;

int len = CHUNK_SIZE - chunkLen;

// 可以继续追加

if(len >= appendLen) {

byte[] newBytes = new byte[appendLen];

System.arraycopy(bytes, 0, newBytes, 0, appendLen);

chunkServerApi = (ChunkServerApi) Naming.lookup("rmi://" + chunkInfo.getFirstReplicaServerName() + "/chunkServer");

chunkServerApi.appendChunk(chunkInfo.getChunk(), newBytes, chunkInfo.getLastReplicaServerName());

masterApi.updateNameNode(fileName, chunkLen + appendLen);

}else {

// 需要新建Chunk

// 最后一个Chunk剩余大小->加满

byte[] leftBytes = new byte[len];

System.arraycopy(bytes, 0, leftBytes, 0, len);

// 更新chunkServer

chunkServerApi = (ChunkServerApi) Naming.lookup("rmi://" + chunkInfo.getFirstReplicaServerName() + "/chunkServer");

chunkServerApi.appendChunk(chunkInfo.getChunk(), leftBytes, chunkInfo.getLastReplicaServerName());

// Master更新

masterApi.updateNameNode(fileName, CHUNK_SIZE);

// 其余处理

String hash;

while (len + CHUNK_SIZE <= appendLen) {

leftBytes = new byte[CHUNK_SIZE];

System.arraycopy(bytes, len, leftBytes, 0, CHUNK_SIZE);

hash = SecurityUtil.getMd5(leftBytes);

uploadChunk(fileName, num, CHUNK_SIZE, leftBytes, hash);

len += CHUNK_SIZE;

num++;

}

if (len < appendLen) {

int lastSize = appendLen - len;

leftBytes = new byte[lastSize];

System.arraycopy(bytes, len, leftBytes, 0, lastSize);

hash = SecurityUtil.getMd5(leftBytes);

uploadChunk(fileName, num, lastSize, leftBytes, hash);

}

}

System.out.println("文件已修改!");

}

4.4 删除

​删除文件仅将Master上的信息进行删除,ChunkServer本地上的文件未删除(软删除)

public void deleteFile(String fileName) throws Exception {

masterApi.deleteNameNode(fileName);

System.out.println("文件删除成功!");

}

4.5 文件列表

public void getFileList() throws Exception {

List fileList = masterApi.getFileList();

if(fileList.size() == 0) {

System.out.println("空");

}

for(String fileName : fileList) {

System.out.println(fileName);

}

}

源代码

​具体详情请查看源代码

系统整体介绍、背景以及设计信息,尽在其他篇章:

你可能感兴趣的:(java,rmi,负载均衡)