2014年7月1日星期二

Mysql中查询相关优化分析

无意中看到一个SQL的对比分析,关于独立子查询和相关子查询对最终结果集的耗时对比。主要的SQL集中在in (select ...)下面分析一下此类SQL优化的原理和方法;

子查询

当一个查询是另外一个查询的条件时,此时称之为子查询;
子查询又分为独立子查询和相关子查询,顾名思义,独立子查询表示该子查询可以独立完成,不依赖于外部变量;而相关子查询则依赖于外部数据传入;

慢查询日志

Mysql可以通过修改/etc/my.cnf来设定慢查询日志。修改my.cnf文件:
slow_query_log = 1
slow_query_log_file = /tmp/mysqlslow.log
long_query_time = 0.0001
log-queries-not-using-indexes
log-slow-admin-statements
log-slow-slave-statements
然后重启mysql
#>service mysqld restart
我们设定的超时时间是0.0001秒,当查询超过这个时间就会打印慢查询日志。

实例

查询一张表,查询出每月最后一天的数据并展示;
我们可能第一时间写出的sql类似于这样:
SELECT * FROM orders where date in (select max(date) from orders group by substring(orders.date, -10, 7));
注意,这个sql语句用到了in,按照我们自然的想法,我们会认为这个sql将会先执行in后面的子查询,再执行外部查询。但实际上是这样吗?
我们用explain解释一下这个sql,返回:
mysql> explain SELECT * FROM orders where date in (select max(date) from orders group by substring(orders.date, -10, 7));
+----+--------------------+----------+------+---------------+------+---------+------+------+---------------------------------+
| id | select_type        | table    | type | possible_keys | key  | key_len | ref  | rows | Extra                           |
+----+--------------------+----------+------+---------------+------+---------+------+------+---------------------------------+
|  1 | PRIMARY            | orders   | ALL  | NULL          | NULL | NULL    | NULL |   93 | Using where                     |
|  2 | DEPENDENT SUBQUERY | orders   | ALL  | NULL          | NULL | NULL    | NULL |   93 | Using temporary; Using filesort |
+----+--------------------+----------+------+---------------+------+---------+------+------+---------------------------------+
我们看到后面的子查询是一个DEPENDENT SUBQUERY,也就是一个相关子查询。
原来,在mysql执行这个sql语句时,该语句被翻译为下面的语句执行:
SELECT * FROM orders where date exists (select max(date) from orders group by substring(orders.date, -10, 7) having `max(date)`=date);
解释之后是作为一个相关子查询来执行,假设外部查询的时间复杂度是O(M), 相关子查询的时间复杂度是O(N), 则此查询的时间复杂度不是O(M+N)而是O(M+M*N)
通过慢查询日志我们也可以看到该子查询的逻辑查询次数:
# Time: 140701 15:28:05
# User@Host: root[root] @ localhost []
# Query_time: 0.015520  Lock_time: 0.000162 Rows_sent: 4  Rows_examined: 9480
SET timestamp=1404199685;
SELECT * FROM orders where date in (select max(date) from orders group by substring(orders.date, -10, 7));
我们当前的表行数只有93行,但是这个sql执行时逻辑查询次数竟然达到了9480次。因此in的效率很低,最好尽量避免使用in语句。

优化

