庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

    典型的J2EE项目,package的设计有成熟的套路可循,如分为domain、dao、service、action等等,职责已经分解的比较单一和清晰,循环依赖这样的情况出现并不多。而在一般的java项目,如服务器程序、客户端程序和通用性框架的开发中,包的设计并没有套路可循,毕竟由于应用和业务种类的不同,想得出通用性的设计套路是不大可能的。这时候遵循一些原则比之生搬硬套更为重要。在《敏捷软件开发》一书中对包的设计有深入的讨论,虽然针对的是发布的二进制包而言,但是对于java package的设计同样有借鉴意义,如对包的内聚性、可重用性、稳定性的强调,对于依赖的探讨,这些都是比较笼统的概念,不是那么直观,需要在实际运用中认真归纳和重构,向这些原则靠拢。
   我所想到一个比较直观的方法就是:对于一个包的描述,你是否能用一句简明扼要的话概括,也就是包的功能或者说介绍能否做到简明扼要,这是衡量一个包的设计是否合理的最简单的方法。如果可以,显然这个包的内聚性很好,所有的类都服务于一个目的,从而带来了重用的可能(其实我对重用性并不感冒,除了工具类外真正能重用的东西少之又少,内聚性才是需要关注的);反之,这个包可能承担了太多的职责或者依赖过多,仔细的重构和分离是需要做的。包的设计同样要遵循接口分离的原则,将接口与实现隔离在不同的包之中,客户程序就不会知道具体的实现,并且也保证了实现对接口的单向依赖。当然,这时就需要引入工厂类、插件或者IOC容器来负责实例化实现类。

posted @ 2008-09-06 00:15 dennis 阅读(2990) | 评论 (3)编辑 收藏

    这个题目比较怪,听俺道来。俺一直在负责公司游戏服务器的开发和维护,日积月累下来终于将原本混乱的代码和结构重构的比较清晰了,在此过程中的体会就是,重构啊,不仅仅是技术活,更多是要克服不情愿的、得过且过的心理去做,去做了才发现麻烦并没有想象中的大。
    改造过程中遇到这么个问题,我想将对某个创建的游戏的操作都固定在一个线程执行,与其他游戏可以并发地处理;或者说依据游戏id派发到某个固定的线程处理,对此游戏的操作都是串行化。不是俺不想彻底并行化,但是要将现有的代码改造成适应并行化相当困难,俺尝试的结果是问题百出,因此就想了这么个折中策略,不同游戏之间的操作可以并行,单个游戏内操作串行。怎么派发呢?很简单的机制,根据id%size结果来处理就好,size就是你准备开的线程数。因此可以很容易地模拟一个生产者消费者模型的线程池,根据游戏id%size的结果将任务塞到队列中,让生产者线程顺序处理。已经有部分代码是这样处理的,不过是自己实现的模型(BlockingQueue),比较不适合俺想要的任务式的处理过程,灵机一动,jdk5引入的线程池不是有个单线程的版本吗?俺将这个线程池再做个池不就OK了?说起来不好理解,看代码:
public interface Task extends Runnable {
    
public int getCode();
}
    嗯,定义一个Task接口,继承Runnable,多了个getCode方法用于决定派发任务到哪个ExecutorService执行。线程池池登场:
public class SingleThreadPoolPool {
    
private Map<Integer, ExecutorService> threadPoolMap = new HashMap<Integer, ExecutorService>();

    
private int size;

    
public SingleThreadPoolPool(int size) {
        
this.size = size;
        
for (int i = 0; i < size; i++) {
            ExecutorService executor 
= Executors.newSingleThreadExecutor();
            threadPoolMap.put(i, executor);
        }
    }

    
public void execute(Task task) {
        
if (task == null)
            
return;
        threadPoolMap.get(getIndex(task.getCode())).execute(task);
    }

    
public void execute(int code, Runnable r) {
        
if (r == null)
            
return;
        threadPoolMap.get(getIndex(code)).execute(r);
    }

    
private int getIndex(int code) {
        
int index = -1;
        
if (code < 0)
            index 
= 0;
        
else
            index 
= code % this.size;
        
return index;
    }

    
public void shutdown() {
        
for (int i = 0; i < size; i++) {
            threadPoolMap.get(i).shutdown();
        }
        threadPoolMap.clear();
    }

    
public int size() {
        
return this.size;
    }
}

    哇靠,这也太简单了,这就能保证code相同的任务会被排队顺序执行。是啊,很简单,不是啥高科技,但简单明了地实现了俺的需求。需要注意的是,只有通过Executor的execute方法提交的任务才会被排到队列中哦。
    补充一个线程安全测试:
