posts - 110, comments - 101, trackbacks - 0, articles - 7
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

1、拦截器是基于java反射机制的,而过滤器是基于函数回调的。
2、过滤器依赖与servlet容器,而拦截器不依赖与servlet容器。
3、拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用。
4、拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。
5、在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。

过滤器是在java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的 action进行业务逻辑,
比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),
或者在传入servlet或者 struts的action前统一设置字符集,
或者去除掉一些非法字符(聊天室经常用到的,一些骂人的话)。。。

拦截器 可通过的是符合条件的action。 拦截器本身是一个普通的Java对象,它能动态拦截Action调用,
Action执行前后执行拦截器本身提供的各种个样的Web项目需求。也可以阻止Action的执行,同时也可以提取
Action中可以复用的部分。
前段时间参与一个项目,过滤器用的是Interceptor 觉得比以前用的Filter好用很多,现在拿出来比较一下
Filter
    该过滤器的方法是创建一个类XXXFilter实现此接口,并在该类中的doFilter方法中声明过滤规则,然后在配置文件web.xml中声明他所过滤的路径
    <filter>
        <filter-name>XXXFilter</filter-name>
        <filter-class>
            com.web.util.XXXFilter
        </filter-class>
    </filter>
   
    <filter-mapping>
        <filter-name>XXXFilter</filter-name>
        <url-pattern>*.action</url-pattern>
    </filter-mapping>
Interceptor
     该过滤器的方法也是创建一个类XXXInterceptor实现此接口,在该类中intercept方法写过滤规则,不过它过滤路径的方法和Filter不同,它与strut.xml结合使用,
   创建一个strus.xml的子配置文件struts-l99-default.xml,它继承与struts2的struts-default,此配置文件是其他子配置文件的父类,只要是继承与该文件的配置文件所声明的路径都会被它过滤 如下
 <package name="XXX-default" namespace="/" extends="struts-default">
        <interceptors>
            <interceptor name="authentication" class="com.util.XXXInterceptor" />
           
            <interceptor-stack name="user">
                <interceptor-ref name="defaultStack" />
                <interceptor-ref name="authentication" />
            </interceptor-stack>
            <interceptor-stack name="user-submit">
                <interceptor-ref name="user" />
                <interceptor-ref name="token" />
            </interceptor-stack>
            <interceptor-stack name="guest">
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
            <interceptor-stack name="guest-submit">
                <interceptor-ref name="defaultStack" />
                <interceptor-ref name="token" />
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="user" />
   </package>
 比较一,filter基于回调函数,我们需要实现的filter接口中doFilter方法就是回调函数,而interceptor则基于java本身的反射机制,这是两者最本质的区别。
 比较二,filter是依赖于servlet容器的,即只能在servlet容器中执行,很显然没有servlet容器就无法来回调doFilter方法。而interceptor与servlet容器无关。
 比较三,Filter的过滤范围比Interceptor大,Filter除了过滤请求外通过通配符可以保护页面,图片,文件等等,而Interceptor只能过滤请求。
 比较四,Filter的过滤例外一般是在加载的时候在init方法声明,而Interceptor可以通过在xml声明是guest请求还是user请求来辨别是否过滤。
        </filter-class>
    </filter>
   
    <filter-mapping>
        <filter-name>XXXFilter</filter-name>
        <url-pattern>*.action</url-pattern>
    </filter-mapping>
Interceptor
     该过滤器的方法也是创建一个类XXXInterceptor实现此接口,在该类中intercept方法写过滤规则,不过它过滤路径的方法和Filter不同,它与strut.xml结合使用,
   创建一个strus.xml的子配置文件struts-l99-default.xml,它继承与struts2的struts-default,此配置文件是其他子配置文件的父类,只要是继承与该文件的配置文件所声明的路径都会被它过滤 如下
 <package name="XXX-default" namespace="/" extends="struts-default">
        <interceptors>
            <interceptor name="authentication" class="com.util.XXXInterceptor" />
           
            <interceptor-stack name="user">
                <interceptor-ref name="defaultStack" />
                <interceptor-ref name="authentication" />
            </interceptor-stack>
            <interceptor-stack name="user-submit">
                <interceptor-ref name="user" />
                <interceptor-ref name="token" />
            </interceptor-stack>
            <interceptor-stack name="guest">
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
            <interceptor-stack name="guest-submit">
                <interceptor-ref name="defaultStack" />
                <interceptor-ref name="token" />
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="user" />
   </package>
 比较一,filter基于回调函数,我们需要实现的filter接口中doFilter方法就是回调函数,而interceptor则基于java本身的反射机制,这是两者最本质的区别。
 比较二,filter是依赖于servlet容器的,即只能在servlet容器中执行,很显然没有servlet容器就无法来回调doFilter方法。而interceptor与servlet容器无关。
 比较三,Filter的过滤范围比Interceptor大,Filter除了过滤请求外通过通配符可以保护页面,图片,文件等等,而Interceptor只能过滤请求。
 比较四,Filter的过滤例外一般是在加载的时候在init方法声明,而Interceptor可以通过在xml声明是guest请求还是user请求来辨别是否过滤。

