Archive for 十二月, 2012

淘宝网拥有国内最具商业价值的海量数据。截至当前,每天有超过30亿的店铺、商品浏览记录,10亿在线商品数,上千万的成交、收藏和评价数据。如何从这些数据中挖掘出真正的商业价值,进而帮助淘宝、商家进行企业的数据化运营,帮助消费者进行理性的购物决策,是淘宝数据平台与产品部的使命。

为此,我们进行了一系列数据产品的研发,比如为大家所熟知的量子统计、数据魔方和淘宝指数等。尽管从业务层面来讲,数据产品的研发难度并不高;但在“海量”的限定下,数据产品的计算、存储和检索难度陡然上升。本文将以数据魔方为例,向大家介绍淘宝在海量数据产品技术架构方面的探索。

淘宝海量数据产品技术架构

数据产品的一个最大特点是数据的非实时写入,正因为如此,我们可以认为,在一定的时间段内,整个系统的数据是只读的。这为我们设计缓存奠定了非常重要的基础。

 

图1 淘宝海量数据产品技术架构
图1 淘宝海量数据产品技术架构 

按照数据的流向来划分,我们把淘宝数据产品的技术架构分为五层(如图1所示),分别是数据源、计算层、存储层、查询层和产品层。位于架构顶端的是我们的数据来源层,这里有淘宝主站的用户、店铺、商品和交易等数据库,还有用户的浏览、搜索等行为日志等。这一系列的数据是数据产品最原始的生命力所在。

在数据源层实时产生的数据,通过淘宝自主研发的数据传输组件DataX、DbSync和Timetunnel准实时地传输到一个有1500个节点的Hadoop集群上,这个集群我们称之为“云梯”,是计算层的主要组成部分。在“云梯”上,我们每天有大约40000个作业对1.5PB的原始数据按照产品需求进行不同的MapReduce计算。这一计算过程通常都能在凌晨两点之前完成。相对于前端产品看到的数据,这里的计算结果很可能是一个处于中间状态的结果,这往往是在数据冗余与前端计算之间做了适当平衡的结果。

不得不提的是,一些对实效性要求很高的数据,例如针对搜索词的统计数据,我们希望能尽快推送到数据产品前端。这种需求再采用“云梯”来计算效率将是比较低的,为此我们做了流式数据的实时计算平台,称之为“银河”。“银河”也是一个分布式系统,它接收来自TimeTunnel的实时消息,在内存中做实时计算,并把计算结果在尽可能短的时间内刷新到NoSQL存储设备中,供前端产品调用。

容易理解,“云梯”或者“银河”并不适合直接向产品提供实时的数据查询服务。这是因为,对于“云梯”来说,它的定位只是做离线计算的,无法支持较高的性能和并发需求;而对于“银河”而言,尽管所有的代码都掌握在我们手中,但要完整地将数据接收、实时计算、存储和查询等功能集成在一个分布式系统中,避免不了分层,最终仍然落到了目前的架构上。

为此,我们针对前端产品设计了专门的存储层。在这一层,我们有基于MySQL的分布式关系型数据库集群MyFOX和基于HBase的NoSQL存储集群Prom,在后面的文字中,我将重点介绍这两个集群的实现原理。除此之外,其他第三方的模块也被我们纳入存储层的范畴。

存储层异构模块的增多,对前端产品的使用带来了挑战。为此,我们设计了通用的数据中间层——glider——来屏蔽这个影响。glider以HTTP协议对外提供restful方式的接口。数据产品可以通过一个唯一的URL获取到它想要的数据。

以上是淘宝海量数据产品在技术架构方面的一个概括性的介绍,接下来我将重点从四个方面阐述数据魔方设计上的特点。

关系型数据库仍然是王道

关系型数据库(RDBMS)自20世纪70年代提出以来,在工业生产中得到了广泛的使用。经过三十多年的长足发展,诞生了一批优秀的数据库软件,例如Oracle、MySQL、DB2、Sybase和SQL Server等。

 

图2 MyFOX中的数据增长曲线
图2 MyFOX中的数据增长曲线 

尽管相对于非关系型数据库而言,关系型数据库在分区容忍性(Tolerance to Network Partitions)方面存在劣势,但由于它强大的语义表达能力以及数据之间的关系表达能力,在数据产品中仍然占据着不可替代的作用。

淘宝数据产品选择MySQL的MyISAM引擎作为底层的数据存储引擎。在此基础上,为了应对海量数据,我们设计了分布式MySQL集群的查询代理层——MyFOX,使得分区对前端应用透明。

 

图3 MyFOX的数据查询过程
图3 MyFOX的数据查询过程 

目前,存储在MyFOX中的统计结果数据已经达到10TB,占据着数据魔方总数据量的95%以上,并且正在以每天超过6亿的增量增长着(如图2所示)。这些数据被我们近似均匀地分布到20个MySQL节点上,在查询时,经由MyFOX透明地对外服务(如图3所示)。

 

图4 MyFOX节点结构
图4 MyFOX节点结构 

