序
我一直以为能编代码,做出产品就足够了,胡老大一次又一次的告诉我停下来消化消化很重要,当时铭记在心,晚上一觉一睡就忘记了,第二天还是想着还有哪些功能没实现,那些地方亟待完善,于是打开电脑还是去看代码,写代码去了。直到有一天,被胡老大恐吓了一下,要记得写总结,再这样下去我也会失去耐心的,当时突然也就想起了胡老大说过的一句话:不通宵写总结的程序员不是一个好码农。尽管我还是没有通宵,但我写到两点也是一个进步吧,呵呵。
尽管还没有云盘还没有实现Hbase,没有给用户限定空间大小,没有…但是我还是写一下这个暑假做的这款产品——“蓝云”吧。当时要是没有"蓝云"项目,我觉得我暑假肯定没有一直学习的动力了。
上手
七月中旬学校放假后,来蓝杰第一天就加入了迪姐的通讯项目组,因为中途插入,直到项目总结,我都没做帮上什么忙。突闻有人在搞Linux,我以前就对它很感兴趣,但是过去后和他们一了解才知道都是大二的,说实话你一要大四的人了在里面还真有点羞愧。当时整屋子都是机器显示屏,原来还在装系统,刚好便宜我从头学起。
工欲善其事必先利其器,首先就是在自己电脑上装上Linux光环下的Ubuntu系统,So Easy,就不多介绍了,接下来就是配置环境:JDK(JAVA运行环境),Eclipse(Java编程环境)…在蓝杰的支持下,我们用了7台普通的PC机,内存和CPU就不用多说了,Hadoop的特点就是机器在多不再精。而让他们统一听从Namenode就需要SSH(Secure Shell),通过SSH无密码公钥认证形式来启动和停止datanode上各种进程。通过使用SSH,你可以把所有传输的数据进行加密,冒充真正的服务器接收你传给服务器的数据,然后再冒充你把数据传给真正的服务器的这种攻击方式就不可能实现了,而且也能够防止DNS欺骗和IP欺骗。使用SSH,还有一个额外的好处就是传输的数据是经过压缩的,所以可以加快传输的速度。
我是跟着湖大的大熊童鞋装的,具体的就去看看他的博客吧:
http://2509477698.iteye.com/blog/1928138
HDFS
记得装好系统后和所有学习Hadoop的人一样,先要多了解HDFS(hadoop分布式文件系统)和MapReduce应用——WordCount。
首先了解一下Hadoop集群的HDFS。HDFS集群中有两种角色:master与slave,master又分为主master与次master。其中:
1) 主 master同时提供NameNode 、SecondaryNameNode 及JobTracker 三种服务;JobTracker 接受作业提交,监控和控制作业的运行,负责任务分发到TaskTracker 。
2) 次master只提供SecondaryNameNode 服务;
3) 所有slave可以提供DateNode 或TaskTracker 两种服务。TaskTracker控制Map/Reduce任务中当前节点的运行。
HDFS的几个设计特点(对于框架设计值得借鉴):
1. Block的放置
默认不配置。一个Block会有三份备份,一份放在NameNode指定的DataNode,另一份放在与指定DataNode非同一Rack上的DataNode,最后一份放在与指定DataNode同一Rack上的DataNode上。备份无非就是为了数据安全,考虑同一Rack的失败情况以及不同Rack之间数据拷贝性能问题就采用这种配置方式。
Block默认情况下块的大小为64M,这意味这就算是存储一个小于64M的文件,它也会占据64M的空间。如果一个文件远小于64M,那就太浪费了空间了,如何解决这个问题呢?是不是可以把很多的小文件合并到一起,形成一个与64M,差不多的文件呢,如果这样可以,当要使用到里面的某一个小文件,那又该如何查询出来呢?
当时我们都思考过,后来搜索到SequeceFile,它是Hadoop API提供的一种二进制文件支持。这种二进制文件直接将<key, value>对序列化到文件中。一般对小文件可以使用这种文件合并,即将文件名作为key,文件内容作为value序列化到大文件中。这种文件格式有以下好处:
1)支持压缩,且可定制为基于Record或Block压缩(Block级压缩性能较优) 2)本地化任务支持:因为文件可以被切分,因此MapReduce任务时数据的本地化情况应该是非常好的。3)难度低:因为是Hadoop框架提供的API,业务逻辑侧的修改比较简单。
坏处是需要一个合并文件的过程,且合并后的文件将不方便查看。最重要的就是这个坏处,合并后的文件将不方便查看,因此也就不了了之,到目前还是没有对其进行优化。
2. 心跳检测
心跳检测DataNode的健康状况,如果发现问题就采取数据备份的方式来保证数据的安全性。
代码:client.sendUrgentData(0xFF); // 发送心跳包
3. 数据复制
数据复制(场景为DataNode失败、需要平衡DataNode的存储利用率和需要平衡DataNode数据交互压力等情况):这里先说一下,使用HDFS的balancer命令,可以配置一个Threshold来平衡每一个DataNode磁盘利用率。例如设置了Threshold为10%,那么执行balancer命令的时候,首先统计所有DataNode的磁盘利用率的均值,然后判断如果某一个DataNode的磁盘利用率超过这个均值Threshold以上,那么将会把这个DataNode的block转移到磁盘利用率低的DataNode,这对于新节点的加入来说十分有用。
4. 数据校验:
采用CRC32作数据交验。在文件Block写入的时候除了写入数据还会写入交验信息,在读取的时候需要交验后再读入。
5. NameNode是单点
如果失败的话,任务处理信息将会记录在本地文件系统和远端的文件系统中。
6. 数据管道性的写入
当客户端要写入文件到DataNode上,首先客户端读取一个Block然后写到第一个DataNode上,然后由第一个DataNode传递到备份的DataNode上,一直到所有需要写入这个Block的NataNode都成功写入,客户端才会继续开始写下一个Block。
7. 安全模式
安全模式主要是为了系统启动的时候检查各个DataNode上数据块的有效性,同时根据策略必要的复制或者删除部分数据块。在分布式文件系统启动的时候,开始的时候会有安全模式,当分布式文件系统处于安全模式的情况下,文件系统中的内容不允许修改也不允许删除,直到安全模式结束。运行期通过命令也可以进入安全模式。在实践过程中,系统启动的时候去修改和删除文件也会有安全模式不允许修改的出错提示,只需要等待一会儿即可。
MapReduce
MapReduce从它名字上来看就大致可以看出个缘由,两个动词Map和Reduce,“Map(展开)”就是将一个任务分解成为多个任务,“Reduce”就是将分解后多任务处理的结果汇总起来,得出最后的分析结果。
具体过程序如下:
1、Input
原始数据—><InputKey, InputValue>
2、MAP
<InputKey, InputValue> —>List<<MapKey, MapValue>>
2.1)对输入的数据有Split(分割)的过程,保证任务并行效率
2.2) Map映射:将原始数据映射成用于Reduce的数据
2.3)Shuffle(混合)的过程,对于提高Reduce的效率以及减小数据传输的压力有很大的帮助。
3、Reduce
<MapKey, List<MapValue>>—><OutputKey, OutputValue>
Reduce合并:将相同Key值的中间数据合并成最终数据
4、Output
<OutputKey, OutputValue>—>结果文件
Output输出:将最终处理结果输出到文件
就拿Hadoop提供的范例Wordcount(计算网页中各个单词的数量)来分析一下吧:
1) Input:文本内容 <行号,文本内容>
2) Map:<行号, 文本内容> List<<单词, 数量1>>
3) Reduce:<单词, List<数量1>> <单词, 数量合计>
4) Output:List<<单词, 数量>> 文本文件
蓝云开发
之前都是基础,当真正要干实事的时候,已经剩下的只有两个人了,尽管只有和大熊两个人相依开发,但由于众位兄弟姐妹还没走的时候就有了一点想法的基础上,我们也才能很快的开发出来。我们比较相关云盘产品后,仿照其功能,陆陆续续的实现上传,下载,重命名,删除,移动。
由于HDFS搭建在Ubutnu上面,而客户端为了受众,当然就必须在Windows系统中,而Windows的客户端不能直接操作HDFS,因此我们对文件的操作都是先把文件放到Ubuntu服务器上面后,再由Ubuntu对HDFS进行操作,例如上传文件,先得把文件上传到Ubuntu服务器后再上传到HDFS,下载亦相反,而移动文件由于HDFS没有提供相关移动的方法供我们调用,因此当客户端想移动时就得先存入服务器,再换个路径往HDFS上面存放,即实现了移动。我们也知道这样做会消耗服务器的效率,所以“蓝云”还亟待拯救。
因为分工的时候我实现的是服务器端程序,因此我也就写一写HDFS中关于蓝云所实现的几个功能:
上传:
/**
* 上传
* @param localSrc 本地的文件地址,即文件的路径
* @param hdfsSrc 存放在hdfs的文件地址
*/
public boolean sendToHdfs1(String localSrc,String hdfsSrc){
InputStream in;
try {
in = new BufferedInputStream(new FileInputStream(localSrc));
Configuration conf = new Configuration();//得到配置对象
FileSystem fs; //文件系统
try {
fs = FileSystem.get(URI.create(hdfsSrc), conf);
//输出流,创建一个输出流
OutputStream out = fs.create(new Path(hdfsSrc), new Progressable() {
//重写progress方法
public void progress() {
//System.out.println("上传完一个设定缓存区大小容量的文件!");
}
});
//连接两个流,形成通道,使输入流向输出流传输数据,
IOUtils.copyBytes(in, out, 10240,true); //in为输入流对象,out为输出流对象,4096为缓冲区大小,true为上传后关闭流
return true;
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return false;
}
下载:
/**
* 下载
* @param localSrc1 本地的文件地址,即文件的路径
* @param hdfsSrc1 存放在hdfs的文件地址
*/
public boolean sendFromHdfs(String hdfsSrc1, String localSrc1) {
Configuration conf = new Configuration();
FileSystem fs = null;
try {
fs = FileSystem.get(URI.create(hdfsSrc1), conf);
Path hdfs_path = new Path(hdfsSrc1);
Path local_path = new Path(localSrc1);
fs.copyToLocalFile(hdfs_path, local_path);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
重命名:
/**
* 重命名
* @param old_st原文件名
* @param new_st新文件名
*/
public boolean changeFileName(String old_st,String new_st){
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(URI.create(old_st), conf);
Path old_path = new Path(old_st);
Path new_path = new Path(new_st);
System.out.println("开始重命名操作...");
fs.rename(old_path, new_path);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
删除
/*
* 删除指定的文件夹的方法
* filepath为制定的文件路径
*/
public boolean DeleteDir(String filepath){
Configuration conf = new Configuration();
FileSystem fs=null;
try {
fs = FileSystem.get(URI.create(filepath), conf);
Path path = new Path(filepath);
fs.delete(path, true);
fs.close();//关闭文件系统
return true;
} catch (IOException e) {
if(fs!=null){
try {
fs.close();//关闭文件系统
} catch (IOException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}
return false;
}
移动:
/**
* 移动
* @param old_st原来存放的路径
* @param new_st移动到的路径
*/
public boolean moveFileName(String old_st, String new_st) {
try {
//下载到服务器本地
DownloadFileThread dft = new DownloadFileThread();
boolean down_flag = dft.sendFromHdfs(old_st,"/home/hadoop/文档/temp");
Configuration conf = new Configuration();
FileSystem fs = null;
//删除源文件
try {
fs = FileSystem.get(URI.create(old_st), conf);
Path hdfs_path = new Path(old_st);
fs.delete(hdfs_path);
} catch (IOException e) {
e.printStackTrace();
}
//从服务器本地传到新路径
UploadeFileThread uft = new UploadeFileThread();
new_st = new_st+old_st.substring(old_st.lastIndexOf("/"));
boolean uplod_flag = uft.sendToHdfs1("/home/hadoop/文档/temp", new_st);
if(down_flag&&uplod_flag){
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
新建文件夹:
/**
*新建文件夹
* @param path_st新建文件夹所在的路径
*/
public boolean makeDir(String path_st) {
Configuration conf = new Configuration();
FileSystem fs=null;
try {
fs = FileSystem.get(URI.create(path_st),conf);
Path path = new Path(path_st);
fs.mkdirs(path);//创建该文件夹
fs.close();//关闭文件系统
return true;
} catch (IOException e){
if(fs!=null){
try {
fs.close();//关闭文件系统
} catch (IOException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}
return false;
}
再谈谈所遇到的一点问题吧,因为系统不同还导致的问题导致其文件流不同,上传下载txt文件首先就是文件结束标志不同,众所周知,Windows下txt文件结束标志为字节-1,但是通过文件流发送到Ubuntu下就成了字节255,因此当按位读取文件时服务器和客户端的判断是否结束的标志就不同。但当我们完成后测试时又发现了一个新的问题,那就是图片自带的包头文件都有字节255,JPG更厉害,包头是以字节255开始的,因此我们不得不针对图片又换了文件流传送方式,采取读取整个文件的byte数组后再传输和接收。尽管图片还是会失真,破损,但是也是一个进步。
而外网如何访问,尽管听起来很简单,但是自己从没了解过,也是一个麻烦事,看了很多资料后才知道原来内网是由端口号来映射,由路由器设置即可,若进入不了路由的话就去下载个 myupnp,这个工具可以不经过路由就能映射,只需要把端口添加进去就行了,最后下载“花生壳”工具并进行免费注册,这样就能把内网和外网ip绑定,即使外网ip变了,也不会影响到web服务器的使用。我直接找了老师进入路由器设置端口映射,也才知道路由器自带的有花生壳工具,最后登录生成域名替换客户端源程序中的服务器地址即大功告成。
概念
当我写总结的时候,老师突然带进来一个学弟,要我跟他解释一下“云计算”,好让他了解一下,我突然就懵了,对哦,什么叫“云计算”,一分钟时间我如何解释清楚这个概念。我急的满头大汗的也只是给他解释了一下分布式集群及它的优点。我们往往忽视了最基础的,其实也不是不懂,只是不知道如何精简通俗的表达出来,在此还是弥补一下,结合百科好好解释一下这个吧。
Wiki定义:云计算是一种通过Internet以服务的方式提供动态可伸缩的虚拟化的资源的计算模式。
这种百科定义,说实话,一下子还真听不懂。还是来看看李开复怎么说,李开复曾打了一个形象的比喻:钱庄。最早人们只是把钱放在枕头底下,后来有了钱庄,很安全,不过兑现起来比较麻烦。现在发展到银行可以到任何一个网点取钱,甚至通过ATM或者国外的渠道。就像用电不需要家家装备发电机,直接从电力公司购买一样。
“云计算”带来的就是这样一种变革——由谷歌、IBM这样的专业网络公司来搭建计算机存储、运算中心,用户通过一根网线借助浏览器就可以很方便的访问,把“云”做为资料存储以及应用服务的中心。
我觉得呢,解释清楚云的概念就好,这种“云”呢,就是一台或若干台用来存储数据的和计算数据的电脑(理论叫做服务器吧),你不知道也不用知道它是一台还是若干台什么样的什么配置的电脑,所以我们用“云”来代替。
云计算有服务端和客户端的概念。而且往往是服务“云”端承担全部工作,客户端仅仅是调用和显示。因此服务端要求很强大,“云”一般是由集群机器构成,所以“云”端的计算是往往依赖分布式来实现,也解决了对高性能计算资源的需求普遍化的问题。
这是一门高深而发展迅速学问,刚刚起步学习的我只是用云盘这一个小项目来鼓励自己,让自己能研究的更多,学的更好。