
节点选择和资源分配体现到了主题中的三个字,自平衡。
自平衡的目的就是为了减少服务器的压力,多文件传输的过程我们通过在客户端进行节点的选择来完成自平衡的的作用。同样利用资源分配策略(将文件进行按大小分片,将片段的集合交给节点),让每个发送节点进行均衡发送,尽量避免节点按照文件来进行发送,文件大小的差异过大导致发送节点压力出现差异的情况而导致接收端的体验度降低。
同样响应客户端请求的速度加快,让客户端有良好的体验。
节点选择
我采用的节点选择策略是最小发送次数法(和最小链接法相似)
对于拥有资源的多个节点来说,每个节点记录自己发送资源的次数,那么对于客户端来说,一句发送节点的发送次数不同,根据需要发送节点的个数,挑选可发送节点中发送过次数最少的节点。
采用桶排序的算法,进行挑选发送次数较小的节点。
看图说话
假设我们现在有7个可发送的节点
对于节点选择具体的代码实现如下类中的getDetailList方法,参数为List为可选节点列表。
NodeSelect
public class NodeSelect implements INodeSelect{
//设置需要发送的节点的个数
private int senderCount;
//这是真正的发武器是否参与发送
private boolean serverDoSent;
public NodeSelect() {
senderCount = DEFUALT_SENDER_COUNT;
serverDoSent = true;
}
public void setServerSend(boolean send) {
this.serverDoSent = send;
}
@Override
public int getSenderCount() {
return senderCount;
}
@Override
public void setSenderCount(int senderCount) {
//用户传入的发送节点的个数小于默认的最小发送节点,则按照最小发送节点
if(senderCount < MIN_SENDER_COUNT) {
this.senderCount = MIN_SENDER_COUNT;
}else {
this.senderCount = senderCount;
}
}
@Override
public List<NetNode> getProperNodeList(List<NetNode> nodeList){
if(nodeList == null) {
return null;
}
int nodeSize = nodeList.size();
if(nodeSize <= 1) {//表明只有一个发送端
this.senderCount = 1;
return nodeList;
}
List<NetNode> tempList = new ArrayList<NetNode>();
if(!serverDoSent) {
for(int i = 0; i < nodeSize; i++) {
NetNode in = nodeList.get(i);
if(!in.getType()) {
//表明不是服务器
tempList.add(in);
}
}
}else {
tempList = nodeList;
}
int realCount = tempList.size();
//在经过一个系列条件判断之后,最后满足的节点个数大于需要的发送节点个数,再进行筛选
//如果不大于需要发送节点的个数,直接将节点的列表返回
if(realCount > senderCount) {
//说明拥有的节点个数大于需要的接点个数,那么进行筛选
tempList = getDetailList(tempList);
}else {
this.senderCount = realCount;
}
return tempList;
}
private List<NetNode> getDetailList(List<NetNode> nodeList){
NetNode maxSendNode = nodeList.get(0);
int size = nodeList.size();
//先通过for训话进行节点的遍历找出最大发送次数
for(int i = 1; i < size; i++) {
NetNode currentNode = nodeList.get(i);
if(currentNode.getSendTime() > maxSendNode.getSendTime()) {
maxSendNode = currentNode;
}
}
//申请一个最大发送次数 + 1的数组
int[] send = new int[maxSendNode.getSendTime() + 1];
//采用桶排序的方式,记录不同发送次数有多少个节点,
for(NetNode one : nodeList) {
++send[one.getSendTime()];
}
int maxsendCount = this.senderCount;
//根据需要发送的节点个数,在数组中进行标记
for(int i = 0; i < send.length; i++) {
if(maxsendCount < 0) {
send[i] = 0;
}else {
maxsendCount = maxsendCount - send[i];
if(maxsendCount < 0) {
send[i] = send[i] + maxsendCount;
}
}
}
//根据数组每个下标中元素的值,来进行真正节点的挑选
List<NetNode> resultList = new ArrayList<NetNode>();
for(NetNode temp : nodeList) {
if(send[temp.getSendTime()] <= 0) {
continue;
}else {
resultList.add(temp);
send[temp.getSendTime()]--;
}
}
return resultList;
}
}
INodeSelect
/*
*可通过继承该接口设置需要发送的节点个数,默认情况是3.
*默认最小的发送个数为1
*/
public interface INodeSelect {
int DEFUALT_SENDER_COUNT = 3;
int MIN_SENDER_COUNT = 1;
List<NetNode> getProperNodeList(List<NetNode> nodeList);
int getSenderCount();
void setSenderCount(int count);
}
资源分配
资源分配就是讲需要请求的的资源包含的文件,进行分片,因为如果我们按照每个节点发送多个完整文件的话,因为文件大小的差异可能导致有些节点分配到的文件都较大,而有些节点分配到的文件很小,导致了节点的发送不均衡。
所以我们先将所有的文件进行等长度的划分为多个相等的片段,将这些片段按照发送节点的个数进行等份分发,可能存在有些节点多分配一两个片段,但总的来说比较均衡。
ResourceSelect
public class ResourceSelect implements IResourceSelect{
private int sectionLength;
public ResourceSelect() {
sectionLength = minSectionLength;
}
@Override
public void setSectionLength(int sectionLength) {
if(sectionLength < minSectionLength) {
this.sectionLength = minSectionLength;
}else {
this.sectionLength = sectionLength;
}
}
/*
*将每个完整的文件进行分片形成多个片段
*/
@Override
public List<FileHead> getSectionHeadList(List<FileHead> completeList) {
//将list中完整的文件进行分片操作。
List<FileHead> headList = new ArrayList<FileHead>();
for(FileHead one : completeList) {
int headLength = one.getSectionLength();
if( headLength < sectionLength) {
headList.add(one);
}else {
//fileHead中的length较长,需要进行切分
int len = 0;//每次切片的长度
long offset = 0L;
while(headLength > 0) {
len = (headLength > sectionLength ? sectionLength : headLength);
headList.add(new FileHead(one.getFileHandle()
, one.getOffset() + offset, len));
headLength -= len;
offset += len;
}
}
}
return headList;
}
/*
*将资源的所有分片的片段根据发送节点个数分组
*/
@Override
public List<List<FileHead>> getSendcountList(List<FileHead> completeList, int sendCount) {
List<FileHead> headList = null;
headList = getSectionHeadList(completeList);
List<List<FileHead>> resultList = new ArrayList<List<FileHead>>();
for(int i = 0 ; i < sendCount; i++) {
List<FileHead> list = new ArrayList<FileHead>();
resultList.add(list);
}
int index = 0;
int average = headList.size() / sendCount;
for(int i = 0; i < headList.size();) {
resultList.get(index).add(headList.get(i));
i++;
if(i % average == 0) {
if(index + 1 == sendCount) {
continue;
}
index++;
}
}
return resultList;
}
}
IResourceSelect
public interface IResourceSelect {
int maxSectionLength = 1 << 24;//固定的文件的分片的长度
int minSectionLength = 1 << 6;
void setSectionLength(int sectionLength);
List<FileHead> getSectionHeadList(List<FileHead> completeList);
List<List<FileHead>> getSendcountList(List<FileHead> completeList, int sendCount);
}
我们虽然采取了最小发送次数的策略进行节点的选择,但是我们并不知道节点当前的状态,用户都选择了发送次数少的节点进行请求,那么对于此节点来说并发请求量会骤然增加,无疑是增加了节点的压力。
可以采用一种方案进行解决,当请求端请求客户端进行资源的发送的时候,由接收端将这个请求转接给注册中心,让注册中心来帮助它记录此时它的的发送状态(有多少接收端进行请求),当服务器发处理完发送任务时,主动告诉注册中心当前任务的完成情况,都注册中心进行记录。
当接收端请求节点列表之后,可通过判断发送次数和节点当前的是否处于忙的状态,两者结合进行较为合理的节点选择。
此方法过程相对较为复杂,并且对节点状态进行频繁的修改,对于注册中心来说,进行查找对应的节点相对耗时,因为我们采用的是键为值为list< NetNode >的形式。
我们可以在注册中心添加一个list或者map专门存储所有的节点,相对来说查找更为方便。