值得一提的是,在MyFOX现有的20个节点中,并不是所有节点都是“平等”的。一般而言,数据产品的用户更多地只关心“最近几天”的数据,越早的数据,越容易被冷落。为此,出于硬件成本考虑,我们在这20个节点中分出了“热节点”和“冷节点”(如图4所示)。

顾名思义,“热节点”存放最新的、被访问频率较高的数据。对于这部分数据,我们希望能给用户提供尽可能快的查询速度,所以在硬盘方面,我们选择了每分钟15000转的SAS硬盘,按照一个节点两台机器来计算,单位数据的存储成本约为4.5W/TB。相对应地,“冷数据”我们选择了每分钟7500转的SATA硬盘,单碟上能够存放更多的数据,存储成本约为1.6W/TB。

将冷热数据进行分离的另外一个好处是可以有效提高内存磁盘比。从图4可以看出,“热节点”上单机只有24GB内存,而磁盘装满大约有1.8TB(300 * 12 * 0.5 / 1024),内存磁盘比约为4:300,远远低于MySQL服务器的一个合理值。内存磁盘比过低导致的后果是,总有一天,即使所有内存用完也存不下数据的索引了——这个时候,大量的查询请求都需要从磁盘中读取索引,效率大打折扣。

NoSQL是SQL的有益补充

在MyFOX出现之后,一切都看起来那么完美,开发人员甚至不会意识到MyFOX的存在,一条不用任何特殊修饰的SQL语句就可以满足需求。这个状态持续了很长一段时间,直到有一天,我们碰到了传统的关系型数据库无法解决的问题——全属性选择器(如图5所示)。

 

图5 全属性选择器
图5 全属性选择器 

这是一个非常典型的例子。为了说明问题,我们仍然以关系型数据库的思路来描述。对于笔记本电脑这个类目,用户某一次查询所选择的过滤条件可能包括“笔记本尺寸”、“笔记本定位”、“硬盘容量”等一系列属性(字段),并且在每个可能用在过滤条件的属性上,属性值的分布是极不均匀的。在图5中我们可以看到,笔记本电脑的尺寸这一属性有着10个枚举值,而“蓝牙功能”这个属性值是个布尔值,数据的筛选性非常差。

在用户所选择的过滤条件不确定的情况下,解决全属性问题的思路有两个:一个是穷举所有可能的过滤条件组合,在“云梯”上进行预先计算,存入数据库供查询;另一个是存储原始数据,在用户查询时根据过滤条件筛选出相应的记录进行现场计算。很明显,由于过滤条件的排列组合几乎是无法穷举的,第一种方案在现实中是不可取的;而第二种方案中,原始数据存储在什么地方?如果仍然用关系型数据库,那么你打算怎样为这个表建立索引?

这一系列问题把我们引到了“创建定制化的存储、现场计算并提供查询服务的引擎”的思路上来,这就是Prometheus(如图6所示)。

 

图6 Prom的存储结构
图6 Prom的存储结构 

从图6可以看出,我们选择了HBase作为Prom的底层存储引擎。之所以选择HBase,主要是因为它是建立在HDFS之上的,并且对于MapReduce有良好的编程接口。尽管Prom是一个通用的、解决共性问题的服务框架,但在这里,我们仍然以全属性选择为例,来说明Prom的工作原理。这里的原始数据是前一天在淘宝上的交易明细,在HBase集群中,我们以属性对(属性与属性值的组合)作为row-key进行存储。而row-key对应的值,我们设计了两个column-family,即存放交易ID列表的index字段和原始交易明细的data字段。在存储的时候,我们有意识地让每个字段中的每一个元素都是定长的,这是为了支持通过偏移量快速地找到相应记录,避免复杂的查找算法和磁盘的大量随机读取请求。

 

图7 Prom查询过程
图7 Prom查询过程 

图7用一个典型的例子描述的Prom在提供查询服务时的工作原理,限于篇幅,这里不做详细描述。值得一提的是,Prom支持的计算并不仅限于求和SUM运算,统计意义上的常用计算都是支持的。在现场计算方面,我们对Hbase进行了扩展,Prom要求每个节点返回的数据是已经经过“本地计算”的局部最优解,最终的全局最优解只是各个节点返回的局部最优解的一个简单汇总。很显然,这样的设计思路是要充分利用各个节点的并行计算能力,并且避免大量明细数据的网络传输开销。

用中间层隔离前后端

上文提到过,MyFOX和Prom为数据产品的不同需求提供了数据存储和底层查询的解决方案,但随之而来的问题是,各种异构的存储模块给前端产品的使用带来了很大的挑战。并且,前端产品的一个请求所需要的数据往往不可能只从一个模块获取。

举个例子,我们要在数据魔方中看昨天做热销的商品,首先从MyFOX中拿到一个热销排行榜的数据,但这里的“商品”只是一个ID,并没有ID所对应的商品描述、图片等数据。这个时候我们要从淘宝主站提供的接口中去获取这些数据,然后一一对应到热销排行榜中,最终呈现给用户。

 

图8 glider的技术架构
图8 glider的技术架构 