import java.util.concurrent.CountDownLatch;
import com.xlands.game.lobby.util.SingleThreadPoolPool;

import junit.framework.TestCase;

class Counter {
    
int i;

    
public void incr() {
        i
++;
    }
}

class IncrTask implements Runnable {
    Counter counter;
    CountDownLatch latch;

    
public IncrTask(Counter counter, CountDownLatch latch) {
        
this.counter = counter;
        
this.latch = latch;
    }

    
public void run() {
        
try {
            counter.incr();

        } 
finally {
            latch.countDown();
        }
    }
}

public class SingleThreadPoolPoolTest extends TestCase {
    
static final int NUM = 10000;
    SingleThreadPoolPool singleThreadPoolPool;

    @Override
    
protected void setUp() throws Exception {
        singleThreadPoolPool 
= new SingleThreadPoolPool(2);
        
super.setUp();
    }

    @Override
    
protected void tearDown() throws Exception {
        singleThreadPoolPool.shutdown();
        
super.tearDown();
    }

    
public void testThreadSafe() throws Exception {
        Counter c1 
= new Counter();
        Counter c2 
= new Counter();
        assertEquals(singleThreadPoolPool.size(), 
2);
        CountDownLatch latch 
= new CountDownLatch(NUM * 2);
        
for (int i = 0; i < NUM; i++) {
            singleThreadPoolPool.execute(
0new IncrTask(c1, latch));
            singleThreadPoolPool.execute(
1new IncrTask(c2, latch));
        }
        latch.await();
        assertEquals(NUM, c1.i);
        assertEquals(NUM, c2.i);
    }
}


posted @ 2008-09-01 19:38 dennis 阅读(2394) | 评论 (2)编辑 收藏

    从8月14日请假开始到现在,俺已经坚持跑步12天了,除了台风那天没法跑之外。一开始跑步是想改善自己的精神状态,工作太容易疲倦,心理疲惫,然后跟老婆打赌说我要坚持跑步至少3天,如果输了就回家不碰电脑一个星期。俺都是绕着天河公园跑,要么是沿着最外圈的四个大门跑,要么绕着天河湖跑三圈,每天都在四公里左右,最后几百米冲刺跑。跑完了之后做做压腿、踢腿,散步下回家,大概要花一个多小时,然后洗澡、吃饭、上班,差不多时间。
    一开始跑挺吃力,基本没有坚持跑完一整圈的,中途都得走上一段缓缓气,从第四天开始就比较轻松了,脚步开始不吃力,但是呼吸还是比较呛。坚持两个星期后,呼吸也跟上来了,现在可以快速地绕天河湖跑上两圈,或者跑大圈的时候也比较轻松啦,特别是脚步感觉很轻松,俺计划着加大运动量,看看五公里怎么样。
    大多时候是早上6点半起床跑,起不来就晚上去跑。早上好多老大爷老大妈练太极的,还有个小孩天天看到,比俺猛多了,不知道绕天河湖跑多少圈,基本上是去的时候遇到他在跑,俺回来他还在跑。早上跑感觉挺好,锻炼的人多,挺有氛围,一早上看看绿色也比较舒适。晚上跑人比较少点,特别是8、9点以后,可以脱了上衣跑,那感觉爽啊。不过俺不大建议晚上跑,一是晚上吃饭比较晚,跑步的时候还没完全消化容易肚子痛,另外就是别跑大圈,听说晚上那边也有抢劫的。跑步路线可以时常换换,俺现在比较热衷穿树林,特别是那边有片竹林,穿梭其间感觉不是一般的妙。
    俺自己感觉身体状态好了不少,过去常常感觉的胸闷的现象没有了,下午精神也好多了。不过俺的工作状态没多大改善,看来不是身体问题,还是出在其他问题上,需要刺激下自己了。

posted @ 2008-08-27 07:40 dennis 阅读(818) | 评论 (2)编辑 收藏

    前两天在公司内网上搭了个2个节点hadoop集群,暂时没有多大实际意义,仅用作自己的测试。遇到的问题在阿里巴巴这位仁兄的《Hadoop集群配置和使用技巧》都有提到的。也遇到了reduce任务卡住的问题,只需要在每个节点的/etc/hosts将集群中的机器都配置上即可解决。
   今天将一个日志统计任务用Hadoop MapReduce框架重新实现了一次,数据量并不大,每天分析一个2G多的日志文件罢了。先前是用Ruby配合cat、grep命令搞定,运行一次在50多秒左右,如果纯粹采用Ruby的话CPU占用率非常高而且慢的无法忍受,利用IO.popen调用linux的cat、grep命令先期处理就好多了。看看这个MapReduce任务:
public class GameCount extends Configured implements
        org.apache.hadoop.util.Tool {
    
public static class MapClass extends MapReduceBase implements
            Mapper
<LongWritable, Text, Text, IntWritable> {

        
private Pattern pattern;

        
public void configure(JobConf job) {
            String gameName 
= job.get("mapred.mapper.game");
            pattern 
= Pattern.compile("play\\sgame\\s" + gameName
                    
+ ".*uid=(\\d+),score=(-?\\d+),money=(-?\\d+)");
        }

        @Override
        
public void map(LongWritable key, Text value,
                OutputCollector
<Text, IntWritable> output, Reporter reporter)
                
throws IOException {
            String text 
= value.toString();
            Matcher matcher 
= pattern.matcher(text);
            
int total = 0// 总次数
            while (matcher.find()) {
                
int record = Integer.parseInt(matcher.group(2));
                output.collect(
new Text(matcher.group(1)), new IntWritable(
                        record));
                total 
+= 1;
            }
            output.collect(
new Text("total"), new IntWritable(total));
        }
    }

    
public static class ReduceClass extends MapReduceBase implements
            Reducer
<Text, IntWritable, Text, IntWritable> {

        @Override
        
public void reduce(Text key, Iterator<IntWritable> values,
                OutputCollector
<Text, IntWritable> output, Reporter reporter)
                
throws IOException {
            
int sum = 0;
            
while (values.hasNext()) {
                sum 
+= values.next().get();
            }
            output.collect(key, 
new IntWritable(sum));
        }

    }

    
static int printUsage() {
        System.out
                .println(
"gamecount [-m <maps>] [-r <reduces>] <input> <output> <gamename>");
        ToolRunner.printGenericCommandUsage(System.out);
        
return -1;
    }

   
public int run(String[] args) throws Exception {
        JobConf conf 
= new JobConf(getConf(), GameCount.class);
        conf.setJobName(
"gamecount");

      
conf.setOutputKeyClass(Text.class);
        conf.setOutputValueClass(IntWritable.class);

        conf.setMapperClass(MapClass.
class);
        conf.setCombinerClass(ReduceClass.
class);
        conf.setReducerClass(ReduceClass.
class);

        List
<String> other_args = new ArrayList<String>();
        
for (int i = 0; i < args.length; ++i) {
            
try {
                
if ("-m".equals(args[i])) {
                    conf.setNumMapTasks(Integer.parseInt(args[
++i]));
                } 
else if ("-r".equals(args[i])) {
                    conf.setNumReduceTasks(Integer.parseInt(args[
++i]));
                } 
else {
                    other_args.add(args[i]);
                }
            } 
catch (NumberFormatException except) {
                System.out.println(
"ERROR: Integer expected instead of "
                        
+ args[i]);
                
return printUsage();
            } 
catch (ArrayIndexOutOfBoundsException except) {
                System.out.println(
"ERROR: Required parameter missing from "
                        
+ args[i - 1]);
                
return printUsage();
            }
        }
        
// Make sure there are exactly 2 parameters left.
        if (other_args.size() != 3) {
            System.out.println(
"ERROR: Wrong number of parameters: "
                    
+ other_args.size() + " instead of 2.");
            
return printUsage();
        }
        FileInputFormat.setInputPaths(conf, other_args.get(
0));
        FileOutputFormat.setOutputPath(conf, 
new Path(other_args.get(1)));
        conf.set(
"mapred.mapper.game", args[2]);
        JobClient.runJob(conf);
        
return 0;
    }

    
public static void main(String[] args) throws Exception {
        
long start = System.nanoTime();
        
int res = ToolRunner.run(new Configuration(), new GameCount(), args);
        System.out.println(
"running time:" + (System.nanoTime() - start)
                
/ 1000000 + " ms");
        System.exit(res);
    }

}
    代码没啥好解释的,就是分析类似"play game DouDiZhu result:uid=1871653,score=-720,money=0"这样的字符串,分析每天玩家玩游戏的次数、分数等。打包成GameCount.jar,执行:
hadoop jar GameCount.jar test.GameCount /usr/logs/test.log /usr/output GameName

   统计的运行时间在100多秒,适当增加map和reduce任务个数没有多大改善,不过CPU占用率还是挺高的。


posted @ 2008-08-23 11:08 dennis 阅读(2651) | 评论 (0)编辑 收藏

    我对佛教的了解很浅薄。近段时间一直在读圣庄严法师的《佛学入门》。我觉的法师对佛学因果与因缘讲的很好,在此按自己的理解说下。佛教的基本教义都可以归 结于这两个词。因果说的是善有善报、恶有恶报,因此人人都该多行善、勿做恶,这一步是信佛学佛的第一步。因缘说的是无常,世间万事万物皆是无常,悟了并践 履这一点才有可能超越自我成佛成圣。
   