可以通过降低相关子查询表的条数来降低整体逻辑查询次数,例如这个sql可以优化为:
SELECT * FROM orders A where exists (select * from (select max(date) from orders group by substring(orders.date, -10, 7)) B where B.`max(date)`=A.date);
解释这个sql可以看到:
+----+--------------------+------------+------+---------------+------+---------+------+------+---------------------------------+
| id | select_type        | table      | type | possible_keys | key  | key_len | ref  | rows | Extra                           |
+----+--------------------+------------+------+---------------+------+---------+------+------+---------------------------------+
|  1 | PRIMARY            | A          | ALL  | NULL          | NULL | NULL    | NULL |   93 | Using where                     |
|  2 | DEPENDENT SUBQUERY |  | ALL  | NULL          | NULL | NULL    | NULL |    4 | Using where                     |
|  3 | DERIVED            | youku_tj   | ALL  | NULL          | NULL | NULL    | NULL |   93 | Using temporary; Using filesort |
+----+--------------------+------------+------+---------------+------+---------+------+------+---------------------------------+
相关子查询的行数只有4行了。大大降低了逻辑查询次数,从慢查询日志也反映了这个改变:
# Time: 140701 15:27:27
# User@Host: root[root] @ localhost []
# Query_time: 0.000915  Lock_time: 0.000201 Rows_sent: 4  Rows_examined: 560
SET timestamp=1404199647;
SELECT * FROM orders A where exists (select * from (select max(date) from orders group by substring(orders.date, -10, 7)) B where B.`max(date)`=A.date);
因此当通过explain查看sql的执行效率时,要注意DEPENDENT SUBQUERY对效率的影响,从两个SQL的执行效率看,改进后的执行之间是0.00915s,改进之前是0.015520s 还是影响比较大的,尤其对行数很大的表影响尤其明显;

2014年6月26日星期四

Nginx中虚拟主机配置原理

背景

今天有一个需求,就是想让指定HOST的http请求可以访问某个服务,但是其他HOST过来的请求无法使用HTTP协议; 查了一下NGINX中关于虚拟主机的配置信息,Nginx中关于虚拟主机的配置原理是这样的,假设现在配置了三个server,例如:
server {
    listen      80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      80;
    server_name example.net www.example.net;
    ...
}

server {
    listen      80;
    server_name example.com www.example.com;
    ...
}
这三个虚拟主机配置,server_name限定了访问的HOST,当example.org,example.net,example.com的host请求来临时,会访问到对应的server下的配置服务上。 当然,如果来了一个localhost,或者其他的HOST请求时,此时会访问到第一个server配置,Nginx中会默认第一个server配置为default配置。 当然,可以通过default_server来指定默认服务配置是哪一个,例如:
server {
    listen      80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      80 default_server;
    server_name example.net www.example.net;
    ...
}

server {
    listen      80;
    server_name example.com www.example.com;
    ...
}
此时会使用第二个配置作为默认配置; (注:default_server从Nginx 0.8.2以后的版本有效,以前使用default关键字) OK,了解这个原理之后,上面的需求也变得非常简单了,只需要在default配置中不配置指定的服务,而将服务proxy配置到对应的虚拟主机下即可达到效果;

深入配置

如何限制不带HOST的请求?可以通过如下配置:

server {
    listen       80;
    server_name  "";
    return       444;
}
如何监听某一个IP地址上的端口?
server {
    listen      192.168.1.1:80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      192.168.1.1:80 default_server;
    server_name example.net www.example.net;
    ...
}

server {
    listen      10.10.1.192:80 default_server;
    server_name example.com www.example.com;
    ...
}
上面的配置展示了监听指定IP地址+端口上的请求,当一个来自192.168.1.1:80端口上的请求来临时,如果match到了配置的HOST会按虚拟主机配置的服务进行中转,没有match则会走default配置。
值得注意的是,不同ip+端口可以有自己的default_server.
所以当配置一个内网请求的时候,可以通过指定监听IP地址的方式,通过监听内网IP+port上的请求来达到限制内网访问的目的。

2014年6月18日星期三

[翻译整理]Apache HBase - HFile

简介