有经验的读者一定可以想到,从本质上来讲,这就是广义上的异构“表”之间的JOIN操作。那么,谁来负责这个事情呢?很容易想到,在存储层与前端产品之间增加一个中间层,它负责各个异构“表”之间的数据JOIN和UNION等计算,并且隔离前端产品和后端存储,提供统一的数据查询服务。这个中间层就是glider(如图8所示)。

缓存是系统化的工程

除了起到隔离前后端以及异构“表”之间的数据整合的作用之外,glider的另外一个不容忽视的作用便是缓存管理。上文提到过,在特定的时间段内,我们认为数据产品中的数据是只读的,这是利用缓存来提高性能的理论基础。

在图8中我们看到,glider中存在两层缓存,分别是基于各个异构“表”(datasource)的二级缓存和整合之后基于独立请求的一级缓存。除此之外,各个异构“表”内部可能还存在自己的缓存机制。细心的读者一定注意到了图3中MyFOX的缓存设计,我们没有选择对汇总计算后的最终结果进行缓存,而是针对每个分片进行缓存,其目的在于提高缓存的命中率,并且降低数据的冗余度。

大量使用缓存的最大问题就是数据一致性问题。如何保证底层数据的变化在尽可能短的时间内体现给最终用户呢?这一定是一个系统化的工程,尤其对于分层较多的系统来说。

 

图9 缓存控制体系
图9 缓存控制体系 

图9向我们展示了数据魔方在缓存控制方面的设计思路。用户的请求中一定是带了缓存控制的“命令”的,这包括URL中的query string,和HTTP头中的“If-None-Match”信息。并且,这个缓存控制“命令”一定会经过层层传递,最终传递到底层存储的异构“表”模块。各异构“表”除了返回各自的数据之外,还会返回各自的数据缓存过期时间(ttl),而glider最终输出的过期时间是各个异构“表”过期时间的最小值。这一过期时间也一定是从底层存储层层传递,最终通过HTTP头返回给用户浏览器的。

缓存系统不得不考虑的另一个问题是缓存穿透与失效时的雪崩效应。缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。在数据魔方里,我们采用了一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存失效时的雪崩效应对底层系统的冲击非常可怕。遗憾的是,这个问题目前并没有很完美的解决方案。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。在数据魔方中,我们设计的缓存过期机制理论上能够将各个客户端的数据失效时间均匀地分布在时间轴上,一定程度上能够避免缓存同时失效带来的雪崩效应。

结束语

正是基于本文所描述的架构特点,数据魔方目前已经能够提供压缩前80TB的数据存储空间,数据中间层glider支持每天4000万的查询请求,平均响应时间在28毫秒(6月1日数据),足以满足未来一段时间内的业务增长需求。

尽管如此,整个系统中仍然存在很多不完善的地方。一个典型的例子莫过于各个分层之间使用短连接模式的HTTP协议进行通信。这样的策略直接导致在流量高峰期单机的TCP连接数非常高。所以说,一个良好的架构固然能够在很大程度上降低开发和维护的成本,但它自身一定是随着数据量和流量的变化而不断变化的。我相信,过不了几年,淘宝数据产品的技术架构一定会是另外的样子。

 

zz from: http://www.programmer.com.cn/7578/

libcurl:

官网:http://curl.haxx.se/libcurl/

错误码:http://curl.haxx.se/libcurl/c/libcurl-errors.html

This is the generic return code used by functions in the libcurl multi interface. Also consider curl_multi_strerror(3).

CURLM_CALL_MULTI_PERFORM (-1)

This is not really an error. It means you should call curl_multi_perform(3) again without doing select() or similar in between. Before version 7.20.0 this could be returned by curl_multi_perform(3), but in later versions this return code is never used.

CURLM_OK (0)

Things are fine.

CURLM_BAD_HANDLE (1)

The passed-in handle is not a valid CURLM handle.

CURLM_BAD_EASY_HANDLE (2)

An easy handle was not good/valid. It could mean that it isn’t an easy handle at all, or possibly that the handle already is in used by this or another multi handle.

CURLM_OUT_OF_MEMORY (3)

You are doomed.

CURLM_INTERNAL_ERROR (4)

This can only be returned if libcurl bugs. Please report it to us!

CURLM_BAD_SOCKET (5)

The passed-in socket is not a valid one that libcurl already knows about. (Added in 7.15.4)

CURLM_UNKNOWN_OPTION (6)

curl_multi_setopt() with unsupported option (Added in 7.15.4)

 

C API:http://curl.haxx.se/libcurl/c/

C multi API:http://curl.haxx.se/libcurl/c/libcurl-multi.html

C example:http://curl.haxx.se/libcurl/c/example.html

结合php的curl extension、libcurl和tcpdump命令,分析curl_multi_*的处理过程如下:

/*
 * Comment by flykobe:
 * sudo /usr/sbin/tcpdump -i eth0  host item.taobao.com
 * */