posted @ 2012-10-20 14:21 云云 阅读(430) | 评论 (0)编辑 收藏

     摘要: java nio从1.4版本就出现了,而且依它优异的性能赢得了广大java开发爱好者的信赖。我很纳闷,为啥我到现在才接触,难道我不是爱好者,难道nio不优秀。经过长达半分钟的思考,我意识到:时候未到。以前总是写那些老掉牙的web程序,唉,好不容易翻身啦,现在心里好受多了。因为真不想自己到了30岁,还在说,我会ssh,会ssi,精通javascript,精通数据库,精通。。。人生苦短,要开拓点不是吗...  阅读全文

posted @ 2012-10-17 14:27 云云 阅读(5674) | 评论 (0)编辑 收藏

  一致性哈希算法是分布式系统中常用的算法。比如,一个分布式的存储系统,要将数据存储到具体的节点上,如果采用普通的hash方法,将数据映射到具体的节点上,如key%N,key是数据的key,N是机器节点数,如果有一个机器加入或退出这个集群,则所有的数据映射都无效了,如果是持久化存储则要做数据迁移,如果是分布式缓存,则其他缓存就失效了。

    因此,引入了一致性哈希算法:


 

把数据用hash函数(如MD5),映射到一个很大的空间里,如图所示。数据的存储时,先得到一个hash值,对应到这个环中的每个位置,如k1对应到了图中所示的位置,然后沿顺时针找到一个机器节点B,将k1存储到B这个节点中。

如果B节点宕机了,则B上的数据就会落到C节点上,如下图所示:


 

这样,只会影响C节点,对其他的节点A,D的数据不会造成影响。然而,这又会造成一个“雪崩”的情况,即C节点由于承担了B节点的数据,所以C节点的负载会变高,C节点很容易也宕机,这样依次下去,这样造成整个集群都挂了。

       为此,引入了“虚拟节点”的概念:即把想象在这个环上有很多“虚拟节点”,数据的存储是沿着环的顺时针方向找一个虚拟节点,每个虚拟节点都会关联到一个真实节点,如下图所使用:


图中的A1、A2、B1、B2、C1、C2、D1、D2都是虚拟节点,机器A负载存储A1、A2的数据,机器B负载存储B1、B2的数据,机器C负载存储C1、C2的数据。由于这些虚拟节点数量很多,均匀分布,因此不会造成“雪崩”现象。

 

