Category: 技术

  • Hadoop reduce 慢

    又一次从博客流量来源上看到一组有意思的词:hadoop reduce 慢。
    我试着搜了一下,结果没找到自己的文章排在哪。

    言归正传,慢真是个大问题!

    首先是技术问题,也是最容易解决的问题,调参数。
    我看到有人在网上问,说WordCount都慢,那就是环境问题了。调HeapSize,调GC参数,调TaskSlot,再不行加加机器。总是能解决的。

    其实我更想说非技术问题,很多人误把hadoop妖魔化了,什么都往上套,一定会慢。
    所谓快慢是要对比的,要么是跟旧系统比,要么是跟心理预期比。
    如果你有旧系统,也是分布式,也是大数据量,写的并不太差,那hadoop是一定慢的。Hadoop能够带来的更多是开发效率的提高。
    如果没有旧系统,比心理预期慢,那就必须先拷问一下自己凭什么预期它快。
    还有就是,reduce天生就比map慢,这个不能比。

    我们遇到过很多挫折:
    在reduce的时候做矩阵运算,肯定快不起来。
    在reduce输入和map一样的数据量,因为reduce个数少,肯定快不起来。
    在map输出某个特殊的key,数据量不平衡,那某个reducer肯定快不起来。
    在繁忙的机器上运行,也一定快不起来。

    在遇到问题时,我会去调查:
    Reduce要从网络读取多少数据。
    排序能不能在内存完成。
    Reduce有没有占很多内存。

    Hadoop现在的名气大,能力相对没有那么多。盲目选择有风险,须谨慎。

  • 休假有感

    结束了连续11天的假期,团队运转良好,没有发生技术故障,我很高兴。

    作为一名工程师,我被遗忘了,很好!总出事别人才会记住你,总延期别人就会担心你,总更包别人就会关注你,可是我认为技术进步到一个阶段,就可以被别人忘记了。尽量早的做优化,尽量多的留余量,尽量高层次的设计接口,就可以做到。
    作为一名管理者,我没有授权充分。对大家OA审批的不够及时,对新服务器资源的分配没有执行,对报警跟踪的推进没有坚持。回去第一天都要补上。

    五月我想做一些改变,让工程师都和我一样解放出来,被别人遗忘。

  • Hadoop存在的问题

    在睡觉前偶然看看Google Analytics的统计,有几个人是通过搜索引擎搜索“Hadoop存在的问题”来访问了这里。
    我也试着搜了一下,我的上一篇写Hadoop的内容在Google出现第10页,baidu第4页。

    既然这个问题都让人翻了这么多页来寻找答案,我就开始反问自己了。

    Hadoop存在什么问题?

    思考了最近两年的过程,我认为Hadoop存在的问题都不是技术问题:

    1. API不稳定

    Hadoop从0.17开始,到目前我用到0.21,每次升级都让我苦不堪言。
    HDFS和MapReduce的向前兼容做的不错,但是过多的外围程序跟进了这个漩涡,例如Hive,HBase。
    由于这些外围程序的不跟进,每次升级都会损失一些追随者。

    当然0.xx版本,本来就没有承诺API是稳定的。只是至今也都没有1.0的规划,很可能将来要出到0.99。
    没有盼头的一直跟随更新,实在是很辛苦。

    2. 宣传和普及的不够

    在刚接触了Hadoop几个月的时候,有幸与zshao@apache座谈过几个小时。
    我想这可能是我能一直坚持着跟进Hadoop开发的一个动力。

    宣传的少会让谨慎的人不敢用,普及的多会让激进的人滥用。
    我自认为在技术选型上是个保守派,所以关于应用HBase的各种讨论,我都投的反对票。

    我认为Hadoop及其衍生品,根源上是批处理系统,高容错,高延时。
    这种系统我会用来做异步的计算,但是一定不能染指用户操作行为。

    应该加强宣传,让大家知道Hadoop不是万能的,它最擅长的是并行处理。

    3. 小众

    整个MapReduce这个概念是基于Google的一篇论文,本是解决PB级别搜索索引问题开发的架构。

    随着发展,虽然功能已经抽象的不仅限于索引,但是量级的优势还是一直存在。
    如果数据量在100TB以下,实在是没有必要用Hadoop。

    可是有100TB数据的组织,真的不多。大部分的试用者,没有办法从应用Hadoop的过程里获得边际收益。

  • 中小规模Hadoop集群优化

    我们有一个Hadoop集群从上个月开始遇到一系列性能问题,在逐一解决的过程中,积累了以下的优化经验。

    1. 网络带宽

    Hadoop集群的服务器在规划时就在统一的交换机下,这是在官方文档中建议的部署方式。

    但是我们的这台交换机和其他交换机的互联带宽有限,所以在客户端遇到了HDFS访问速度慢的问题。

    把操作集群的客户端也联入DataNode的交换机内部,解决了这个问题。

    2. 系统参数

    对ulimit -c的修改也是官方文档建议的修改,在集群只有10台服务器时,并没有遇到问题。

    随着机器增加和任务增加,这个值需要改的更大。

    3. 配置文件管理

    这个集群用的是Cloudera发行的版本,配置文件默认存在/etc/hadoop/conf位置。这是一个只有root才能修改的位置。

    为了修改方便,我把配置文件统一保存在一台机器上,修改后用脚本分发。保证所有服务器都是统一的配置。

    4. mapred.tasktracker.map.tasks.maximum

    这个参数控制每个TaskTracker同时运行的Map任务数。

    以前的设置是和CPU核数相同的,偶尔遇到任务挤占DataNode资源的问题。

    现在改成map+reduce+1==num_cpu_cores。

    5. 严格控制root权限

    Cloudera的发行版会创建一个hadoop用户,各种守护进程都应该以这个用户运行。

    曾经有误操作(/usr/lib/hadoop/bin/hadoop datanode &)导致本地的数据目录被root写入新文件,于是正确启动的hadoop用户进程无法读写。

    所以现在的集群服务器不提供日常的root权限访问。

    6. Java的GC模式

    在mapred.child.java.opts和HADOOP_OPTS都增加了-XX:+UseConcMarkSweepGC。

    JDK的文档中推荐现代多核处理器系统,采用这种GC方式,可以充分利用CPU的并发能力。

    这个改动对性能的积极影响很大。

    7. 选择正确的JDK

    这个集群有部分服务器的JDK用的是32位版本,不能创建-Xmx4g以上的进程。

    统一为x64版本的JDK。

    8. mapred.reduce.slowstart.completed.maps

    这个参数控制slowstart特性的时机,默认是在5%的map任务完成后,就开始调度reduce进程启动,开始copy过程。

    但是我们的机器数量不多,有一次大量的任务堆积在JobTracker里,每个TaskTracker的map和reduce slots都跑满了。

    由于map没有足够资源迅速完成,reduce也就无法结束,造成集群的资源互相死锁。

    把这个参数改成了0.75,任务堆积的列表从平均10个,变成了3个。

    9. mapred.fairscheduler.preemption

    这个参数设为了true。以便fairscheduler在用户最小资源不能满足时,kill其他人的任务腾出足够的资源。

    集群运行着各种类型的任务,有些map任务需要运行数小时。这个参数会导致这类任务被频繁kill,几乎无法完成。曾经有个任务在7小时内被kill了137次。

    可以通过调整fairscheduler的pool配置解决,给这种任务单独配置一个minMap==maxMap的pool。

    10. mapred.jobtracker.completeuserjobs.maximum

    限制每个用户在JobTracker的内存中保存任务的个数。

    因为这个参数过大,我们的JobTracker启动不到24小时就会陷入频繁的FullGC当中。

    目前改为5,JT平稳运行一天处理1500个任务,只占用800M内存。

    这个参数在>0.21.0已经没有必要设置了,因为0.21版本改造了completeuserjobs的用法,会尽快的写入磁盘,不再内存中长期存在了。

    11. mapred.jobtracker.update.faulty.tracker.interval和mapred.jobtracker.max.blacklist.percent

    一个写错的任务,会导致一大批TaskTracker进入黑名单,而且要24小时才能恢复。这种状况对中小规模的集群性能影响是非常大的。只能通过手工重启TaskTracker来修复。所以我们就修改了部分JobTracker的代码,暴露了两个参数:

    mapred.jobtracker.update.faulty.tracker.interval控制黑名单重置时间,默认是24小时不能改变,我们现在改成了1小时。

    mapred.jobtracker.max.blacklist.percent控制进入黑名单TT的比例,我们改成了0.2。

    我正在补充这两个参数的TestCase,准备提交到trunk中。

    12. 多用hive少用streaming

    由于streaming的方便快捷,我们做了很多基于它的开发。但是由于streaming的任务在运行时还要有一个java进程读写stdin/out,有一定的性能开销。

    类似的需求最好改用自定义的Deserializer+hive来完成。

  • Memcache协议

    Memcache协议在Web应用很广泛,为高效通信设计,简单而且美丽。
    上个季度中间层组织开发了一套使用Memcached做后台的缓存系统,对服务器利用率和维护成本都有很大的改善。这个版本的review结束后,我打算开源以便可以得到更多的应用实践。
    也就是在实现上面说到的系统的过程中,我发现各种版本的开源Memcache客户端,都有一些侧重。上周被java-memcached的客户端折腾了一周,虽然最后解决了,但是这类极限问题最近遇到的越来越多。我们现在需要一个侧重可靠性和性能的客户端,功能并不是这个阶段的核心问题。
    这个季度的10%课外时间,我会尝试实现一个自己的客户端。

  • Hadoop技术沙龙 感想

    昨天参加了由CSDN和Yahoo公司组织的技术沙龙活动,听Milind Bhandarkar分享了《Hadoop应用性能调优案例分析》的经验,整个过程给我很多启发。

    按照时间的顺序回忆,首先是出发之前。本来这次会议我们要有三个部门参加,各派出一名工程师。但是到临行前突然有两名工程师遇到紧急的事务,甚至预计要熬夜赶工。最终我只有半小时更换人选。

    不要乱了阵脚

    错过一次学习的机会只是偶然的,我们被工作控制的情况每个人都遇到过。我认为程序员的主要工作不是完成复杂的功能,反而是化繁为简,用同简单的方法完成复杂的事情,并且千方百计的提高自己的效率。

    社区和企业

    大概六点半左右我们到了Yahoo的办公室,当时会场还在布置,所以有时间参观了一下这里的休闲区。面积也就只有我们一半,墙面刷成淡黄色,一个水池一台咖啡机一台冰箱两台售货机。别的东西可能因为开会收走了。

    这半个小时见到了中文Hadoop社区认识的韩轶平先生,他也是这次沙龙的组织者之一,我们谈论了在社区中的趣事和各自公司遇到的技术和管理问题。在对开源的使用上我很向往Yahoo和Hadoop的共生方式,互相依靠一起发展。

    架构师

    大概进行到七点半,我对台上这个人做的事非常钦佩。他把自己团队对Hadoop使用的经验变成了工具。这个方面的尝试我们现在做的太少了,经验传承现在是靠文档甚至是作坊式的师徒。作为架构师,把团队的经验固化并传播,我认为应该是最重要的。

    后面的讨论大概进行了一个小时深浅不一,提到了HA NameNode的几个实现,HBase等等。会议时间感觉还短,要是再有机会我还要去参加。

  • 今天在用人人网的时候,突然想到上学时候学的政治课,马克思的《资本论》里面讲到过社会发展的规律,和目前我们在做的SNS有一些指导意义。
    翻箱倒柜的找了半天,都没有找到以前的政治课本,于是到网络上搜了一段描述,用来对比。

    社会发展的动力与规律
    转自维基百科 http://zh.wikipedia.org/wiki/历史唯物主义
    历史唯物主义认为:生产力和生产关系之间的矛盾,经济基础和上层建筑之间的矛盾,这是人类社会的基本矛盾。这两对矛盾存在于一切社会形态之中,贯穿于每一个社会形态的始终,决定着其他各种社会矛盾,是推动社会发展的基本动力,决定着社会历史的一般进程。
    在我们这里,技术架构是生产力,业务功能是生产关系。
    生产力和生产关系的辩证关系是:
    生产力决定生产关系:生产力对生产关系起着决定作用、支配作用,其主要表现在两个方面:第一,生产力的性质决定生产关系的性质。第二,生产力的发展变化决定生产关系的改变。
    生产关系反作用于生产力:这种反作用表现为两种情况:第一,适合生产力的性质和发展要求的先进的生产关系,促进生产力的发展;第二,不适合生产力的性质和发展要求的落后的生产关系,阻碍生产力的发展。
    生产力和生产关系之间的矛盾运动:生产力和生产关系之间的矛盾,在生产发展的不同阶段具有不同的情况。在一种生产关系产生和确立后的一段时间内,它与生产力的性质和发展要求是基本适合的,对生产力的发展具有积极的推动作用,促进生产力以前所未有的速度向前发展。虽然这时生产力和生产关系之间也有矛盾,人们也会自觉或不自觉地对生产关系作某些调整,但却不会引起生产关系的根本变革。
    在我们这里,用户行为是经济基础,盈利模式是上层建筑。
    经济基础和上层建筑的辩证关系是:
    经济基础决定上层建筑。首先,经济基础的性质决定上层建筑的性质,一定的上层建筑总是为了适应一定的经济基础的需要而建立起来的;经济上占统治地位的阶级,必然在国家政权和意识形式上占统治地位。第二,经济基础的变革决定上层建筑的变革,当经济基础发生变革后,上层建筑迟早会发生变革,以求得与经济基础相适应,经济基础的变化发展还规定着上层建筑变化发展的方向。
    上层建筑对经济基础具有能动的反作用。这种反作用表现为,上层建筑为经济基础提供政治保障和意识形态形式。这种反作用,取决于上层建筑所服务的经济基础的性质。当上层建筑适合于经济基础的要求时,它就起到巩固经济基础和促进生产力发展的作用。当上层建筑不适应经济基础的要求时,它就起到阻碍和生产力发展的作用。
    经济基础和上层建筑的矛盾运动:经济基础和上层建筑的相互作用,表现为经济基础对上层建筑的决定作用和上层建筑对经济基础的反作用。经济基础的决定作用,是第一性的;上层建筑的反作用是第二性的。经济基础的决定作用是根本性的;上层建筑的反作用是派生的和从属的。经济基础的决定作用与上层建筑的反作用,构成二者之间的矛盾运动,体现为上层建筑必须适合经济基础发展的基本规律。

  • 更新笔记 2010.03.08

    今天更新,在过程中有很多的想法,记录几句话:

    首先进度计划的太紧了,这是后续各种困难的来源。

    比较复杂的问题,一般都是人和人的问题。
    沟通大概要花一半的时间,而灵感只需要5分钟。
    代码,画时间最多的,不是最复杂的部分,是最枯燥的部分。
    遵守约定很重要,可以极大的提高效率。

  • Mail乱码解决方案

    使用Mail回复Rich Format格式的邮件时,经常会变成是乱码。打开Terminal输入下面这个命令,可以解决。
    defaults write com.apple.mail NSPreferredMailCharset "gbk"
    写个日志保存,免得重装了以后忘记怎么改。

  • 人人网中间层:实践篇

    之前的问题篇和求解篇描述了人人网在发展过程中遇到的问题,并且介绍了我们采用中间层来提高性能的解决方案。今天的实践篇将通过一个例子来实现一个中间层服务。
    这个服务要达到的目的是快速的查询用户是否有效,数据将要使用bitset保存在内存中,每个用户一位,仅保存正整数约21亿,占用内存256M。

    开始编码

    下面的代码都在这个位置保存:http://gitorious.org/renren/bitserver

    接口定义

    定义接口如下:
    [code lang=”c++”]#include
    module renren {
         struct BitSegment {
             int begin;
             int end;
             Ice::ByteSeq data;
         };
        interface BitServer {
             bool get(int offset);
             Ice::BoolSeq gets(Ice::IntSeq offsets);
             BitSegment getSegment(int begin, int end);
         };
    };[/code]
    这个BitServer.ice文件,通过slice2cpp命令编译成为服务端的Skeleton文件:
    [code lang=”bash”]slice2cpp -I/opt/Ice-3.3/slice BitServer.ice[/code]

    服务端

    有了上面生成的服务端文件后,就可以实现我们自己的业务功能了。
    BitServerI.h和BitServerI.cpp,暂时只是实现了单个get的接口。
    [code lang=”c++”]#ifndef __BitServerI_h__
    #define __BitServerI_h__

    #include

    #define SIZE_OF_BIT 2147483647
    #include

    namespace renren
    {

    class BitServerI : virtual public BitServer
    {
    public:
    void initialize();

    virtual bool get(::Ice::Int,
    const Ice::Current&);

    virtual ::Ice::BoolSeq gets(const ::Ice::IntSeq&,
    const Ice::Current&);

    virtual ::renren::BitSegment getSegment(::Ice::Int,
    ::Ice::Int,
    const Ice::Current&);
    private:
    std::bitset bits_;
    };

    }

    #endif[/code]
    [code lang=”c++”]
    #include
    #include

    int main(int argc, char** argv) {
    int status = 0;
    Ice::CommunicatorPtr ic;
    try{
    ic = Ice::initialize(argc, argv);
    Ice::ObjectAdapterPtr adapter = ic->createObjectAdapter(“BitServer”);
    renren::BitServerI* obj = new renren::BitServerI;
    obj->initialize();
    adapter->add(obj, ic->stringToIdentity(“BitServer”));
    adapter->activate();
    ic->waitForShutdown();
    } catch (const Ice::Exception& e) {
    std::cerr << e << std::endl; status = 1; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; status = 1; } catch (...) { std::cerr << "unknown exception" << std::endl; status = 1; } if (ic) { try { ic->destroy();
    } catch (const Ice::Exception& e) {
    std::cerr << e << std::endl; status = 1; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; status = 1; } catch (...) { std::cerr << "unknown exception" << std::endl; status = 1; } } return status; } void renren::BitServerI::initialize() { for (int i=0; i<0xFFFFF;i=i+2) { bits_[i]=true; } } bool renren::BitServerI::get(::Ice::Int offset, const Ice::Current& current) { if(offset < 0) return false; return bits_[offset]; } ::Ice::BoolSeq renren::BitServerI::gets(const ::Ice::IntSeq& offsets, const Ice::Current& current) { return ::Ice::BoolSeq(); } ::renren::BitSegment renren::BitServerI::getSegment(::Ice::Int begin, ::Ice::Int end, const Ice::Current& current) { return ::renren::BitSegment(); }[/code]

    客户端

    我们使用Java作为客户端,首先用slice2java工具生成Java的Proxy类。
    [code lang=”bash”]slice2java -I/opt/Ice-3.3/slice BitServer.ice[/code]
    然后自己实现客户端代码:
    [code lang=”java”]package renren;

    class BitServerAdapter {
    private final String endpoints_;
    private Ice.Communicator ic_;
    private renren.BitServerPrx prx_;

    public BitServerAdapter(String endpoints) {
    this.endpoints_ = endpoints;
    }

    public void initialize() {
    ic_ = Ice.Util.initialize();
    prx_ = renren.BitServerPrxHelper.uncheckedCast(ic_.stringToProxy(endpoints_));
    }

    public boolean get(int id) {
    return prx_.get(id);
    }

    public static void main(String[] args) {
    BitServerAdapter adapter = new BitServerAdapter(args[0]);
    adapter.initialize();
    boolean ret = adapter.get(Integer.valueOf(args[1]));
    System.out.println(ret);
    System.exit(0);
    }
    }[/code]

    性能测试

    完成了代码,来测试一下性能吧。
    首先启动服务器
    [code lang=”bash”]target/bitserver –Ice.Config=config[/code]
    再启动客户端
    [code lang=”bash”]java -cp /opt/Ice-3.3/lib/Ice.jar:target/bitclient.jar \
    renren.BitServerAdapter “BitServer:default -p 10000” 1022[/code]
    在客户端调用增加循环50000次,单线程平均每秒处理一万次。

    在多线程的环境下,单个服务器每秒可处理的请求8万次左右,已经超过了目前的需要。