Apache HBase是Hadoop的分布式开源的存储管理工具,非常适合随机实时的io操作。
我们知道,Hadoop的Sequence File是一个顺序读写,批量处理的系统。但是为什么HBase能做到随机的,实时的io操作呢?
Hadoop底层使用Sequence File文件格式存储,Sequence File允许以追加的方式增加k-v数据,根据hdfs的append-only的特性,Sequence File不允许修改或删除一个指定的数据。只有append操作是被允许的,而且你想要查找某个key,你只能遍历文件,知道找到这个key为止。
所以,在这个文件格式至上搭建的HBase系统,是如何搭建随机读写,低访问延迟的呢?
上文中提到了MapFile,是一个以Sequence File为基础的文件格式。MapFile实际上是由两个Sequence File组成的,/data存储了数据,/index存储了索引。
MapFile提供了一个按顺序存储的方式,每当N(N是可配置的)条记录写入后,会将文件偏移地址写入index文件;这就实现了快速查找,相比直接遍历Sequence File,你可以直接遍历index文件,index文件存储了更少的文件句柄,一旦找到了文件块所在的位置,可以直接跳到该文件块查找;因此查找速度非常快。
但是还有另外两个问题:
1. 如何删除或更新一个k-v记录;
2. 当插入数据不是有序的,如何使用MapFile

HBase&MapFile


HBase的key包括:行键,列族,column qualifier,时间戳,属性。
为了解决删除的问题,使用key中的type标记该记录是否被删除。
解决更新的问题,实际上是获取时间戳更晚的记录,正确的数据总会更接近文件的末尾。
为了解决无序数据的问题,hbase将插入的数据暂存在内存中,直到一个阈值到达,hbase会将内存中的数据存储到MapFile中。
hbase在内存中使用sorted ConcurrentSkipListMap数据结构存储数据,每次到达阈值(hbase.hregion.memstore.flush.size)或到达内存上限(hbase.regionserver.global.memstore.upperLimit),都会将内存中的数据向一个新的mapFile写入。
由于每次flush操作都会写入一个新的MapFile,这就意味着查找时,会横跨多个文件,这也会耗费更多的资源和时间。
为了避免在查找get和scan扫描时横跨过多的文件,hbase中有一个线程来执行文件合并的相关操作。当线程发现文件数量达到阈值(hbase.hstore.compaction.max)后,会有一个compaction进程来执行文件合并,将小的文件合并到一个大文件中。
hbase中有两种合并模式,minor和major。minor会合并两个或多个较小的文件到一个大文件中。另外的major会合并所有的文件到一个文件中,并且执行一些清理,被删除的数据将不会被写入新的文件,并且重复的数据会被清除,只留下最新的有效数据。
0.20以下版本的hbase使用上述的文件存储方式,到0.20以后,hfile v1被引入替代了MapFile。

HFile v1

在HBase 0.20版本中,一个新的文件存储格式被引入到hbase中-hfile。hfile和mapfile非常类似,但是一些新的特性被添加进来。例如支持元数据,index也被保存到同一个文件中。

数据块(data blocks)中存储key-values,可以看做是一个MapFile。
当block关闭操作时,第一个key会被写入index中,index文件在hfile关闭操作时写入。
Hfile还增加两个额外的元数据类型,Meta和FileInfo。这两个数据也是在hfile的close时被写入的。
HBase的Regionserver的存储文件中,使用Meta Block存储了BloomFilter,使用FileInfo存储了最大的SequenceId,Major compaction key and 时间跨度信息。
这些信息在旧文件或非常新的文件中判断key是否存在非常有用。
关于BloomFilter,是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。

HFile v2

在hbase 0.92版本中,为了改进在大数据存储下的效率,HFile做了改变。
hfile v1的主要问题是,你需要load所有的单片索引和BloomFilter到内存中;
为了解决这个问题,v2引入了多级索引和分块BloomFilter;HFile v2改进了速度,内存和缓存利用率。
v2的最主要特性是内联块。主要思想是拆分了索引和BloomFilter到每个数据块中,来解决整个文件的索引和BloomFilter都load到内存中的问题;
因为索引被拆分到每个数据块中,这就意味着每个数据块都有自己的索引(leaf-index);每个数据块中的最后一个key被当做节点组建了类似b+树的多级索引结构;
数据块的头信息中的Block Magic被Block Type替换,Block Type包含描述Block数据的一些信息,包括叶子索引,Bloom,元数据,根索引等。
其他三个字段(compressed/uncompressed size and offset prev block)也被添加用于前后的快速查找;

  • BlockType:8字节长度(long),支持的类型包括
    • 数据块(data blocks)
    • LEAF_INDEX
    • BLOOM_CHUNK
    • META
    • INTERMEDIATE_INDEX
    • ROOT_INDEX
    • FILE_INFO
    • BLOOM_META
    • TRAILER
    • INDEX_V1
  • 压缩大小,不包括header。可以在扫描的时候略过本块数据;
  • 非压缩大小,当压缩算法为NON的时候,和压缩大小相同
  • 同一类型上一块的block偏移地址,可以帮助搜索上一数据块/索引块的地址;
  • 压缩的数据(如果压缩算法为NON,则是非压缩数据)