Java实现:

  1. public class Shard<S> { // S类封装了机器节点的信息 ,如name、password、ip、port等   
  2.   
  3.     private TreeMap<Long, S> nodes; // 虚拟节点   
  4.     private List<S> shards; // 真实机器节点   
  5.     private final int NODE_NUM = 100// 每个机器节点关联的虚拟节点个数   
  6.   
  7.     public Shard(List<S> shards) {  
  8.         super();  
  9.         this.shards = shards;  
  10.         init();  
  11.     }  
  12.   
  13.     private void init() { // 初始化一致性hash环   
  14.         nodes = new TreeMap<Long, S>();  
  15.         for (int i = 0; i != shards.size(); ++i) { // 每个真实机器节点都需要关联虚拟节点   
  16.             final S shardInfo = shards.get(i);  
  17.   
  18.             for (int n = 0; n < NODE_NUM; n++)  
  19.                 // 一个真实机器节点关联NODE_NUM个虚拟节点   
  20.                 nodes.put(hash("SHARD-" + i + "-NODE-" + n), shardInfo);  
  21.   
  22.         }  
  23.     }  
  24.   
  25.     public S getShardInfo(String key) {  
  26.         SortedMap<Long, S> tail = nodes.tailMap(hash(key)); // 沿环的顺时针找到一个虚拟节点   
  27.         if (tail.size() == 0) {  
  28.             return nodes.get(nodes.firstKey());  
  29.         }  
  30.         return tail.get(tail.firstKey()); // 返回该虚拟节点对应的真实机器节点的信息   
  31.     }  
  32.   
  33.     /** 
  34.      *  MurMurHash算法,是非加密HASH算法,性能很高, 
  35.      *  比传统的CRC32,MD5,SHA-1(这两个算法都是加密HASH算法,复杂度本身就很高,带来的性能上的损害也不可避免) 
  36.      *  等HASH算法要快很多,而且据说这个算法的碰撞率很低. 
  37.      *  http://murmurhash.googlepages.com/ 
  38.      */  
  39.     private Long hash(String key) {  
  40.           
  41.         ByteBuffer buf = ByteBuffer.wrap(key.getBytes());  
  42.         int seed = 0x1234ABCD;  
  43.           
  44.         ByteOrder byteOrder = buf.order();  
  45.         buf.order(ByteOrder.LITTLE_ENDIAN);  
  46.   
  47.         long m = 0xc6a4a7935bd1e995L;  
  48.         int r = 47;  
  49.   
  50.         long h = seed ^ (buf.remaining() * m);  
  51.   
  52.         long k;  
  53.         while (buf.remaining() >= 8) {  
  54.             k = buf.getLong();  
  55.   
  56.             k *= m;  
  57.             k ^= k >>> r;  
  58.             k *= m;  
  59.   
  60.             h ^= k;  
  61.             h *= m;  
  62.         }  
  63.   
  64.         if (buf.remaining() > 0) {  
  65.             ByteBuffer finish = ByteBuffer.allocate(8).order(  
  66.                     ByteOrder.LITTLE_ENDIAN);  
  67.             // for big-endian version, do this first:   
  68.             // finish.position(8-buf.remaining());   
  69.             finish.put(buf).rewind();  
  70.             h ^= finish.getLong();  
  71.             h *= m;  
  72.         }  
  73.   
  74.         h ^= h >>> r;  
  75.         h *= m;  
  76.         h ^= h >>> r;  
  77.   
  78.         buf.order(byteOrder);  
  79.         return h;  
  80.     }  
  81.   
  82. }  

posted @ 2012-10-10 11:32 云云 阅读(48790) | 评论 (5)编辑 收藏

这两天公司MQ出现一个怪现象,有三台MQ server 其中一台死掉后
consumer不会到其它两台消费,这个问题后来发现是配置的问题
在brokerURL只配置了一个brokerURL,所以容器启动时只会建立一个连接
当这个连接挂掉后 就只能等待这个连接重启后才能执行。

jms.brokerUrl=failover\:(tcp\://ip1\:61616?wireFormat.maxInactivityDurationInitalDelay\=30000,tcp\://ip2\:61616?
wireFormat.maxInactivityDurationInitalDelay\=30000)?
jms.useAsyncSend\=true&randomize\=true&initialReconnectDelay\=50&maxReconnectAttempts\=1&timeout\=1000&backup=true


属性 backup的作用 官方解释:
如果backup=true,并且the URIs to use for reconnect from the list provided的数量大于一个的情况下,broker将会维护着两个连接,其中一个作为备份,在主连接出现故障时实现快速切换

这里的故障不一定是死机 也可以是消费过慢 消息就发送到另一台server上



posted @ 2012-09-26 16:52 云云 阅读(6940) | 评论 (0)编辑 收藏

linkedHashMap也是map的实现,使用Iterator遍历的时候 最先得到的是先插入的数据。
保证了数据插入的顺序。

public class LRUMap<K, V> extends LinkedHashMap<K, V> {

    private static final long serialVersionUID = -3700466745992492679L;

    private int               coreSize;

    public LRUMap(int coreSize) {
        super(coreSize + 1, 1.1f, true);
        this.coreSize = coreSize;
    }

    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        return size() > coreSize;
    }
}


覆盖removeEldestEntry方法,当超过这个容量的时候,
put进新的值方法返回true时,便移除该map中最老的键和值

public LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder);

 initialCapacity   初始容量

 loadFactor    加载因子,一般是 0.75f

 accessOrder   false 基于插入顺序  true  基于访问顺序(get一个元素后,这个元素被加到最后,使用了LRU 最近最少被使用的调度算法)

