2015年6月30日星期二

记低版本Hive的left outer join Bug.

由于公司使用的是低版本的hive(0.8),同时也进行了自研发。因此线上的hive版本较低,并且存在left outer join的Bug.

Bug解释:

 left outer join(或者右外关联、全外关联)最重要的特性与原则就是主表的数据不能丢,例如有A,B两个表,都有a,b两个字段:
hive> select * from A order by a,b;
OK
a b
1 1
1 2
1 3
2 3
2 4
hive> select * from B order by a,b;
OK
a b
1 1
1 2
1 3
那么以下这个左外关联查询应该得到以下的结果:
hive> select a.*,b.* from A left outer join B on A.a = B.a and A.b = B.b;
OK
a b a b
1 1 1 1
1 2 1 2
1 3 1 3
2 3 NULL NULL
2 4 NULL NULL
但是实际情况却是,低版本的Hive在外关联上的一个重要缺陷。
select A.*,B.* from A left outer join B on A.a = B.a and A.b = B.b  where A.a =1 and B.b = 2;
上述SQL多了一条where条件。根据外关联的原则:主表数据不能丢失,即A表的数据不能丢失,当on里面的条件为真时,使用B的数据进行关联,当on里面的条件为假时,B的列全部为NULL。因此这个SQL的结果应该是:
a b a b
1 1 NULL NULL
1 2 1 2
1 3 NULL NULL
关系型数据库中验证此SQL的结果正确。
但是在Hive中结果却如下:
select A.*,B.* from A left outer join B on A.a = B.a and A.b = B.b  where A.a =1 and B.b = 2;
OK
a b a b
1 2 1 2
问题分析:
Hive在做left outer join时,首先进行join,结果集中B表对应的列会存在没有数据的情况,即NULL;然后,hive进行where条件过滤,但是此时B表的部分行的列为NULL(即A无法匹配到B表列的结果),这些行会被过滤掉,导致结果集错误。
hive的执行逻辑顺序有误,where条件会对关联后的B表列进行过滤。
正确的逻辑顺序: 过滤-->关联
规避方法:
将B表的where条件作为子查询,具体如下:(对于有分区限制的情况,也需要添加至子查询)
select A.*,C.* from A left outer join (select * from B where B.b = 2 ) C on A.a = C.a and A.b = B.b ;

velocity如何判空

问题: I want to check for null, something like this:
#if ($car.fuel == null)

Approach 1: null在条件表达式中表示false. (null is evaluated as a false conditional.)
#if( ! $car.fuel )
  • 备注: 如果$car.fuel为'false',依然可以通过判断.What this approach is actually checking is whether the reference is null or false.
Approach 2: null是一个空string (null is evaluated as an empty string in quiet references.)
#if( "$!car.fuel" == "" )
  • 备注: 当$car.fuel为空String ''依然可以通过pass. What this approach is actually checking is whether the reference is null or empty.
BTW, just checking for empty can be achieved by:
#if( "$car.fuel" == "" )
Approach 3: 结合Approach 1 and 2,就可以判断null并且只判断null.
#if ((! $car.fuel) && ("$!car.fuel" == ""))

Approach 4: Use a Tool that can check for null (NullTool,ViewNullTool).
#if( $null.isNull($car.fuel) )
  • Note: Of course, NullTool must be in the Context as $null in this case.
Approach 5: Don't check for null directly, use a self-explaining method.
#if( $car.fuelEmpty )
  • Note: 建议的方法. You have to implement the method, but it makes the template so easy-to-read.
public boolean isFuelEmpty()
{
  // return true if fuel is empty.
}
Approach 6: Use a custom directive. cf. IfNullDirectiveIfNotNullDirective
#ifnull( $car.fuel )
#ifnotnull( $car.fuel )
  • Note: You will have to register the directive in your velocity.properties.

userdirective = org.apache.velocity.tools.generic.directive.Ifnull
userdirective = org.apache.velocity.tools.generic.directive.Ifnotnull

2015年6月29日星期一

GC算法原理

  GC算法原理在传统JVM内存管理中,我们把Heap空间分为Young/Old两个分区,Young分区又包括一个Eden和两个Survivor分区,如图1所示。新产生的对象首先会被存放在Eden区,而每次minor GC发生时,JVM一方面将Eden分区内存活的对象拷贝到一个空的Survivor分区,另一方面将另一个正在被使用的Survivor分区中的存活对象也拷贝到空的Survivor分区内。在此过程中,JVM始终保持一个Survivor分区处于全空的状态。一个对象在两个Survivor之间的拷贝到一定次数后,如果还是存活的,就将其拷入Old分区。当Old分区没有足够空间时,GC会停下所有程序线程,进行Full GC,即对Old区中的对象进行整理。这个所有线程都暂停的阶段被称为Stop-The-World(STW),也是大多数GC算法中对性能影响最大的部分。