function curl_multi_fetch($urlarr=array()){
    $result=$res=$ch=array();
    $nch = 0;
    $mh = curl_multi_init();
    foreach ($urlarr as $nk => $url) {
        $timeout=2;
        $ch[$nch] = curl_init();
        curl_setopt_array($ch[$nch], array(
        CURLOPT_URL => $url,
        CURLOPT_HEADER => false,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => $timeout,
        ));
        curl_multi_add_handle($mh, $ch[$nch]);
        ++$nch;
    }

        /*
         * Comment by flykobe:
         * 针对mrc做这样的判断后,只要任意一个url出错,则整体会退出。
         *
         * 这里的exec做的是连接建立。
         *
         * exec会针对handler队列中的每一个,都进行处理。
         * */
    /* wait for performing request */
    do {
        $mrc = curl_multi_exec($mh, $running);
                #echo "curl_multi_exec pos1: $running, $mrc\n";
    } while (CURLM_CALL_MULTI_PERFORM == $mrc);

        /*
         * Comment by flykobe:
         * 这时完成3次握手,若无进一步动作,则可能会连接丢失。
         * 推测同对端web server的timeout配置时间相同。
         * 或者是之前设置的timer超时?
         * */
        #sleep(10);

    while ($running && $mrc == CURLM_OK) {
        // wait for network
        if (curl_multi_select($mh, 0.5) > -1) {
            // pull in new data;
            do {
                /*
                 * Comment by flykobe:
                 * 这里的exec做的是http请求的发送、进行从connect到completed的状态转移。
                 * */
                $mrc = curl_multi_exec($mh, $running);
                                #echo "curl_multi_exec pos2: $running, $mrc\n";
            } while (CURLM_CALL_MULTI_PERFORM == $mrc);
        }
    }

    if ($mrc != CURLM_OK) {
        error_log("CURL Data Error");
    }

    /* get data */
    $nch = 0;
    foreach ($urlarr as $moudle=>$node) {
        if (($err = curl_error($ch[$nch])) == '') {
            /*
             * Comment by flykobe:
             * 即使这里不读取,实际上,响应已经发送到本地缓存了。通过tcpdump可以看到。
             * */
            $res[$nch]=curl_multi_getcontent($ch[$nch]);
            $result[$moudle]=$res[$nch];
        }
        else
        {
            error_log("curl error");
                        echo "curl error: $err\n";
        }
        curl_multi_remove_handle($mh,$ch[$nch]);
        curl_close($ch[$nch]);
        ++$nch;
    }
}
$url_arr=array(
         'http://item.taobao.com/item.htm?id=14392877692',
         'http://item.taobao.com/item.htm?id=16231676302',
         'http://item.taobao.com/item.htm?id=17037160462',
     );
function microtime_float()
{
   list($usec, $sec) = explode(" ", microtime());
   return ((float)$usec + (float)$sec);
}
$time_start = microtime_float();
$data=curl_multi_fetch($url_arr);
$time_end = microtime_float();
$time = $time_end - $time_start;
 echo "multi timecost:{$time}\n";

php的curl_multi_exec实际上封装的是libcurl库的curl_multi_preform,在libcurl的源码中,可以看到,该方法针对stack中的每个url handler,都会调用一次到多次multi_runsingle方法,进行以下状态间的转移:

CURLM_STATE_INIT, /* 0 – start in this state */
CURLM_STATE_CONNECT, /* 1 – resolve/connect has been sent off */
CURLM_STATE_WAITRESOLVE, /* 2 – awaiting the resolve to finalize */
CURLM_STATE_WAITCONNECT, /* 3 – awaiting the connect to finalize */
CURLM_STATE_WAITPROXYCONNECT, /* 4 – awaiting proxy CONNECT to finalize */
CURLM_STATE_PROTOCONNECT, /* 5 – completing the protocol-specific connect
phase */
CURLM_STATE_WAITDO, /* 6 – wait for our turn to send the request */
CURLM_STATE_DO, /* 7 – start send off the request (part 1) */
CURLM_STATE_DOING, /* 8 – sending off the request (part 1) */
CURLM_STATE_DO_MORE, /* 9 – send off the request (part 2) */
CURLM_STATE_DO_DONE, /* 10 – done sending off request */
CURLM_STATE_WAITPERFORM, /* 11 – wait for our turn to read the response */
CURLM_STATE_PERFORM, /* 12 – transfer data */
CURLM_STATE_TOOFAST, /* 13 – wait because limit-rate exceeded */
CURLM_STATE_DONE, /* 14 – post data transfer operation */
CURLM_STATE_COMPLETED, /* 15 – operation complete */
CURLM_STATE_MSGSENT, /* 16 – the operation complete message is sent */

这里就包含了连接建立、请求发送、响应获取(到curl的缓存中)、连接关闭等。