如 boolean accessOrder = true; 
      Map<String, String> m = new LinkedHashMap<String, String>(20, .80f,  accessOrder  );
      m.put("1", "my"));

      m.put("2", "map"));

      m.put("3", "test"));

      m.get("1");

      m.get("2");

      Log.d("tag",  m);

     若 accessOrder == true;  输出 {3=test, 1=my, 2=map}

         accessOrder == false;  输出 {1=my, 2=map,3=test}






posted @ 2012-09-05 14:16 云云 阅读(1022) | 评论 (2)编辑 收藏

在网浏览的时候  发现了这篇文章  很有用  就保留了下来

hbase不是数据库,一些数据库中基本的功能hbase并不具备.
二级索引就是其中很重要的一点,在数据库中索引是在平常不过的功能了.
而在hbase中,value上的索引只能靠自己来实现.

hbase中最简单的二级索引的实现方式是通过另外一个hbase表来实现.
下面通过postput方法,实现对表sunwg01的二级索引.

举例说下二级索引实现:
表sunwg01的f1:k1有如下记录
100 tom
101 mary

对于表sunwg01来说,可以通过100,101直接访问记录,但是如果想要访问mary这条记录,则只能全表遍历
为了解决这个问题,创建了表sunwg02
表sunwg02中的f1:k1有如下记录
tom 100
mary 101

现在如果要查找mary这条记录,可以先查表sunwg02中,找到mary的value的为101

下面通过postput方式实现,在put源表的同时更新索引表的功能。
详细代码如下:

import java.io.IOException; import java.util.Iterator; import java.util.List;   import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver; import org.apache.hadoop.hbase.coprocessor.ObserverContext; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.regionserver.wal.WALEdit;   public class postput_test extends BaseRegionObserver {         @Override      public void postPut(final ObserverContext<RegionCoprocessorEnvironment> e,           final Put put, final WALEdit edit, final boolean writeToWAL) throws IOException {             HTable table = new HTable("sunwg02");           List<KeyValue> kv = put.get("f1".getBytes(), "k1".getBytes());           Iterator<KeyValue> kvl = kv.iterator();             while(kvl.hasNext()) {               KeyValue tmp = kvl.next();               Put tput = new Put(tmp.getValue());               tput.add("f1".getBytes(),"k1".getBytes(),tmp.getRow());               table.put(tput);             }           table.close();       } 





posted @ 2012-08-16 17:30 云云 阅读(1161) | 评论 (1)编辑 收藏

通常在项目中我们都会把log4j的配置放到classpath里,
log4j的输出路径也就直接写在log4j.xml或log4j.properties中了,
原本就这样了不用麻烦什么了,可是在我们公司什么都要配置分离。
所以 log4j的输出目录也就不再开发人员指定了,
那么如何做到分离呢。
有的是直接把log4j.xml或properties文件分离,在项目启动时加载进来,
那么这样一来 整个log4j的配置文件都不由开发人员控制,
可是通常log4j的配置由运维人员配置的东东也就一个输出目录了,
而log4j的其它配置还是由开发人员控制,
这时可以用${},来指定
<param name="file" value="${log4j.home}/test.log" />
log4j.home由容器启动时指定,jvm中加上   -Dlog4j.home=D:/log
这样在Log4j.xml中的${log4j.home}就知道了实际的输出目录了,
同样也可以把这个log4j.home放到分离的properties中,这时候可以
在容器启动时在监听器来解析properties,获取到log4j.home变量后
把值设置到system.env中
System.setProperties("log4j.home");
这样一来 ,log4j一样可以找到输出目录





posted @ 2012-07-29 14:48 云云 阅读(4487) | 评论 (0)编辑 收藏

 光从字面上来理解,很容易让一些初学者先入为主的认为:SecondaryNameNode(snn)就是NameNode(nn)的热备进程。其 实不是。snn是HDFS架构中的一个组成部分,但是经常由于名字而被人误解它真正的用途,其实它真正的用途,是用来保存namenode中对HDFS metadata的信息的备份,并减少namenode重启的时间。对于hadoop进程中 ,要配置好并正确的使用 snn,还是需要做一些工作的。hadoop的默认配置中让 snn进程默认运行在了 namenode 的那台机器上,但是这样的话,如果这台机器出错,宕机,对恢复HDFS文件系统是很大的灾难,更好的方式是:将snn的进程配置在另外一台机器 上运行。
在hadoop中,namenode负责对HDFS的metadata的持久化存储,并且处理来自客户端的对HDFS的各种操作的交互反馈。为了保 证交互速度,HDFS文件系统的metadata是被load到namenode机器的内存中的,并且会将内存中的这些数据保存到磁盘进行持久化存储。为 了保证这个持久化过程不会成为HDFS操作的瓶颈,hadoop采取的方式是:没有对任何一次的当前文件系统的snapshot进行持久化,对HDFS最 近一段时间的操作list会被保存到namenode中的一个叫Editlog的文件中去。当重启namenode时,除了 load fsImage意外,还会对这个EditLog文件中 记录的HDFS操作进行replay,以恢复HDFS重启之前的最终状态。
而SecondaryNameNode,会周期性的将EditLog中记录的对HDFS的操作合并到一个checkpoint中,然后清空 EditLog。所以namenode的重启就会Load最新的一个checkpoint,并replay EditLog中 记录的hdfs操作,由于EditLog中记录的是从 上一次checkpoint以后到现在的操作列表,所以就会比较小。如果没有snn的这个周期性的合并过程,那么当每次重启namenode的时候,就会 花费很长的时间。而这样周期性的合并就能减少重启的时间。同时也能保证HDFS系统的完整性。
这就是SecondaryNameNode所做的事情。所以snn并不能分担namenode上对HDFS交互性操作的压力。尽管如此,当 namenode机器宕机或者namenode进程出问题时,namenode的daemon进程可以通过人工的方式从snn上拷贝一份metadata 来恢复HDFS文件系统。
至于为什么要将SNN进程运行在一台非NameNode的 机器上,这主要出于两点考虑:

  1. 可扩展性: 创建一个新的HDFS的snapshot需要将namenode中load到内存的metadata信息全部拷贝一遍,这样的操作需要的内存就需要 和namenode占用的内存一样,由于分配给namenode进程的内存其实是对HDFS文件系统的限制,如果分布式文件系统非常的大,那么 namenode那台机器的内存就可能会被namenode进程全部占据。
  2. 容错性: 当snn创建一个checkpoint的时候,它会将checkpoint拷贝成metadata的几个拷贝。将这个操作运行到另外一台机器,还可以提供分布式文件系统的容错性。

配置将SecondaryNameNode运行在另外一台机器上
HDFS的一次运行实例是通过在namenode机器上的$HADOOP_HOME/bin/start-dfs.sh( 或者start-all.sh ) 脚本来启动的。这个脚本会在运行该脚本的机器上启动 namenode进程,而slaves机器上都会启动DataNode进程,slave机器的列表保存在 conf/slaves文件中,一行一台机器。并且会在另外一台机器上启动一个snn进程,这台机器由 conf/masters文件指定。所以,这里需要严格注意,conf/masters 文件中指定的机器,并不是说jobtracker或者namenode进程要 运行在这台机器上,因为这些进程是运行在 launch bin/start-dfs.sh或者 bin/start-mapred.sh(start-all.sh)的机器上的。所以,masters这个文件名是非常的令人混淆的,应该叫做 secondaries会比较合适。然后,通过以下步骤:




1.修改conf/core-site.xml

增加

<property> 
<name>fs.checkpoint.period</name> 
<value>3600</value> 
<description>The number of seconds between two periodic checkpoints. </description> 
</property> 
<property> 
<name>fs.checkpoint.size</name> 
<value>67108864</value> 
<description>The size of the current edit log (in bytes) that triggers a periodic checkpoint even if the fs.checkpoint.period hasn't expired. </description> 
</property> 

<property> 
<name>fs.checkpoint.dir</name> 
<value>/data/work/hdfs/namesecondary</value> 
<description>Determines where on the local filesystem the DFS secondary name node should store the temporary images to merge. If this is a comma-delimited list of directories then the image is replicated in all of the directories for redundancy. </description> 
</property>
复制代码

fs.checkpoint.period表示多长时间记录一次hdfs的镜像。默认是1小时。
fs.checkpoint.size表示一次记录多大的size,默认64M

2.修改conf/hdfs-site.xml

增加

复制代码
<property> 
<name>dfs.http.address</name> 
<value>master:50070</value> 
<description> The address and the base port where the dfs namenode web ui will listen on. If the port is 0 then the server will start on a free port. </description> 
</property>
复制代码

0.0.0.0改为namenode的IP地址

3.重启hadoop,然后检查是否启动是否成功

登录secondarynamenode所在的机器,输入jps查看secondarynamenode进程
进入secondarynamenode的目录/data/work/hdfs/namesecondary
正确的结果:
如果没有,请耐心等待,只有到了设置的checkpoint的时间或者大小,才会生成。

4.恢复

制造namenode宕机的情况
1) kill 掉namenode的进程