说因果,很多人就要说了,为什么现实中很多人多行善举,却遭恶报,而坏人却是逍遥自在未得报应?佛教给出的解答是“ 三世因果说” 。看因果不能只看现世,要 看的是前世、今世、来世,因此前世造因,今世受果,今世造因,来世受果。俗话说的好,不是不报,时候未到罢了。也许你要说佛教在狡辩,因为似乎没人知道到 底有没有这三世,特别是对于经过系统科学教育的人也更难接受。可宗教没有真假的问题,只有信与不信的问题。
  
因果,因就是因素。世间事物都是由因素和因缘组成,所谓因缘就是因素之间的联系、关系。当因素被打散、分解、重组,也就是因缘发生变化的时候,事物也变化 了,消失了,产生了。因而,事物是无常的,是没有恒定的。世间的财、色、名、食、睡都是虚幻的,无常的,不值的留恋的。如果能参透了万物,参透了人之本来 面目,超越了物我的二元对立,那才是真的悟了,有了成佛的基础。然而能放下五欲出家修行终究是少数人,这少数人按佛教的说法是修了前世的深厚福缘才由此机 缘出家。
  
道理很简单,但是,知而不行却是不知。因此对于我这个自控力并不是很好的人,在此妄谈因果因缘倒是贻笑大方了

posted @ 2008-08-23 09:17 dennis 阅读(498) | 评论 (0)编辑 收藏

    远程调用由于涉及到服务器和客户端等多个节点以及需要通过网络通讯等,会引入更多的故障可能。本地调用的语义都是恰好一次,不会多也不会少。而远程调用的语义就比较复杂,依据三个选择将产生不同的语义:
1)重发请求消息:客户端是否重发请求,直到收到应答或者认定服务器故障为止
2)过滤重复消息:当客户端重发请求时候,服务器是否过滤重复的请求
3)重传结果:服务器是否保存结果消息的历史,以便服务器不用重新执行操作就能重传结果。

对这三个选择的不同组合将产生三种可能的远程调用语义:
重发请求消息 过滤重复消息 重传结果            语义

否            不适用       不适用             或许
是            否           重新执行操作       至少一次
是            是           重传结果           至多一次

或许调用语义
:远程方法可能执行一次,或者根本不执行(在消息遗漏或者服务器崩溃的情况下)

至少一次调用语义:远程方法要嘛至少执行一次并返回结果,要嘛返回一个异常。因为启用了重发请求消息,服务器也重新执行操作,因此远程调用至少执行一次,除非服务器崩溃;但是,由于服务器重新执行操作,如果调用不是幂等的,那么多次重复执行将产生副作用叠加,可能不符合预期需求。对于幂等操作,至少一次调用语义是可以接受的。

至多一次调用语义:在此语义下,服务器会过滤重复的请求,并且缓存执行的结果重传而非重新执行。因此远程调用同样也是返回调用结果,或者一个异常。在返回结果的情况下,可以确认服务器恰好执行一次,与本地调用语义一样,不会有副作用叠加。如果返回异常,就是通知调用者没有返回结果,远程调用要嘛执行了一次,要嘛根本没有执行。总之,远程调用至多执行一次。

Java RMI的调用语义是至多一次。


posted @ 2008-08-19 23:47 dennis 阅读(1701) | 评论 (1)编辑 收藏

    分布式文件系统的设计需求大概是这么几个:透明性、并发控制、可伸缩性、容错以及安全需求等。我想试试从这几个角度去观察HDFS的设计和实现,可以更清楚地看出HDFS的应用场景和设计理念。
    首先是透明性,如果按照开放分布式处理的标准确定就有8种透明性:访问的透明性、位置的透明性、并发透明性、复制透明性、故障透明性、移动透明性、性能透明性和伸缩透明性。对于分布式文件系统,最重要的是希望能达到5个透明性要求:
1)访问的透明性:用户能通过相同的操作来访问本地文件和远程文件资源。HDFS可以做到这一点,如果HDFS设置成本地文件系统,而非分布式,那么读写 分布式HDFS的程序可以不用修改地读写本地文件,要做修改的是配置文件。可见,HDFS提供的访问的透明性是不完全的,毕竟它构建于java之上,不能 像NFS或者AFS那样去修改unix内核,同时将本地文件和远程文件以一致的方式处理。
2)位置的透明性:使用单一的文件命名空间,在不改变路径名的前提下,文件或者文件集合可以被重定位。HDFS集群只有一个Namenode来负责文件系 统命名空间的管理,文件的block可以重新分布复制,block可以增加或者减少副本,副本可以跨机架存储,而这一切对客户端都是透明的。
3)移动的透明性,这一点与位置的透明性类似,HDFS中的文件经常由于节点的失效、增加或者replication因子的改变或者重新均衡等进行着复制或者移动,而客户端和客户端程序并不需要改变什么,Namenode的edits日志文件记录着这些变更。
4)性能的透明性和伸缩的透明性:HDFS的目标就是构建在大规模廉价机器上的分布式文件系统集群,可伸缩性毋庸置疑,至于性能可以参考它首页上的一些benchmark。

    其次是并发控制,客户端对于文件的读写不应该影响其他客户端对同一个文件的读写。要想实现近似原生文件系统的单个文件拷贝语义,分布式文件系统需要做出复 杂的交互,例如采用时间戳,或者类似回调承诺(类似服务器到客户端的RPC回调,在文件更新的时候;回调有两种状态:有效或者取消。客户端通过检查回调承 诺的状态,来判断服务器上的文件是否被更新过)。HDFS并没有这样做,它的机制非常简单,任何时间都只允许一个写的客户端,文件经创建并写入之后不再改 变,它的模型是write-one-read-many, 一次写,多次读。这与它的应用场合是一致,HDFS的文件大小通常是兆至T级的,这些数据不会经常修改,最经常的是被顺序读并处理,随机读很少,因此 HDFS非常适合MapReduce框架或者web crawler应用。HDFS文件的大小也决定了它的客户端不能像某些分布式文件系统那样缓存常用到的几百个文件。

    第三,文件复制功能,一个文件可以表示为其内容在不同位置的多个拷贝。这样做带来了两个好处:访问同个文件时可以从多个服务器中获取从而改善服务的伸缩 性,另外就是提高了容错能力,某个副本损坏了,仍然可以从其他服务器节点获取该文件。HDFS文件的block为了容错都将被备份,根据配置的 replication因子来,默认是3。副本的存放策略也是很有讲究,一个放在本地机架的节点,一个放在同一机架的另一节点,另一个放在其他机架上。这 样可以最大限度地防止因故障导致的副本的丢失。不仅如此,HDFS读文件的时候也将优先选择从同一机架乃至同一数据中心的节点上读取block。

    第四,硬件和操作系统的异构性。由于构建在java平台上,HDFS的跨平台能力毋庸置疑,得益于java平台已经封装好的文件IO系统,HDFS可以在不同的操作系统和计算机上实现同样的客户端和服务端程序。

    第五,容错能力,在分布式文件系统中,尽量保证文件服务在客户端或者服务端出现问题的时候能正常使用是非常重要的。HDFS的容错能力大概可以分为两个方面:文件系统的容错性以及Hadoop本身的容错能力。文件系统的容错性通过这么几个手段:
1)在Namenode和Datanode之间维持心跳检测,当由于网络故障之类的原因,导致Datanode发出的心跳包没有被Namenode正常收 到的时候,Namenode就不会将任何新的IO操作派发给那个Datanode,该Datanode上的数据被认为是无效的,因此Namenode会检 测是否有文件block的副本数目小于设置值,如果小于就自动开始复制新的副本并分发到其他Datanode节点。
2)检测文件block的完整性,HDFS会记录每个新创建的文件的所有block的校验和。当以后检索这些文件的时候,从某个节点获取block,会首先确认校验和是否一致,如果不一致,会从其他Datanode节点上获取该block的副本。
3)集群的负载均衡,由于节点的失效或者增加,可能导致数据分布的不均匀,当某个Datanode节点的空闲空间大于一个临界值的时候,HDFS会自动从其他Datanode迁移数据过来。
4)Namenode上的fsimage和edits日志文件是HDFS的核心数据结构,如果这些文件损坏了,HDFS将失效。因而,Namenode可以配置成支持维护多 个FsImageEditlog的拷贝。任何对FsImage或者Editlog的修改,都将同步到它们的副本上。它总是选取最近的一致的FsImageEditlog使用。NamenodeHDFS是单点存在,如果Namenode所在的机器错误,手工的干预是必须的。
5)文件的删除,删除并不是马上从Namenode移出namespace,而是放在/trash目录随时可恢复,直到超过设置时间才被正式移除。
    再说Hadoop本身的容错性,Hadoop支持升级和回滚,当升级Hadoop软件时出现bug或者不兼容现象,可以通过回滚恢复到老的Hadoop版本。
    最后一个就是安全性问题,HDFS的安全性是比较弱的,只有简单的与unix文件系统类似的文件许可控制,未来版本会实现类似NFS的kerberos验证系统。

    总结下:HDFS作为通用的分布式文件系统并不适合,它在并发控制、缓存一致性以及小文件读写的效率上是比较弱的。但是它有自己明确的设计目标,那就是支 持大的数据文件(兆至T级),并且这些文件以顺序读为主,以文件读的高吞吐量为目标,并且与MapReduce框架紧密结合。



  