DataBlock编码


因为大部分的key都非常类似,因此要设计一个良好的压缩方式;HBase0.94开始可以在前缀编码和diff编码做选择;
前缀编码的特点是相同的前缀只存储一次;
Diff编码把前缀编码做到了极致;相同的项将会被省略存储,例如key len,如果两个key len相同,第二个将被省略;而对于递增的时间戳,只存储了相对于上一个时间戳的变化量;
注意,这样的存储格式是默认关闭的,因为将会导致查询变慢,但是更多的数据可以加载到cache中。通过修改表的ATA_BLOCK_ENCODING = PREFIX | DIFF | FAST_DIFF 来设置使用的存储方式;

HFile v3

 HBASE-5313包含了一个重新变更HFile格式以提高数据压缩率的提议。
原文地址:http://blog.cloudera.com/blog/2012/06/hbase-io-hfile-input-output/

[翻译]Hadoop中的SequenceFile

【原文】http://blog.cloudera.com/blog/2011/01/hadoop-io-sequence-map-set-array-bloommap-files/ hadoop中的SequenceFile提供了一种持久存储二进制k-v键值对的数据结构。和B-tree不同,SequenceFile不能支持对指定key的修改,增加或删除。整个文件只能以追加的方式写入数据。 SequenceFile有三种存储格式:非压缩格式,记录压缩格式和分块压缩格式; 每种格式都包含一个Header,这个Header可以帮助读取方识别存储格式; 1. 包括key值和value的Class信息,读取方可以通过反射机制实例化这些类。 2. 版本 version number 3. 存储数据的格式,如果是压缩的格式,则还会提供压缩编码的class name信息; SequenceFile还可以包含一个二级k-v结构的列表,作为整个SequenceFile的metadata; SequenceFile的元数据信息中只能存储text-text类型的数据,元数据是在SequenceFile的Writer初始化的时候被写入的,因此元数据是不能被更改的。 元数据(摘自维基百科):其它文件保存信息常常伴随着文件自身保存在文件系统中。文件长度可能是分配给这个文件的区块数,也可能是这个文件实际的字节数。文件最后修改时间也许记录在文件的时间戳中。有的文件系统还保存文件的创建时间,最后访问时间及属性修改时间。(不过大多数早期的文件系统不记录文件的时间信息)其它信息还包括文件设备类型(如:区块数,字符集,套接口,子目录等等),文件所有者的ID,组ID,还有访问权限(如:只读,可执行等等)。 SequenceFile的文件存储有三种格式,其中非压缩和记录压缩两个格式非常类似;调用append方法来添加记录时,会append一个记录(record)到SequenceFile,Record包括: 整体record的长度(key长度+value长度);key长度;key;value;(key,value都是raw格式数据) 记录压缩和非压缩的区别仅在于,value是否是被压缩的,并且压缩时会提供一个压缩编码类; Block压缩格式相对比较复杂;数据在达到一个阈值前不会被写入,当达到阈值之后,所有的key和value都会被集中压缩,并生成keys的长度的list以及values的长度;两个块记录中间存在一个同步标记(sync marker) 如上图,每个块记录会记录buffer的记录数,还包含key长度的列表,keys列表,value长度列表,value列表; Hadoop的SequenceFile是其他文件的基础,例如:MapFile, SetFile, ArrayFile and BloomMapFile. 关于Sync标记: a sync point is a point in the stream which can used by to resynchronize with a record boundary if the reader is "lost" - for example after seeking to an arbitrary position on the stream. 其他: MapFile:实际上是一个有序的sequenceFile。它是一个包含两个sequenceFile的目录,data文件(/data)和index文件(/index); data包含所有的k-v记录,但是key(n+1)必须大于等于key(n), 当进行append()操作时会检查,如果检查失败,会返回一个IOException “Key out of order”.
index记录了所有key值和记录的偏移位置;index的数据会被加载到内存中,通过存储的映射关系快速定位到文件中的记录; 需要注意的是,index不会将所有的key都加载到内存中,默认情况,没个128条记录会有一条index的映射值存储到内存中,可以通过setIndexInterval来改变这个数值; SetFile和ArrayFile都是基于MapFile的,通过增加很少的几行代码实现的。 setFile只会append(key), value是一个NullWritable 的实例; ArrayFile只会append(value), key是一个LongWritable实例的记录编号,内容是count+1; BloomFile实现了MapFile,并且增加了另外一个bloom file(/bloom), 这个文件中存储的是序列化的DynamicBloomFilter变量,存储了新增加的key值;