安装步骤

  1. 安装pcre依赖库,从http://www.pcre.org/下载pcre源码包,解压
  2. 由于我机器上gcc版本过低,过需安装4.2以上版本的gcc。
  3. 安装libdrizzle,从http://agentzh.org/misc/nginx/drizzle7-2011.07.21.tar.gz下载,
    1. export CC=/home/work/local/gcc4/bin/gcc
    2. ./configure –without-server –prefix=/usr/local/drizzle ;make install-libdrizzle-1.0 # 注意,这里没有make && make install过程
    3. 安装过程中,报错如下,需要升级python。在Makefile里搜索python可见,其直接使用当前env环境的python命令,故升级后,需要修改path使其使用新版本python。import subprocess
      ImportError: No module named subprocess
  4. 安装nginx(包含ngx_drizzle模块),从http://nginx.org下载nginx源码包,./configure –prefix=/home/work/local/nginx –with-pcre=/home/chengy/src/pcre-8.32 –with-cc=/home/work/local/gcc4/bin/gcc  –with-http_drizzle_module –with-libdrizzle=/home/work/local/drizzle/; make && make install (注意,这里的–with-pcre后面跟的是pcre源码地址,而非安装后的路径)

 

这里建议直接使用ngx_openresty,是淘宝把一堆开源的nginx module集合而成的安装包。

 

GCC4.2.1安装问题

从http://gcc.gnu.org/mirrors.html下载4.2.1

./configure –disable-multilib   –prefix=/home/work/local/gcc4 ; make && make install

在安装gcc4.2.1的过程中,make时报错:

/usr/bin/ld: crti.o: No such file: 没有那个文件或目录

这时使用/usr/bin/ld -verbose查看ld加载路径,并locate crti.o,发现ld路径里也有crti.o。

这时,建立了ln -s /usr/lib64/crti.o /usr/lib/crti.o。

./configure –disable-multilib   –prefix=/home/work/local/gcc4

还得检查/etc/ld.*下是否有相应的lib库。

 

参考url:

http://zacharyhu.org/?p=25

https://github.com/agentzh/ngx_openresty/blob/master/README

  1. http://www.searchtb.com/2012/06/rolling-curl-best-practices.html Rolling cURL: PHP并发最佳实践, 淘宝PHP团队使用curl_multi_*族函数实现并发获取HTTP数据
  2. PPT,mysqlndquickoverview2011-111011164041-phpapp02:mysqlnd概览,自夸型,列举了相对于libmysql的优势
  3. PPT,mysqlndasyncipc2008-1225229964241711-8: SUN开发团队,分享mysqlnd的异步请求示例
  4. http://blog.zoomquiet.org/pyblosxom/2012/03/ 由Lua 粘合的Nginx生态环境
  5. http://www.londonlua.org/scripting_nginx_with_lua/slides.html     scripting nginx by lua
  6. http://wiki.nginx.org/HttpLuaModule  ngx_lua模块手册
  7. http://www.lua.org/docs.html lua官方文档
  8. http://nginx.2469901.n2.nabble.com/ 论坛
  9. http://blog.csdn.net/chosen0ne/article/details/7304192  使用ngx_lua构建高并发应用
  10. http://www.slideshare.net/joshzhu/nginx-internals
  11. http://blog.sina.com.cn/openresty 章亦春的blog

openresty:把nginx变成应用服务器

2011年12月23日 下午 54:57 | 作者:hemon

http://openresty.org

  1. 安装基础库
    apt-get install libreadline-dev libpcre3-dev libssl-dev perl
  2. 安装libdrizzle
    wget http://agentzh.org/misc/nginx/drizzle7-2011.07.21.tar.gz
    tar xzvf drizzle7-2011.07.21.tar.gz
    cd drizzle7-2011.07.21/
    ./configure –without-server
    make libdrizzle-1.0
    make install-libdrizzle-1.0
  3. 安装openresty
    wget http://agentzh.org/misc/nginx/ngx_openresty-1.0.10.24.tar.gz
    tar xzvf ngx_openresty-1.0.10.24.tar.gz
    ./configure –with-luajit –with-http_drizzle_module
    make -j2 # 2是CPU双核,单核就直接 make
    make install
  4. 安装目录:/usr/local/openresty,写程序就是改nginx配置
    /usr/local/openresty/nginx/conf/nginx.conf
  5. 怎么用nginx.conf写程序呢?http://agentzh.org/misc/slides/nginx-conf-scripting/

    http://agentzh.org/misc/slides/recent-dev-nginx-conf/

    http://agentzh.org/misc/slides/nginx-state-of-the-art/

    http://agentzh.org/misc/slides/perl-lz-apps/

     

    zz from: http://www.hemono.com/?p=601

mysql对字符集的支持有哪些种类,与php有什么区别,mysql各版本间有什么区别?

 

Mysql字符集设置概览

字符集和校对规则有4个级别的默认设置:服务器级、数据库级、表级和连接级。针对每一列,甚至每一个字符串(select _latin1 ‘string’),mysql也可以指定其编码,但是一般不用。其中,服务器、数据库、表、列级都是相对静态的,而连接级相对动态。

数据库支持的编码:

mysql> SHOW CHARACTER SET;
+———-+—————————–+———————+——–+
| Charset | Description | Default collation | Maxlen |
+———-+—————————–+———————+——–+
| dec8 | DEC West European | dec8_swedish_ci | 1 |
| cp850 | DOS West European | cp850_general_ci | 1 |
| hp8 | HP West European | hp8_english_ci | 1 |

……
+———-+—————————–+———————+——–+
28 rows in set (0.00 sec)