posted @ 2008-08-15 22:38 dennis 阅读(5901) | 评论 (3)编辑 收藏

HDFS用户指南
原文地址:http://hadoop.apache.org/core/docs/current/hdfs_user_guide.html
译者:dennis zhuang(killme2008@gmail.com),有错误请指正,多谢。

目的

本文档可以作为使用Hadoop分布式文件系统用户的起点,无论是将HDFS应用在一个Hadoop集群中还是作为一个单独的分布式文件系统使用。HDFS被设计成可以马上在许多环境中工作起来,那么一些HDFS的运行知识肯定能大大地帮助你对一个集群做配置改进和诊断。

概览

HDFS是Hadoop应用的主要分布式存储。一个HDFS集群由一个管理文件系统元数据的NameNode,和存储实际 数据的一些Datanode组成。HDFS的架构在这里有详细描述。这个用户指南主要提供给需要跟HDFS集群打交道的用户或者管理员。HDFS架构文章 中的图描绘了Namenode、Datanode和客户端们之间的基本交互。本质上,客户端与Namenode通讯获取或者修改文件的元数据,与 Datanode进行实际的IO操作。

下面的列表应该是大多数用户关心的HDFS突出特点。斜体字的术语将在后面详细描述。

1)Hadoop,包括HDFS,非常适合廉价机器上的分布式存储和分布式处理。它是容错的、可伸缩的,并且非常易于扩展。并且,以简单性和适用性著称的Map-Reduce是Hadoop不可或缺的组成部分。

2)HDFS的默认配置适合于大多数安装的应用。通常情况下,只有在一个非常大规模的集群上才需要修改默认配置。

3)HDFS是用java编写的,支持大多数平台。

4)支持shell命令行风格的HDFS目录交互。

5)Namenode和Datanode都内建了web服务器,可以方便地查看集群的状态

6)HDFS经常性地实现新的特性和改进,下面是HDFS中的一些有用特性的子集:

   文件许可和授权

   Rack awareness:当调度任务和分配存储的时候将节点的物理位置考虑进去。

   Safemode(安全模式):用于维护的一个管理状态

   fsck: 诊断文件系统的一个工具,用来查找丢失的文件或者block

   Rebalancer:当数据在Datanode间没有均匀分布的时候,用于重新平衡集群的工具

   升级和回滚:当Hadoop软件升级,在升级遇到不可预期的问题的时候,可以回滚到HDFS升级前的状态

   二级Namenode:帮助Namenode维持包含了HDFS修改的日志的文件(edits日志文件,下文谈到)大小在限制范围内。

前提条件

下面的文档描述了一个Hadoop集群的安装和设置:


本文档的剩余部分假设你已经搭设并运行了一个至少拥有一个Datanode的HDFS。基于本文档的目的,Namenode和Datanode可以运行在同一台机器上。

Web接口

Namenode和Datanode分别跑了一个内置的web服务器,来展现集群当前状态的一些基本信息。在默认配置 下,Namenode的首页地址是http://namenode:50070(namenode就是Namenode节点所在机器IP或者名称)。这个 页面列出了集群中的所有datanode以及集群的基本统计。web接口同样可以用于浏览文件系统(点击Namenode首页上的“Browse the file system"链接)。

Shell命令

Hadoop包括了多种shell风格的命令,用于跟HDFS或者Hadoop支持的其他文件系统交互。命令 bin/hadoop fs -help 可以列出Hadoop shell支持的命令。更进一步,bin/hadoop fs -help command 可以展现特定命令command的帮助细节。这些命令支持一般文件系统的操作,例如拷贝文件、修改文件权限等。同时也支持了部分HDFS特有的命令,例如 修改文件的replication因子。

DFSAdmin命令

'bin/hadoop dfsadmin' 命令支持一些HDFS管理功能的操作。'bin/hadoop dfsadmin -help'可以列出所有当前支持的命令。例如:

  • -report : 报告HDFS的基本统计信息。部分信息同时展现在Namenode的web首页上。 
  • -safemode : 尽管通常并不需要,管理员还是可以通过手工操作进入或者离开safemode状态
  • -finalizeUpgrade : 移除上一次升级时集群所做的备份。

二级Namenode