分年代的Heap结构

而G1 GC则完全改变了这一传统思路。它将整个Heap分为若干个预先设定的小区域块(如图2),每个区域块内部不再进行新旧分区, 而是将整个区域块标记为Eden/Survivor/Old。当创建新对象时,它首先被存放到某一个可用区块(Region)中。当该区块满了,JVM就会创建新的区块存放对象。当发生minor GC时,JVM将一个或几个区块中存活的对象拷贝到一个新的区块中,并在空余的空间中选择几个全新区块作为新的Eden分区。当所有区域中都有存活对象,找不到全空区块时,才发生Full GC。而在标记存活对象时,G1使用RememberSet的概念,将每个分区外指向分区内的引用记录在该分区的RememberSet中,避免了对整个Heap的扫描,使得各个分区的GC更加独立。在这样的背景下,G1 GC大大提高了触发Full GC时的Heap占用率,同时也使得Minor GC的暂停时间更加可控,对于内存较大的环境非常友好。这些颠覆性的改变,将给GC性能带来怎样的变化呢?最简单的方式,我们可以将老的GC设置直接迁移为G1 GC,然后观察性能变化。
G1 Heap结构示意

哪些因素影响Java调用的性能?



哪些因素影响Java调用的性能?

 from http://www.codeceo.com/article/java-performance-inspect.html

  • 最快与最短的方法调用的类型之间存在巨大的性能差别。
  • 在实际应用中,添加或删除final关键字并不会真正影响性能。但如果除此以外,你还在层级结构上进行某些操作,那这些行为则可能导致性能下降。
  • 更深的类的层次结构并不会真正影响到调用的性能。
  • 单态调用比双态调用更快。
  • 双态调用比超多状态调用更快。
  • 我们在能够进行剖析(profile-ably),但是不能进行查验的单态调用点中看到类型保护,这种保护会使得这些调用点的调用性能低于那些能够进行查验的单态调用点。


因为在子类中可以重写一个非 final 方法,这使得调用点可以调用不同的方法。现假设我传入一个 Foo 的实例或一个重写了 bar 子类—— Baz的实例,编译器如何得知要调用哪一个 bar 方法呢?在默认情况下,方法将在Java中被虚拟化(可重写)。对于任一调用点,编译器需要在一个称为虚拟表(vtable)的表中寻找与其对应的方法。这是个非常耗时的过程,所以,能进行优化的编译器,总是会试图减少这种查询带来的开销。一种方法就是先前提到的内联,这的确是个良策,但前提是编译器能证明在给定的调用点上调用的方法唯一。而这样的调用点我们称为单态(monomorphic)调用点。


不幸的是,进行这种分析需要耗费大量时间。所以在实际过程中,确定一个调用点是否单态是个不太可取的方法。对此,JIT编译器倾向于使用一种替代方法:列出哪些类可以在此调用点被调用,接着根据之前的N个相同的调用猜测此调用点是否是单态的。以假定某个调用点永远为单态,来进行投机性质的优化往往是可取的行为。因为这样的优化往往都是正确的,但也因它无法确保永远正确,编译器需要在方法调用之前注入一个用于检查方法类型的防护机制。
除了单态的调用点以外,还有两种调用点我们希望对其进行优化。一种称为双态(bimorphic)调用点,在该点上有两个候选方法。对此你依然可以实现内联——借助防护代码,让其检测应调用哪一个方法,并引导程序跳转至内联在调用点的两个方法体中真正对应的那一个。这样的方式还是比查看所有虚拟表的方式要快得多。但在某些情况下,我们得利用内联缓存来进行优化。内联缓存需要借助一张特定的跳转表( jump table),这种表类似于对虚拟表查找做的一份缓存。hotsopt JIT编译器支持双态内联缓存,并定义那些拥有三个及三个以上候选方法的调用点为超多状态(megamorphic)调用点。
这就使得我在基准测试与探究当中,需要额外地把调用情况划分为三类:单态、双态、超多状态。

2015年6月28日星期日