2013年10月12日星期六

使用Imagemagick和Jmagick拼接图片并打水印

安装

注:本文参考系统:2.6.32-279.el6.x86_64, CentOS6.x
首先需要安装jpeg库和freetype库来支持imagemagick安装。
1. 安装libjpeg
2. 安装Imagemagick,执行configure
#>./configure --without-perl
执行之后查看configure结果
Shared libraries  --enable-shared=yes           yes
Static libraries  --enable-static=yes           yes
Module support    --with-modules=yes            yes
GNU ld            --with-gnu-ld=yes             yes
Quantum depth     --with-quantum-depth=16       16
High Dynamic Range Imagery
                  --enable-hdri=no              no

Delegate Configuration:
BZLIB             --with-bzlib=yes              no
DJVU              --with-djvu=no                no
DPS               --with-dps=yes                no
FlashPIX          --with-fpx=yes                no
FontConfig        --with-fontconfig=no          no
FreeType          --with-freetype=yes           yes
GhostPCL          None                          pcl6 (unknown)
GhostXPS          None                          gxps (unknown)
Ghostscript       None                          gs (8.70)
result_ghostscript_font_dir='none'
Ghostscript fonts --with-gs-font-dir=default
Ghostscript lib   --with-gslib=yes              no
Graphviz          --with-gvc=yes                no
JBIG              --with-jbig=yes               no
JPEG v1           --with-jpeg=yes               yes
JPEG-2000         --with-jp2=yes                no
LCMS              --with-lcms=yes               no
LQR               --with-lqr=no         no
Magick++          --with-magick-plus-plus=yes   yes
OpenEXR           --with-openexr=yes            no
PERL              --with-perl=no                no
PNG               --with-png=yes                no
RSVG              --with-rsvg=no                no
TIFF              --with-tiff=yes               no
result_windows_font_dir='none'
Windows fonts     --with-windows-font-dir=
WMF               --with-wmf=yes                no
X11               --with-x=                     no
XML               --with-xml=no         no
ZLIB              --with-zlib=yes               yes
发现JPEG和FreeType已经是yes
然后make && make install
此时可以使用命令测试:
#>montage test.jpg test1.jpg test2.jpg -tile 3X1 -geometry 480x360+0+0 output.jpg
如果出现:sh:gs not found 安装yum install ghostscript
3. 安装Jmagick-6.4.0
安装结束后,拷贝jmagick-6.4.0.jar到$JAVA_HOMT/jre/lib/ext下,将jmagick-6.4.0.so拷贝到$JAVA_HOME/jre/lib/amd64下
4. 执行java程序,拼接图片,发现会有fatal error出现:反复测试发现setGeometry之后获取getGeometry出现数据不一致。
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f0d20471889, pid=11404, tid=139694626883328
#
# JRE version: 6.0_37-b06
# Java VM: Java HotSpot(TM) 64-Bit Server VM (20.12-b01 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [libMagickCore.so.1+0x131889]  imaginary long double+0x39
#
# An error report file with more information is saved as:
# /opt/deploy/papaq/papaq-pic/hs_err_pid11404.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.