Namenode将对文件系统的修改存储在一个原生文件系统文件中(名为edits的文件)。当Namenode启动的时 候,它从映像文件(fsimage)读取HDFS的状态,然后将edits日志文件中的修改作用在此内存状态上,接着将得到的新的HDFS状态写回 fsimage,后续的正常操作开始于一个空的edits日志文件。由于Namenode仅仅在启动的时候将fsimage和edits合并,因此在一个 大的集群上经过一定时间操作后,edits文件将会非常大。由此带来的一个副作用就是下次Namenode的重新启动将花费很长时间。二级 Namenode就是为了解决这个问题,它会周期性地合并fsimage和edits日志文件,并且将edits日志文件的大小保持在限制范围内。通常它 会跑在另一个机器上,因为它的内存要求跟主namenode一样。二级Namenode可以通过'bin/start-dfs.sh'启动在conf /masters配置文件里配置的节点上。

Rebalancer

HDFS的数据可能不会总是在Datanode之间分布得很一致。一个常见的原因是往现有的集群中加入了新的Datanode。当分配block的时候,Namenode依据几个参数来决定哪个datanode来接受这些block。一些需要考虑的因素如下:

1)一个block的副本存放在正在写该block的节点上

2)需要将一个block的副本扩展到其他机架上,防止因为整个机架故障导致的数据丢失。

3)副本之一通常放在同一个机架的另一个节点上,减少跨机架的网络IO

4)将HDFS数据均匀一致地分布在集群中的datanode上。

    基于这些相互竞争的因素,数据可能不会在Datanode之间扩展得一致。HDFS给管理员提供了一个工具,用来分析block的分配情况和在datanode之间重新平衡数据。这个功能暂未实现,它的描述可以在这个 PDF文档中看到,记录编号HADOOP-1652.

Rack Awareness

典型的大规模Hadoop集群是部署在数个机架上的,那么显然同一个机架内的节点间的网络通讯比之不同机架间节点间的网 络通讯更可取。另外,Namenode会尝试将block的副本分布在数个机架中以提高容错性。Hadoop让集群管理员来决定某个节点从属于哪个机架, 通过配置变量dfs.network.script来实现。当这个脚本有配置的时候,每个节点都运行该脚本来决定它的rackid。默认安装假设所有的节 点从属于同一个机架。这个特性和配置进一步的阐述在这个PDF文档,编号为 HADOOP-692

Safemod(安全模式)

当Namenode启动的时候,它从fsimage和edits日志两个文件中加载文件系统的状态。然后等待 datanode报告他们的block信息,以便防止Namenode在确认block副本是否足够前过早地开始复制block。这段时间的 Namenode就是处于所谓safemode状态。处于safemode的Namenode也是HDFS集群的只读模型,此时不允许任何对文件系统或者 block的修改。正常情况下,Namenode会在开始后自动退出safemode。如果有需要,HDFS可以通过'bin/hadoop dfsadmin -safemode'命令显式地进入safemode状态。Namenode的web首页显示当前的safemode是否打开。更详细的描述和配置可以参 考setSafeMode()方法的JavaDoc。

译 注:详细介绍下safemode的配置参数,在safemode状态,Namenode会等待所有的datanode报告他们自己的block信息,看看 所有的block的副本是否达到最低要求的数目,这个数目可以通过dfs.replication.min参数配置,默认是1,也就是至少要求有一个副 本。当报告合格的Datanode的数目达到一定百分比,Namenode才会离开safemode状态。这个百分比也是可配置的,通过 dfs.safemode.threshold.pct参数,默认是0.999f(也就是要求99.9%的Datanode 合格)。Namenode在合格的datanode数目达到要求的时候,并不是马上离开safemode状态,会有一个扩展时间,让剩余的 datanode来报告block信息,这个扩展时间默认是30秒,可以通过dfs.safemode.extension参数配置,单位是毫秒。

Fsck

HDFS提供了fsck命令用来检测各种各样的不一致性。fsck被设计用来报告各种文件的问题,例如某个文件丢失的 block,block的副本数目是否低于设置等。不同于传统的一般原生文件系统的fsck命令,hdfs的fsck命令并不修正所检测到的错误。通常情 况下,Namenode会自动修正大多数可以被修复的错误,HDFS的fsck不是Hadoop shel的命令,可以通过'bin/hadoop fsck'执行,可以运行在整个文件系统上或者一个文件子集上。

升级和回滚