vim常用命令


  • 查找和替换
    vi/vim 中可以使用:s 命令来替换字符串。该命令有很多种不同细节使用方法,可以实现复杂的功能。 

  
  :s/vivian/sky/g 替换当前行所有 vivian 为 sky 
  
  :n,$s/vivian/sky/ 替换第 n 行开始到最后一行中每一行的第一个 vivian 为 sky 
  
  :n,$s/vivian/sky/g 替换第 n 行开始到最后一行中每一行所有 vivian 为 sky 
  
  n 为数字,若 n 为 .,表示从当前行开始到最后一行 
  
  :%s/vivian/sky/(等同于 :g/vivian/s//sky/) 替换每一行的第一个 vivian 为 sky 
  
  :%s/vivian/sky/g(等同于 :g/vivian/s//sky/g) 替换每一行中所有 vivian 为 sky 
  
  可以使用 # 作为分隔符,此时中间出现的 / 不会作为分隔符 
  
  :s#vivian/#sky/# 替换当前行第一个 vivian/ 为 sky/ 
  
  :%s+/oradata/apras/+/user01/apras1+ (使用+ 来 替换 / ): /oradata/apras/替换成/user01/apras1/

   替换确认
   需要在某个字符(串)在文章中某些位置出现时被替换,而其它位置不被替换的有选择的操作,这就需要用户来进行确认,vi的查找替换同样支持
   例如
   :s/vivian/sky/g 替换当前行所有 vivian 为 sky 
   在命令后面加上一个字母c就可以实现,即:s/vivian/sky/gc
   顾名思意,c是confirm的缩写

Java 中间件


  • 配置管理:

  Disconf
  Diamond


  • zookeeper客户端

  Curator


  • MQ
  HermesJMS:一个开源免费的跨平台的JMS消息监听工具。它可以很方便和各种JMS框架集成和交互,可以用来监听、发送、接收、修改、存储消息等。

Hive常用命令

执行hive sql文件:hive -f filePath


删除数据库:
DROP DATABASE是删除所有的表并删除数据库的语句。它的语法如下:
DROP DATABASE StatementDROP (DATABASE|SCHEMA) [IF EXISTS] database_name [RESTRICT|CASCADE];
下面的查询用于删除数据库。假设要删除的数据库名称为userdb。
hive> DROP DATABASE IF EXISTS userdb;
以下是使用CASCADE查询删除数据库。这意味着要全部删除相应的表在删除数据库之前。
hive> DROP DATABASE IF EXISTS userdb CASCADE;
以下使用SCHEMA查询删除数据库。
hive> DROP SCHEMA userdb;
此子句中添加在Hive0.6版本。

行转列:
lateral view explode(split(site,'\\|')) site as temp_site;

join加行转列:对转后的列进行临时命名,否则会有重名问题

SELECT   group_id,
                 temp_${OperateDate}_site as site,
                 site_price,
                 start_dt,
                 end_dt
from (
SELECT            ods_site.groupid as group_id,
                  site,
                              ods_site_price.price as site_price,
          
       '${OperateDate}' AS start_dt
       ,'20991231' AS end_dt
  FROM ods_site LEFT OUTER JOIN ods_site_price
                     ON ods_site_price.group_id = ods_site.groupid                          
WHERE ods_site.yymmdd=${OperateDate} and ods_site_price.yymmdd=${OperateDate})  
lateral view explode(split(site,'\\|')) site as temp_${OperateDate}_site   ;

添加分区表语法(表已创建,在此基础上添加分区):
ALTER TABLE table_name ADD
partition_spec [ LOCATION 'location1' ]
partition_spec [ LOCATION 'location2' ] ...

for example:
ALTER TABLE day_table ADD
PARTITION (dt='2008-08-08', hour='08')
location '/path/pv1.txt'
 
删除分区语法:
ALTER TABLE table_name DROP
partition_spec, partition_spec,...
用户可以用 ALTER TABLE DROP PARTITION 来删除分区。分区的元数据和数据将被一并删除。

for example:
ALTER TABLE day_hour_table DROP PARTITION (dt='2008-08-08', hour='09');

数据加载进分区表中语法:
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]

for example:
LOAD DATA INPATH '/user/pv.txt' INTO TABLE day_hour_table PARTITION(dt='2008-08- 08', hour='08'); LOAD DATA local INPATH '/user/hua/*' INTO TABLE day_hour partition(dt='2010-07- 07');
当数据被加载至表中时,不会对数据进行任何转换。Load操作只是将数据复制至Hive表对应的位置。数据加载时在表下自动创建一个目录

数据装载:
http://blog.csdn.net/macyang/article/details/7259416

insert into 和overwrite的用法:
INSERT INTO TABLE tablename1 [PARTITION \
(partcol1=val1, partcol2=val2 ...)] \
select_statement1 FROM from_statement;
INSERT OVERWRITE TABLE tablename1 [PARTITION \
(partcol1=val1, partcol2=val2 ...) [IF NOT EXISTS]] \
select_statement1 FROM from_statement;
两种方式的相同点: 1.两个表的维度必须一样,才能够正常写入 2.如果查询出来的数据类型和插入表格对应的列数据类型不一致,将会进行转换,但是不能保证转换一定成功,比如如果查询出来的数据类型为int,插入表格对应的列类型为string,可以通过转换将int类型转换为string类型;但是如果查询出来的数据类型为string,插入表格对应的列类型为int,转换过程可能出现错误,因为字母就不可以转换为int,转换失败的数据将会为NULL。 不同点: 1.insert into是增加数据 2.insert overwrite是删除原有数据然后在新增数据,如果有分区那么只会删除指定分区数据,其他分区数据不受影响