针对同一种编码,可以有多种比较规则:

mysql> SHOW COLLATION where Charset = ‘utf8′;
+——————–+———+—–+———+———-+———+
| Collation | Charset | Id | Default | Compiled | Sortlen |
+——————–+———+—–+———+———-+———+
| utf8_general_ci | utf8 | 33 | Yes | Yes | 1 |
……
+——————–+———+—–+———+———-+———+
21 rows in set (0.00 sec)

与编码和比较相关的配置项:

mysql> show variables like ‘%character%';
+————————–+—————————————————+
| Variable_name | Value |
+————————–+—————————————————+
| character_set_client | utf8 |     ; 客户端发送的查询中使用的字符集
| character_set_connection | utf8 | ; 将客户端发送的查询从character_set_client系统变量转换到character_set_connection(除非字符串文字具有象_latin1或_utf8的引介词)

| character_set_database | gbk | ; 默认数据库的字符集
| character_set_filesystem | binary |
| character_set_results | utf8 | ; 服务器返回查询结果到客户端使用的字符集。包括结果数据,例如列值和结果元数据(如列名)。
| character_set_server | gbk | ; 当前的服务器字符集
| character_set_system | utf8 |
| character_sets_dir | /home/mysql/local/mysql6603/share/mysql/charsets/ |
+————————–+—————————————————+
8 rows in set (0.00 sec)

mysql> show variables like ‘%collation%';
+———————-+—————–+
| Variable_name | Value |
+———————-+—————–+
| collation_connection | utf8_general_ci |
| collation_database | gbk_chinese_ci |
| collation_server | gbk_chinese_ci |
+———————-+—————–+
3 rows in set (0.00 sec)

连接级编码,引mysql5.1参考手册如下,并略作调整:

一些字符集和校对规则系统变量与客户端和服务器的交互有关。

在客户端和服务器的连接处理中也涉及了字符集和校对规则变量。每一个客户端有一个连接相关的字符集和校对规则变量。

考虑什么是一个“连接”:它是连接服务器时所作的事情。客户端发送SQL语句,例如查询,通过连接发送到服务器。服务器通过连接发送响应给客户端,例如结果集。对于客户端连接,这样会导致一些关于连接的字符集和 校对规则的问题,这些问题均能够通过系统变量来解决:

·         当查询离开客户端后,在查询中使用哪种字符集?

服务器使用character_set_client变量作为客户端发送的查询中使用的字符集。

·         服务器接收到查询后应该转换为哪种字符集?

转换时,服务器使用character_set_connection和collation_connection系统变量。它将客户端发送的查询从character_set_client系统变量转换到character_set_connection(除非字符串文字具有象_latin1或_utf8的引介词)。collation_connection对比较文字字符串是重要的。对于列值的字符串比较,它不重要,因为列具有更高的 校对规则优先级。

·         服务器发送结果集或返回错误信息到客户端之前应该转换为哪种字符集?

character_set_results变量指示服务器返回查询结果到客户端使用的字符集。包括结果数据,例如列值和结果元数据(如列名)。

根据上文可知,与连接相关的config是character_set_client、character_set_connection、character_set_results。一般,php代码里会通过mysql_set_charset之类的函数,设置希望的连接编码。

混杂字符集的弊端

在文章一开头,提到过,虽然mysql能够对列、字符串设置编码,但是一般不用。理由很简单,这样会造成不必要的困扰。比如在做字符串比较时,不同编码会很复杂。各种字符串操作的函数如果同时涉及不同编码,也会很复杂。而这些,个人认为,不应该加诸于mysql之上,而应该会从架构层面消除。

Mysql UTF8以及陷阱

在记忆中,utf8是变长1-4字节,但是mysql里却明确规定最大为3字节。查询其手册页可见,即mysql的实现中,不支持四字节,这是与php有区别的地方。

UTF8字符集(转换Unicode表示)是存储Unicode数据的一种可选方法。它根据 RFC 3629执行。UTF8字符集的思想是不同Unicode字符采用变长字节序列编码:

·         基本拉丁字母、数字和标点符号使用一个字节。

·         大多数的欧洲和中东手写字母适合两个字节序列:扩展的拉丁字母(包括发音符号、长音符号、重音符号、低音符号和其它音符)、西里尔字母、希腊语、亚美尼亚语、希伯来语、阿拉伯语、叙利亚语和其它语言。

·         韩语、中文和日本象形文字使用三个字节序列。

RFC 3629说明了采用一到四个字节的编码序列。当前,MySQLUTF8不支持四个字节。(UTF8编码的旧标准是由RFC 2279给出,它描述了从一到六个字节的UTF8编码序列。RFC 3629补充了作废的RFC 2279;因此,不再使用5个字节和6个字节的编码序列。)

提示:使用UTF8时为了节省空间,使用VARCHAR而不要用CHAR。否则,MySQL必须为一个CHAR(10) CHARACTER SET utf8列预备30个字节,因为这是可能的最大长度。

这里需要注意的是,length和char_length的结果会有区别(utf8和gbk等都有该问题),比如,针对一张gbk编码的表:

mysql> select username, length(username), char_length(username) from user where userid = 1 or userid = 2;
+————+——————+———————–+
| username | length(username) | char_length(username) |
+————+——————+———————–+
| engname | 7 | 7 |
| 我真是汉字 | 10 | 5 |
+————+——————+———————–+

可见,length返回的是它真实占用的存储字节数,而char_length是字符数。在php中同样有这些问题,strlen的结果和mb_strlen的结果就是不相同的。

还有一个需要注意的点,在作为索引列时,会使用length的长度。并且如果一个索引使用了utf8编码的列,那么mysql会假设所有的字符都是3字节的,即使它其实是1-3字节!所以,索引的长度限制会变成平常的1/3!

 

参考URL:

http://dev.mysql.com/doc/refman/5.1/zh/charset.html

http://php.net/manual/zh/mysqlinfo.concepts.charset.php

http://php.net/manual/zh/function.mysql-set-charset.php

java环境搭建

hadoop是基于java的,所以必须首先安装jdk。

1. 下载jdk bin,chmod a+x jdk-6u34-ea-bin-b03-linux-amd64-20_jun_2012.bin

2. ./jdk-6u34-ea-bin-b03-linux-amd64-20_jun_2012.bin 执行,即可生成jdk1.6.0_34目录

3. 将jdk1.6.0_34目录mv到合适的位置

4. 在~/.bash_profile或者/etc/profile里配置java环境:

# java env
JAVA_HOME=/home/chengy/local/jdk1.6.0_34
PATH=$JAVA_HOME/bin:$PATH
CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
export JAVA_HOME PATH CLASSPATH

hadoop多机版搭建

单机版很简单,直接参考下面的url即可。多机版需要配置免密钥的ssh,可以参考我的blog

hadoop中有4种服务器角色,官方定义请自行google,我的浅薄理解如下:

1. namenode:存储的调度节点。

2.jobtracker:job的调度节点。

3.datanode:真正存储数据的节点。

4.tasktracker:真正干活的job节点。

一般会使用一台服务器作为namenode,一台作为jobtracker,其余服务器作为slaves,同时部署datanode和tasktracker。但是我目前只有两台服务器,所以使用其中一台A作为namenode、datanode和tasktracker。另一台B作为jobtracker、datanode和tasktracker。需要指出的是,其实这些服务器的部署是可以完全一致的。

解压安装完之后,需要在hadoop/conf/hadoop-env.sh中设置export JAVA_HOME=/path/to/jdk。

另外还最少需要配置以下参数:

$ cat core-site.xml
<?xml version=”1.0″?>
<?xml-stylesheet type=”text/xsl” href=”configuration.xsl”?>

<!– Put site-specific property overrides in this file. –>

<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://hostnameA:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/home/chengy/local/hadoopdata/tmp/${user.name}</value>
</property>
</configuration>

[chengy@yf-dba-darwin-uc-mongodb00 conf]$ cat mapred-site.xml
<?xml version=”1.0″?>
<?xml-stylesheet type=”text/xsl” href=”configuration.xsl”?>

<!– Put site-specific property overrides in this file. –>

<configuration>
<property>
<name>mapred.job.tracker</name>
<value>hostnameB:9001</value>
</property>
<property>
<name>mapred.system.dir</name>
<value>/home/chengy/local/hadoopdata/system/${user.name}</value>
</property>
<property>
<name>mapred.local.dir</name>
<value>/home/chengy/local/hadoopdata/local01/${user.name},/home/chengy/local/hadoopdata/local02/${user.name}</value>
</property>
<property>
<name>mapred.tasktracker.map.tasks.maximum</name>
<value>8</value>
</property>
<property>
<name>mapred.tasktracker.reduce.tasks.maximum</name>
<value>8</value>
</property>
</configuration>

[chengy@yf-dba-darwin-uc-mongodb00 conf]$ cat hdfs-site.xml
<?xml version=”1.0″?>
<?xml-stylesheet type=”text/xsl” href=”configuration.xsl”?>

<!– Put site-specific property overrides in this file. –>

<configuration>
<property>
<name>dfs.name.dir</name>
<value>/home/chengy/local/hadoopdata/name/${user.name}</value>
</property>
<property>
<name>dfs.data.dir</name>
<value>/home/chengy/local/hadoopdata/data/${user.name}</value>
</property>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
</configuration>

$ cat slaves
hostnameA
hostnameB

初始化和启动、停止

以上conf配置完毕后,可以推送到每台hadoop服务器上,采取同构部署。

这时,需要初始化namenode:在namenode上,执行bin/hadoop fs -format,如果之前有初始化信息,这时会询问是否要覆盖,如果选择覆盖的话,之前的信息就没了。

启动分两部分:

1. 在namenode上,执行 bin/start-dfs.sh

2. 在jobtracker上,执行bin/start-mapred.sh

停止也一样:

1. 在namenode上,执行 bin/stop-dfs.sh

2. 在jobtracker上,执行bin/stop-mapred.sh

如果出问题,可以查看log,默认在hadoop/logs下面(log很多,建议在异常时tail -f * )。