当升级某个集群的Hadoop的时候,正如任何软件的升级一样,可能会引入新的bug或者不兼容的修改导致现有的应用出 现过去没有发现的问题。在所有重要的HDFS安装应用中,是不允许出现因丢失任何数据需要从零开始重启HDFS的情况。HDFS允许管理员恢复到 Hadoop的早期版本,并且将集群的状态回滚到升级前。HDFS的升级细节请参考 upgrade wiki。HDFS在任何时间只能有一个备份,因此在升级前,管理员需要通过'bin/hadoop dfsadmin -finalizeUpgrade'命令移除现有的备份。下面简要描述了典型的升级过程:

1)在升级Hadoop前,如果已经存在备份,需要先结束(finalize)它。可以通过'dfsadmin -upgradeProgress status'命令查询集群是否需要执行finalize

2)停止集群,分发部署新版本的Hadoop

3)执行新版本的hadoop,通过添加 -upgrade 选项,例如/bin/start-dfs.sh -upgrade

4)大多数情况下,集群在升级后可以正常运行。一旦新的HDFS在运行若干天的操作后没有出现问题,那么就可以结束(finalize)这次升级。请注意,在升级前删除的文件并不释放在datanode上的实际磁盘空间,直到集群被结束(finalize)升级前。

5)如果有需要回到老版本的Hadoop,那么可以:

   a)停止集群,分发部署老版本的Hadoop

   b)通过rollback选项启动集群,例如bin/start-dfs.sh -rollback

文件许可和安全

文件许可的设计与其他平台(如linux) 的文件系统类似。在当前实现,安全被限制在简单的文件许可上。启动Namenode的用户被作为HDFS的超级用户。HDFS的未来版本将支持网络验证, 例如Kerberos方案(译注:MIT开发的一个验证系统)的用户验证以及数据传输的加密。更详细的讨论参考Permissions User and Administrator Guide

伸缩性

Hadoop正运行在成千上万个节点的集群上。 PoweredBy Hadoop列 出了一些部署Hadoop在大规模集群上的组织和机构。HDFS在每个集群上只有一个Namenode节点,Namenode节点上可用内存是当前伸缩性 的主要限制。在非常大规模的集群上,增加HDFS中存储的文件的平均大小,将可以帮助提高集群的大小而不用增加Namenode的内存需求。默认的配置可 能不适合非常大规模的集群应用。Hadoop FAQ页列出了对于大规模Hadoop集群的配置改进建议。

关联文档

 本用户指南可作为使用HDFS很好的一个起点,在本文档持续改进的同时,有一些非常有价值的关于Hadoop和HDFS的文档资料可供参考。下列资料可作为进一步探索的起点:


posted @ 2008-08-14 20:24 dennis 阅读(7059) | 评论 (2)编辑 收藏


    memcached 1.2新增加了几个参数,试着做下总结,有错误请指正:
-U <num> 监听UDP端口,默认是11211端口

-f <factor> 这个参数很重要,用于设置chunk大小的递增因子。memcached的存储模型类似一个二维数组:slab->chunk->item,每个slab大小是1M,slab中的chunk的大小等于chunk的初始大小乘以f^sid(f的sid次方),其中sid是当前slab的id,chunk的默认大小在1.1是1字节,在1.2是80字节。f就是chunk的递增倍数,在1.1固定为2,在1.2可通过-f参数设置,默认为1.25。memcachd存储的item大小一般会比存储的chunk size小,那么就有部分空间被浪费,为了尽量节省内存,正确设置-f参数就显的非常重要,通过计算尽量让chunk的大小接近或者略大于存储的item的大小。

-M 这个参数在1.1中就有了。这个参数用于在内存溢出的时候,禁止自动移除缓存数据(LRU),替代的是返回一个error。

-s <size> 设置分配给item的key、value和flag的最小字节数,默认是48字节。根据你存储的item大小适当调整这个值,可以更有效地利用内存。

-t <num> 设置处理请求的线程数。这个参数仅在编译memcached启用线程时有效。这个参数通常设置的大小等于CPU个数。


posted @ 2008-08-07 22:16 dennis 阅读(1683) | 评论 (0)编辑 收藏

    Ehcache的overflowToDisk属性用来配置当缓存存储的数据达到maxInMemory限制时是否overflow到磁盘上。如果这个属性为true,那么要求缓存的数据必须是可序列化的,如果不是可序列化的,ehcache将在日志中打印这个错误(文档中仅指出了这点),并且调用memoryStoreEvictionPolicy设置的策略(例如LRU)移除内存中的一个缓存元素再放入新的Element,同时触发CacheEventListener的notifyElementEvicted方法。

posted @ 2008-08-06 23:17 dennis 阅读(1765) | 评论 (0)编辑 收藏

仅列出标题
共56页: First 上一页 23 24 25 26 27 28 29 30 31 下一页 Last