[root@master name]# jps 
11749 NameNode 
12339 Jps 
11905 JobTracker 
[root@master name]# kill 11749

 

2)删除dfs.name.dir所指向的文件夹,这里是/data/work/hdfs/name

[root@master name]# rm -rf *

删除name目录下的所有内容,但是必须保证name这个目录是存在的

 

3)从secondarynamenode远程拷贝namesecondary文件到namenode的namesecondary

[root@master hdfs]# scp -r slave-001:/data/work/hdfs/namesecondary/ ./

4)启动namenode

[root@master /data]# hadoop namenode –importCheckpoint

正常启动以后,屏幕上会显示很多log,这个时候namenode就可以正常访问了

5)检查

使用hadoop fsck /user命令检查文件Block的完整性

hadoop fsck /

6)停止namenode,使用crrl+C或者会话结束

7)删除namesecondary目录下的文件(保存干净)

[root@master namesecondary]# rm -rf *


8)正式启动namenode

[root@master bin]# ./hadoop-daemon.sh start namenode

恢复工作完成,检查hdfs的数据

 

9)balancer

在使用start-balancer.sh时,
默认使用1M/S(1048576)的速度移动数据(so slowly...)
修改hdfs-site.xml配置,这里我们使用的是20m/S

<property> 
<name>dfs.balance.bandwidthPerSec</name> 
<value>20971520</value> 
<description> Specifies the maximum bandwidth that each datanode can utilize for the balancing purpose in term of the number of bytes per second. </description> 
</property>

然后结果是导致job运行变得不稳定,出现一些意外的长map单元,某些reduce时间处理变长(整个集群负载满满的情况下,外加20m/s的balance),据说淘宝的为10m/s,需要调整后实验,看看情况如何。


hadoop balancer -threshold 5

posted @ 2012-07-27 10:59 云云 阅读(3218) | 评论 (0)编辑 收藏

HBase提供了setCaching设置 cache数量,但是很多时候 如果设置不当,会相当耗内存。
如果不设置该值,默认是1条。如果设置该值很大,是可以加快速度,同时也消耗了太多的内存。
所以 合理的设置就很重要了。
当设置了setCaching(n)后,我们的server会从regin server上读取出n条数据。
那么client端读取数据的时候会直接从server的缓存中返回,
但是如果每次你只需要读取100条记录,但是设置了setCaching(1000),那么每次
都会从region server 多余的拿出900条记录,这样会让应用的server内存吃不消了
比较好的解决方案就是 设置setCaching(n)为实际需要的记录数。

posted @ 2012-07-25 11:12 云云 阅读(1407) | 评论 (1)编辑 收藏