参考url:

http://hadoop.apache.org/docs/r0.19.2/cn/quickstart.html 官方单机和单机多线程hadoop搭建guide

http://hadoop.apache.org/docs/r0.19.2/cn/cluster_setup.html 官方分布式hadoop搭建guide

http://hadoop.apache.org/releases.html#Download hadoop官方下载(直接下载了bin版,解压即可)

http://ecmp.baidu.com/page/site/vsopnewbit/document-details?nodeRef=workspace://SpacesStore/6106e412-a165-4848-bbce-134bdde0fb30 (内网,也是单机版安装guide)

http://blog.linezing.com/2011/05/hadoop%E8%B6%85%E7%BA%A7%E5%AE%89%E8%A3%85%E6%89%8B%E5%86%8C

The goal of caching in HTTP/1.1 is to eliminate the need to send requests in many cases, and to eliminate the need to send full responses in many other cases. 即通过“过期”机制直接在浏览器端搞定,减少交互;或者“验证”机制,不发送完整响应,从而减少带宽占用。

协议里,针对透明度有一些折中,在我们的应用场景下,初期可以不考虑,但是需要明确指定no-cache或者明确的cache机制。但是,这里的透明性和非透明性操作指什么呢?

http的cache机制中,有3种角色:客户端、服务器端、缓存服务器(cache)。在某些中文翻译里,将原协议中的cache翻译为缓存服务器,但是个人觉得,这里所指的cache,完全不同于web架构中的缓存服务器(squid等),而是浏览器等客户端实现中的缓存组件。

 

If an origin server wishes to force a semantically transparent cache to validate every request, it MAY assign an explicit expiration time in the past. This means that the response is always stale, and so the cache SHOULD validate it before using it for subsequent requests. See section 14.9.4 for a more restrictive way to force revalidation.

If an origin server wishes to force any HTTP/1.1 cache, no matter how it is configured, to validate every request, it SHOULD use the “must- revalidate” cache-control directive (see section 14.9).

这两段,提到expiration model中如何强制客户端验证每一个请求。可以通过设置an explicit expiration time in the past“建议”验证,也可以通过cache-control指令,“force”验证。在我们的场景下,我认为应该使用后者,保证不该被cache的内容,绝对每次都被验证。

Servers specify explicit expiration times using either the Expires header, or the max-age directive of the Cache-Control header.与明确的过期时间相对的,还有Heuristic Expiration(启发式过期),即通过last-modified time等推测一个可能的过期时间。

expiration model涉及两种时间:cache age和freshness lifetime。cache age的计算涉及到Date(response header), Age(如果中间有代理服务器,则可能设置该header)。这里cache age的计算,有一些晕,存疑。freshness lifetime的计算涉及Expire,Cache-Control: max-age, or Cache-Control: s- maxage,该值的计算都是以server端时间为准的,不依赖与客户端的本地时间。判断一个缓存是否过期,只需要比较cache age 和 freshness lifetime即可。

在expiration model里,server端只需要提供expire time or max-age,缓存是否过期由client端判断。

 

另一种是validatation model,这里有强弱之分。我们的应用当使用强验证,即任何内容的改变都会导致缓存失效。baidu首页是有etag的,但明显它是分布式server,那么使用规则生成的etag呢?在这种model里,server端需要生成etag和last-modified time,并在接收到request header里的if-modified-since,if-match等字段时,比较缓存是否失效,从而选择发送304或者完成的body。

 

需要确定几个名词:

user agent: The client which initiates a request. These are often browsers, editors, spiders (web-traversing robots), or other end user tools. (例如浏览器,SDK)

server: An application program that accepts connections in order to service requests by sending back responses. Any given program may be capable of being both a client and a server; our use of these terms refers only to the role being performed by the program for a particular connection, rather than to the program’s capabilities in general. Likewise, any server may act as an origin server, proxy, gateway, or tunnel, switching behavior based on the nature of each request.

origin server: The server on which a given resource resides or is to be created.(例如web server)

proxy: An intermediary program which acts as both a server and a client for the purpose of making requests on behalf of other clients. Requests are serviced internally or by passing them on, with possible translation, to other servers. A proxy MUST implement both the client and server requirements of this specification. A “transparent proxy” is a proxy that does not modify the request or response beyond what is required for proxy authentication and identification. A “non-transparent proxy” is a proxy that modifies the request or response in order to provide some added service to the user agent, such as group annotation services, media type transformation, protocol reduction, or anonymity filtering. Except where either transparent or non-transparent behavior is explicitly stated, the HTTP proxy requirements apply to both types of proxies.

cache: A program’s local store of response messages and the subsystem that controls its message storage, retrieval, and deletion. A cache stores cacheable responses in order to reduce the response time and network bandwidth consumption on future, equivalent requests. Any client or server may include a cache, though a cache cannot be used by a server that is acting as a tunnel.(浏览器缓存、代理缓存等)

当前需要做的3件事:

1. 对于非缓存的内容,明确指明 cache-control: no-store,禁止client缓存

2.支持expire模式

3.支持etag+last modified模式