或

java: magick/montage.c:425: MontageImageList: Assertion `montage_info->signature == 0xabacadabUL' failed. 

此时需要查看JMagick源码,实现在src/magick/jmagick.h的setstringmethod,此处set值可能有问题,重写实现代码,实现类:src/magick/magick_Montage.c,重写方法:
setStringMethod(Java_magick_MontageInfo_getGeometry,
  geometry,
  "montageInfoHandle",
  MontageInfo)

JNIEXPORT void JNICALL Java_magick_MontageInfo_setGeometry
(JNIEnv *env, jobject self, jstring geometryValue)
{
    MontageInfo *montageInfo = NULL;
    const char *cstr = NULL;
    
    montageInfo = (MontageInfo*) getHandle(env, self,
                                           "montageInfoHandle", NULL);
    
    if (montageInfo == NULL) {
        throwMagickException(env, "Unable to obtain MontageInfo handle");
        return;
    }
    
    cstr = (*env)->GetStringUTFChars(env, geometryValue, 0);
    strcpy(montageInfo->geometry, cstr);
    (*env)->ReleaseStringUTFChars(env, geometryValue, cstr);

}
重新编译,替换之前的so文件之后再测试,测试通过;

2013年4月25日星期四

使用Git(GitLab)和Maven向Nexus发布Release

前置工具

Git配合GitLab;使用Nexus Sonatype管理本地maven仓库。

Maven主要命令

mvn release:prepare #设定本次版本信息和接下来的开发版本信息,并准备提交用到的一些jar文件。
mvn release:prepare -Dresume=false #重新开始prepare操作,忽略之前的release设置
mvn release:perform #发布release到maven仓库

Maven主要配置

<scm>
   <connection>scm:git:git@serverIp:root/project.git</connection>
   <url>scm:git:git@serverIp:root/project.git</url>
   <developerConnection>scm:git:git@serverIp:root/project.git</developerConnection>
</scm>
<plugins>
    <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-release-plugin</artifactId>
       <version>2.1</version>
    </plugin>
</plugins>
如果release中报Source files encoding not set的错误,还需要增加encoding的设置:
<properties>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

Git主要命令

git tag #查看本地的tag,release后会增加一个tag
git tag -d {tag_name} #删除tag
git push origin:refs/tags/{tag_name} #删除远程tag,先删除本地,在push到远端

Release成功后

GitLab上可以看到增加了一个xxx-1.0的tag。
本地代码的pom文件版本已经更新至下一版本,例如1.1-SNAPSHOT

2013年4月18日星期四

一致性Hash算法原理

算法介绍

consistent hash算法如图所示:首先将服务节点映射到一个0~2^32的圆上。例如server1~server3,hash(server1), hash(server2), hash(server3)
图1. 将server映射到圆上
然后将key值同样映射到圆上,并沿着顺时针查找第一个server节点,那么这个key值就会被保存到对应的server上。如图2.
图2.将key用同样的算法映射到圆上并寻找对应的server
如果添加或减少一个节点,响应会调整对应的节点和server的对应关系。如图3.
图3. 增加server4后,节点重新分布。
从图3看出,只有server4到server4之间的key会改变映射关系,key5会保存到server4.其他部分不会受到影响。

均匀分布

如图1-3, server在圆上的映射可能不是均匀的,这会导致有些节点映射的key比较多,有些则比较少。为了尽可能的均匀分布,我们引入了虚节点的概念。
所谓虚节点,就是将每个server在圆上映射多个hash值,例如server1,可以通过编号进行hash值映射计算:hash(server1#1),hash(server1#2)...hash(server1#n).一般每个server在圆上映射100-200个虚节点以后,key的分布会比较平均。
之前有一篇测试hash平均分布的文章,传送门。