这里面说的read既包括get,也包括scan,实际底层来看这两个操作也是一样的。
我们将要讨论的是,当我们从一张表读取数据的时候hbase到底是怎么处理的。
分二种情况来看,第一种就是表刚创建,所有put的数据还在memstore中,并没有刷新到hdfs上;第二种情况是,该store已经进行多次的flush操作,产生了多个storefile了。
在具体说明两种情况前,先考虑下表的region的问题,如果表只有一个region,那么没有说的,肯定是要扫描这个唯一的region。假设该表有多个region,此时.META.表就派上用场了,hbase会首先根据你要扫描的数据的rowkey来判断到底该数据放在哪个region上,该region所在服务器地址,然后把数据读取的请求发送给该region server。好了,实际对数据访问的任务都会放在region server上执行,为了简单起见,接下来的讨论都是在单台region server上对单个region的操作。
首先来看第一种情况,表刚创建,所有put的数据还在memstore中,并没有刷新到hdfs上。这个时候数据是在memstore中,并没有storefile产生,理所当然,hbase要查找memstore来获得相应的数据。对于memstore或者storefile来说,内存中都有关于rowkey的索引的,所以对于通过rowkey的查询速度是非常快速的。通过查询该索引就知道是否存在需要查看的数据,已经该数据在memstore中的位置。通过索引提供的信息就很容易找得到所需要的数据。这种情况很简单。
在来看第二种情况,该store已经进行多次的flush操作,产生了多个storefile了。那么数据应该从哪里查呢?所有的storefile?别忘记还有memstore。此时memstore中可能还会有没来得及flush的数据呢。如果此时该region还有很多的文件,是不是所有的文件都需要查找呢?hbase在查找先会根据时间戳或者查询列的信息来进行过滤,过滤掉那些肯定不含有所需数据的storefile或者memstore,尽量把我们的查询目标范围缩小。
尽管缩小了,但仍可能会有多个文件需要扫描的。storefile的内部有三维有序的,但是各个storefile之间并不是有序的。比如,storefile1中可能有rowkey为100到110的记录,而storefile2可能有rowkey为105到115的数据,storefile的rowkey的范围很有可能有交叉。所以查询数据的过程也不可能是对storefile的顺序查找。
hbase会首先查看每个storefile的最小的rowkey,然后按照从小到大的顺序进行排序,结果放到一个队列中,排序的算法就是按照hbase的三维顺序,按照rowkey,column,ts进行排序,rowkey和column是升序,而ts是降序。
实际上并不是所有满足时间戳和列过滤的文件都会加到这个队列中,hbase会首先对各个storefile中的数据进行探测,只会扫描扫描那些存在比当前查询的rowkey大的记录的storefile。举例来说,我当前要查找的rowkey为108,storefile1中rowkey范围为100~104,storefile2中rowkey的范围为105~110,那么对于storefile1最大的rowkey为104,小于105,所以不存在比所查rowkey105大的记录,storefile并不会被加到该队列中。根据相同的规则,storefile2则会被添加到该队列中。
队列有了,下面开始查询数据,首先通过poll取出队列的头storefile,会从storefile读取一条记录返回;接下来呢,该storefile的下条记录并不一定是查询结果的下一条记录,因为队列的比较顺序是比较的每个storefile的第一条符合要求的rowkey。所以,hbase会继续从队列中剩下的storefile取第一条记录,把该记录与头storefile的第二条记录做比较,如果前者大,那么返回头storefile的第二条记录;如果后者大,则会把头storefile放回队列重新排序,在重新取队列的头storefile。然后重复上面的整个过程。这个过程比较烦,语言描述不清楚,代码会更加清晰。
这段代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public KeyValue next()  throws IOException {
  if(this.current == null) {
    return null;
  }
  KeyValue kvReturn = this.current.next();
  KeyValue kvNext = this.current.peek();
  if (kvNext == null) {
    this.current.close();
    this.current = this.heap.poll();
  } else {
    KeyValueScanner topScanner = this.heap.peek();
    if (topScanner == null ||
        this.comparator.compare(kvNext, topScanner.peek()) >= 0) {
      this.heap.add(this.current);
      this.current = this.heap.poll();
    }
  }
  return kvReturn;
}



以上的代码在KeyValueHeap.java类中。
举个例子来说明:表sunwg01,有两个storefile,storefile1中包括rowkey100,rowkey110;storefile2中包括rowkey104,rowkey108。我现在执行scan ‘sunwg01′扫描表sunwg01中的所有的记录。
根据前面提到的排序规则,队列中会有2个元素,按顺序分别为storefile1,storefile2。
1,取出storefile1中的第一条记录rowkey100,并返回该结果
2,取出storefile1中的下一条记录rowkey110,同时取出队列剩余storefile的第一条记录rowkey104,经过比较rowkey110大于rowkey104,则将storefile1放回队列中
3,因为队列是有序的队列,会重新对storefile进行排序,因为此时storefile1的最小rowkey为110,而storefile2的最小rowkey为104,所以排序的结果为storefile2,storefile1
4,重复上面的过程,直到查不到记录为止。
最后查到的结果为:rowkey100,rowkey104,rowkey108,rowkey110。
顺便说下block cache的事情,当从storefile中读数据的时候会首先查看block cache中是否有该数据,如果有则直接查block cache,就没必要查询hdfs;如果没有该数据,那么就只能去查hdfs了。这也是为了block cache的命中率对性能有很大影响的原因。
上面描述了从hbase中read的基本的过程,还有些细节没有具体说,但是大概过程应该是都说到了。

posted @ 2012-07-18 18:04 云云 阅读(2919) | 评论 (0)编辑 收藏

仅列出标题
共12页: 上一页 1 2 3 4 5 6 7 8 9 下一页 Last