??xml version="1.0" encoding="utf-8" standalone="yes"?>亚洲视频在线观看地址,亚洲人成77777在线播放网站不卡,国产亚洲免费的视频看http://m.tkk7.com/DLevin/category/54911.htmlIn general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatrazh-cnSun, 27 Sep 2015 04:21:15 GMTSun, 27 Sep 2015 04:21:15 GMT60Netty3架构解析http://m.tkk7.com/DLevin/archive/2015/09/04/427031.htmlDLevinDLevinFri, 04 Sep 2015 01:40:00 GMThttp://m.tkk7.com/DLevin/archive/2015/09/04/427031.htmlhttp://m.tkk7.com/DLevin/comments/427031.htmlhttp://m.tkk7.com/DLevin/archive/2015/09/04/427031.html#Feedback0http://m.tkk7.com/DLevin/comments/commentRss/427031.htmlhttp://m.tkk7.com/DLevin/services/trackbacks/427031.html前记很早以前有读Netty源码的打了Q然而第一ơ尝试的时候从Netty4开始,一直抓不到核心的框架流E,后来因ؓ其他事情忙着放下了。这ơ趁着休假重新捡vq个骨_因ؓNetty3现在q在被很多项目用,因而这ơ决定先从Netty3入手Q瞬间发现Netty3的代码比Netty4中规中矩的多Q很多概念在代码本n中都有清晰的表达Q所以半天就把整个框架的骨架搞清楚了。再?a >Netty4对Netty3的改qȝQ回去读Netty4的源码,反而觉得轻松了Q一U豁然开朗的感觉?br />
记得d读Jetty源码的时候,因ؓ代码太庞大,q且自己的HTTP Server的了解太,因而只能自底向上的一个一个模块的叠加Q直到最后把所以的模块q接在一赯看清它的真正核心骨架。现在读源码Q开始习惯先把骨架理清,然后延C同的器官、血肉而看清整个h体?br />
本文从Reactor模式在Netty3中的应用Q引出Netty3的整体架构以及控制流E;然而除了Reactor模式QNetty3q在ChannelPipeline中用了Intercepting Filter模式Q这个模式也在Servlet的Filter中成功用,因而本文还会从Intercepting Filter模式出发详细介绍ChannelPipeline的设计理c本文假设读者已l对Netty有一定的了解Q因而不会包含过多入门介l,以及帮Netty做宣传的文字?br />

Netty3中的Reactor模式

Reactor模式在Netty中应用非常成功,因而它也是在Netty中受大肆宣传的模式,关于Reactor模式可以详细参考本人的另一文?a href="http://m.tkk7.com/DLevin/archive/2015/09/02/427045.html">《Reactor模式详解?/a>Q对Reactor模式的实现是Netty3的基本骨Ӟ因而本节会详l介lReactor模式如何应用Netty3中?br />
如果诅RReactor模式详解》,我们知道Reactor模式由Handle、Synchronous Event Demultiplexer、Initiation Dispatcher、Event Handler、Concrete Event Handler构成Q在Java的实现版本中QChannel对应HandleQSelector对应Synchronous Event DemultiplexerQƈ且Netty3q用了两层ReactorQMain Reactor用于处理Client的连接请求,Sub Reactor用于处理和Clientq接后的dhQ关于这个概念还可以参考Doug Lea的这PPTQ?a >Scalable IO In JavaQ。所以我们先要解决Netty3中用什么类实现所有的上述模块q把他们联系在一LQ以NIO实现方式ZQ?br />
模式是一U抽象,但是在实CQ经怼因ؓ语言Ҏ、框架和性能需要而做一些改变,因而Netty3对Reactor模式的实现有一套自q设计Q?br />1. ChannelEventQ?/strong>Reactor是基于事件编E的Q因而在Netty3中用ChannelEvent抽象的表达Netty3内部可以产生的各U事Ӟ所有这些事件对象在Channels帮助cM产生Qƈ且由它将事g推入到ChannelPipeline中,ChannelPipeline构徏ChannelHandler道QChannelEvent经q个道实现所有的业务逻辑处理。ChannelEvent对应的事件有QChannelStateEvent表示Channel状态的变化事gQ而如果当前Channel存在Parent ChannelQ则该事件还会传递到Parent Channel的ChannelPipeline中,如OPEN、BOUND、CONNECTED、INTEREST_OPS{,该事件可以在各种不同实现的Channel、ChannelSink中生;MessageEvent表示从Socket中读取数据完成、需要向Socket写数据或ChannelHandler对当前Message解析(如Decoder、Encoder)后触发的事gQ它由NioWorker、需要对Message做进一步处理的ChannelHandler产生QWriteCompletionEvent表示写完成而触发的事gQ它由NioWorker产生QExceptionEvent表示在处理过E中出现的ExceptionQ它可以发生在各个构件中Q如Channel、ChannelSink、NioWorker、ChannelHandler中;IdleStateEvent由IdleStateHandler触发Q这也是一个ChannelEvent可以无缝扩展的例子。注Q在Netty4后,已经没有ChannelEventc,所有不同事仉用对应方法表达,q也意味qChannelEvent不可扩展QNetty4采用在ChannelInboundHandler中加入userEventTriggered()Ҏ来实现这U扩展,具体可以参?a >q里?br />2. ChannelHandlerQ?/strong>在Netty3中,ChannelHandler用于表示Reactor模式中的EventHandler。ChannelHandler只是一个标记接口,它有两个子接口:ChannelDownstreamHandler和ChannelUpstreamHandlerQ其中ChannelDownstreamHandler表示从用户应用程序流向Netty3内部直到向Socket写数据的道Q在Netty4中改名ؓChannelOutboundHandlerQChannelUpstreamHandler表示数据从Socketq入Netty3内部向用户应用程序做数据处理的管道,在Netty4中改名ؓChannelInboundHandler?br />3. ChannelPipelineQ?/strong>用于理ChannelHandler的管道,每个Channel一个ChannelPipeline实例Q可以运行过E中动态的向这个管道中d、删除ChannelHandlerQ由于实现的限制Q在最末端的ChannelHandler向后d或删除ChannelHandler不一定在当前执行程中v效,参?a >q里Q。ChannelPipeline内部l护一个ChannelHandler的双向链表,它以Upstream(Inbound)方向为正向,Downstream(Outbound)方向为方向。ChannelPipeline采用Intercepting Filter模式实现Q具体可以参?a href="http://m.tkk7.com/DLevin/archive/2015/09/03/427086.html">q里Q这个模式的实现在后一节中q是详细介绍?br />4. NioSelectorQ?/strong>Netty3使用NioSelector来存放SelectorQSynchronous Event DemultiplexerQ,每个C生的NIO Channel都向q个Selector注册自己以让q个Selector监听q个NIO Channel中发生的事gQ当事g发生Ӟ调用帮助cChannels中的Ҏ生成ChannelEvent实例Q将该事件发送到q个Netty Channel对应的ChannelPipeline中,而交l各UChannelHandler处理。其中在向Selector注册NIO ChannelӞNetty Channel实例以Attachment的Ş式传入,该Netty Channel在其内部的NIO Channel事g发生Ӟ会以Attachment的Ş式存在于SelectionKey中,因而每个事件可以直接从q个Attachment中获取相关链的Netty ChannelQƈ从Netty Channel中获取与之相兌的ChannelPipelineQ这个实现和Doug Lea?a >Scalable IO In Java一模一栗另外Netty3q采用了Scalable IO In Java中相同的Main Reactor和Sub Reactor设计Q其中NioSelector的两个实玎ͼBoss即ؓMain ReactorQNioWorker为Sub Reactor。Boss用来处理新连接加入的事gQNioWorker用来处理各个q接对Socket的读写事Ӟ其中Boss通过NioWorkerPool获取NioWorker实例QNetty3模式使用RoundRobin方式攑֛NioWorker实例。更形象一点的Q可以通过Scalable IO In Java的这张图表达Q?br />
若与Ractor模式对应QNioSelector中包含了Synchronous Event DemultiplexerQ而ChannelPipeline中管理着所有EventHandlerQ因而NioSelector和ChannelPipeline共同构成了Initiation Dispatcher?br />5. ChannelSinkQ?/strong>在ChannelHandler处理完成所有逻辑需要向客户端写响应数据Ӟ一般会调用Netty Channel中的writeҎQ然而在q个writeҎ实现中,它不是直接向其内部的Socket写数据,而是交给Channels帮助c,内部创徏DownstreamMessageEventQ反向从ChannelPipeline的管道中过去,直到W一个ChannelHandler处理完毕Q最后交lChannelSink处理Q以避免d写而媄响程序的吞吐量。ChannelSink这个MessageEvent提交lNetty Channel中的writeBufferQueueQ最后NioWorker会等到这个NIO Channel已经可以处理写事件时无阻塞的向这个NIO Channel写数据。这是上图的send是从SubReactor直接出发的原因?br />6. ChannelQ?/strong>Netty有自qChannel抽象Q它是一个资源的容器Q包含了所有一个连接涉及到的所有资源的饮用Q如装NIO Channel、ChannelPipeline、Boss、NioWorkerPool{。另外它q提供了向内部NIO Channel写响应数据的接口write、连?l定到某个地址的connect/bind接口{,个h感觉虽然对Channel本n来说Q因为它装了NIO ChannelQ因而这些接口定义在q里是合理的Q但是如果考虑到Netty的架构,它的Channel只是一个资源容器,有这个Channel实例可以得到和它相关的基本所有资源,因而这Uwrite、connect、bind动作不应该再由它负责Q而是应该由其他类来负责,比如在Netty4中就在ChannelHandlerContextd了writeҎQ虽然netty4q没有删除Channel中的write接口?br />

Netty3中的Intercepting Filter模式

如果说Reactor模式是Netty3的骨Ӟ那么Intercepting Filter模式则是Netty的中枢。Reactor模式主要应用在Netty3的内部实玎ͼ它是Netty3h良好性能的基Q而Intercepting Filter模式则是ChannelHandlerl合实现一个应用程序逻辑的基Q只有很好的理解了这个模式才能用好NettyQ甚臌得心应手?br />
关于Intercepting Filter模式的详l介l可以参?a href="http://m.tkk7.com/DLevin/archive/2015/09/03/427086.html">q里Q本节主要介lNetty3中对Intercepting Filter模式的实玎ͼ其实是DefaultChannelPipeline对Intercepting Filter模式的实现。在上文有提到Netty3的ChannelPipeline是ChannelHandler的容器,用于存储与管理ChannelHandlerQ同时它在Netty3中也起到桥梁的作用,卛_是连接Netty3内部到所有ChannelHandler的桥梁。作为ChannelPipeline的实现者DefaultChannelPipelineQ它使用一个ChannelHandler的双向链表来存储Q以DefaultChannelPipelineContext作ؓ节点Q?br />
public interface ChannelHandlerContext {
    Channel getChannel();

    ChannelPipeline getPipeline();

    String getName();

    ChannelHandler getHandler();

    
boolean canHandleUpstream();
    
boolean canHandleDownstream();
    
void sendUpstream(ChannelEvent e);
    
void sendDownstream(ChannelEvent e);
    Object getAttachment();

    
void setAttachment(Object attachment);
}

private final class DefaultChannelHandlerContext implements ChannelHandlerContext {
   
volatile DefaultChannelHandlerContext next;
   
volatile DefaultChannelHandlerContext prev;
   
private final String name;
   
private final ChannelHandler handler;
   
private final boolean canHandleUpstream;
   
private final boolean canHandleDownstream;
   
private volatile Object attachment;
.....
}
在DefaultChannelPipeline中,它存储了和当前ChannelPipeline相关联的Channel、ChannelSink以及ChannelHandler链表的head、tailQ所有ChannelEvent通过sendUpstream、sendDownstream为入口流l整个链表:
public class DefaultChannelPipeline implements ChannelPipeline {
    
private volatile Channel channel;
    
private volatile ChannelSink sink;
    
private volatile DefaultChannelHandlerContext head;
    
private volatile DefaultChannelHandlerContext tail;
......
    
public void sendUpstream(ChannelEvent e) {
        DefaultChannelHandlerContext head 
= getActualUpstreamContext(this.head);
        
if (head == null) {
            
return;
        }
        sendUpstream(head, e);
    }

    
void sendUpstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
        
try {
            ((ChannelUpstreamHandler) ctx.getHandler()).handleUpstream(ctx, e);
        } 
catch (Throwable t) {
            notifyHandlerException(e, t);
        }
    }

    
public void sendDownstream(ChannelEvent e) {
        DefaultChannelHandlerContext tail 
= getActualDownstreamContext(this.tail);
        
if (tail == null) {
            
try {
                getSink().eventSunk(
this, e);
                
return;
            } 
catch (Throwable t) {
                notifyHandlerException(e, t);
                
return;
            }
        }
        sendDownstream(tail, e);
    }

    
void sendDownstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
        
if (e instanceof UpstreamMessageEvent) {
            
throw new IllegalArgumentException("cannot send an upstream event to downstream");
        }
        
try {
            ((ChannelDownstreamHandler) ctx.getHandler()).handleDownstream(ctx, e);
        } 
catch (Throwable t) {
            e.getFuture().setFailure(t);
            notifyHandlerException(e, t);
        }
    }
对Upstream事gQ向后找到所有实CChannelUpstreamHandler接口的ChannelHandlerl成链(getActualUpstreamContext()Q?/span>Q而对Downstream事gQ向前找到所有实CChannelDownstreamHandler接口的ChannelHandlerl成链(getActualDownstreamContext()Q:
    private DefaultChannelHandlerContext getActualUpstreamContext(DefaultChannelHandlerContext ctx) {
        
if (ctx == null) {
            
return null;
        }
        DefaultChannelHandlerContext realCtx 
= ctx;
        
while (!realCtx.canHandleUpstream()) {
            realCtx 
= realCtx.next;
            
if (realCtx == null) {
                
return null;
            }
        }
        
return realCtx;
    }
    
private DefaultChannelHandlerContext getActualDownstreamContext(DefaultChannelHandlerContext ctx) {
        
if (ctx == null) {
            
return null;
        }
        DefaultChannelHandlerContext realCtx 
= ctx;
        
while (!realCtx.canHandleDownstream()) {
            realCtx 
= realCtx.prev;
            
if (realCtx == null) {
                
return null;
            }
        }
        
return realCtx;
    }
在实际实现ChannelUpstreamHandler或ChannelDownstreamHandlerӞ调用 ChannelHandlerContext中的sendUpstream或sendDownstreamҎ控制流E交l下一? ChannelUpstreamHandler或下一个ChannelDownstreamHandlerQ或调用Channel中的writeҎ发? 响应消息?br />
public class MyChannelUpstreamHandler implements ChannelUpstreamHandler {
    
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
        
// handle current logic, use Channel to write response if needed.
        
// ctx.getChannel().write(message);
        ctx.sendUpstream(e);
    }
}

public class MyChannelDownstreamHandler implements ChannelDownstreamHandler {
    
public void handleDownstream(
            ChannelHandlerContext ctx, ChannelEvent e) 
throws Exception {
        
// handle current logic
        ctx.sendDownstream(e);
    }
}
当ChannelHandler向ChannelPipelineContext发送事件时Q其内部从当前ChannelPipelineContext节点出发扑ֈ下一个ChannelUpstreamHandler或ChannelDownstreamHandler实例Qƈ向其发送ChannelEventQ对于Downstream链,如果到达铑ְQ则ChannelEvent发送给ChannelSinkQ?br />
public void sendDownstream(ChannelEvent e) {
    DefaultChannelHandlerContext prev 
= getActualDownstreamContext(this.prev);
   
if (prev == null) {
       
try {
            getSink().eventSunk(DefaultChannelPipeline.
this, e);
        } 
catch (Throwable t) {
            notifyHandlerException(e, t);
        }
    } 
else {
        DefaultChannelPipeline.
this.sendDownstream(prev, e);
    }
}

public void sendUpstream(ChannelEvent e) {
    DefaultChannelHandlerContext next 
= getActualUpstreamContext(this.next);
   
if (next != null) {
        DefaultChannelPipeline.
this.sendUpstream(next, e);
    }
}
正是因ؓq个实现Q如果在一个末ChannelUpstreamHandler中先U除自己Q在向末添加一个新的ChannelUpstreamHandlerQ它是无效的Q因为它的next已经在调用前固定设|ؓnull了?br />
ChannelPipeline作ؓChannelHandler的容器,它还提供了各U增、删、改ChannelHandler链表中的ҎQ而且如果某个ChannelHandlerq实CLifeCycleAwareChannelHandlerQ则该ChannelHandler在被dqChannelPipeline或从中删除时都会得到同志Q?br />
public interface LifeCycleAwareChannelHandler extends ChannelHandler {
    
void beforeAdd(ChannelHandlerContext ctx) throws Exception;
    
void afterAdd(ChannelHandlerContext ctx) throws Exception;
    
void beforeRemove(ChannelHandlerContext ctx) throws Exception;
    
void afterRemove(ChannelHandlerContext ctx) throws Exception;
}

public interface ChannelPipeline {
    
void addFirst(String name, ChannelHandler handler);
    
void addLast(String name, ChannelHandler handler);
    
void addBefore(String baseName, String name, ChannelHandler handler);
    
void addAfter(String baseName, String name, ChannelHandler handler);
    
void remove(ChannelHandler handler);
    ChannelHandler remove(String name);

    
<extends ChannelHandler> T remove(Class<T> handlerType);
    ChannelHandler removeFirst();

    ChannelHandler removeLast();

    
void replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler);
    ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);

    
<extends ChannelHandler> T replace(Class<T> oldHandlerType, String newName, ChannelHandler newHandler);
    ChannelHandler getFirst();

    ChannelHandler getLast();

    ChannelHandler get(String name);

    
<extends ChannelHandler> T get(Class<T> handlerType);
    ChannelHandlerContext getContext(ChannelHandler handler);

    ChannelHandlerContext getContext(String name);

    ChannelHandlerContext getContext(Class
<? extends ChannelHandler> handlerType);
    
void sendUpstream(ChannelEvent e);
    
void sendDownstream(ChannelEvent e);
    ChannelFuture execute(Runnable task);

    Channel getChannel();

    ChannelSink getSink();

    
void attach(Channel channel, ChannelSink sink);
    
boolean isAttached();
    List
<String> getNames();
    Map
<String, ChannelHandler> toMap();
}

在DefaultChannelPipeline的ChannelHandler链条的处理流EؓQ?br />

参考:

《Netty主页?/a>
《Netty源码解读Q四QNetty与Reactor模式?/a>
《Netty代码分析?/a>
Scalable IO In Java
Intercepting Filter Pattern


DLevin 2015-09-04 09:40 发表评论
]]>
Intercepting Filter模式详解http://m.tkk7.com/DLevin/archive/2015/09/03/427086.htmlDLevinDLevinThu, 03 Sep 2015 14:14:00 GMThttp://m.tkk7.com/DLevin/archive/2015/09/03/427086.htmlhttp://m.tkk7.com/DLevin/comments/427086.htmlhttp://m.tkk7.com/DLevin/archive/2015/09/03/427086.html#Feedback0http://m.tkk7.com/DLevin/comments/commentRss/427086.htmlhttp://m.tkk7.com/DLevin/services/trackbacks/427086.html问题描述在服务器~程中,通常需要处理多U不同的hQ在正式处理h之前Q需要对h做一些预处理Q如Q?br />
  1. U录每个Client的每ơ访问信息?/li>
  2. 对Clientq行认证和授权检查(Authentication and AuthorizationQ?/li>
  3. 查当前Session是否合法?/li>
  4. 查Client的IP地址是否可信赖或不可信赖QIP地址白名单、黑名单Q?/li>
  5. h数据是否先要解压或解码?/li>
  6. 是否支持Clienth的类型、Browser版本{?/li>
  7. d性能监控信息?/li>
  8. d调试信息?/li>
  9. 保证所有异帔R被正捕获到Q对未预料到的异常做通用处理Q防止给Client看到内部堆栈信息?br />

在响应返回给客户端之前,有时候也需要做一些预处理再返回:

  1. 对响应消息编码或压羃?/li>
  2. 为所有响应添加公共头、尾{消息?/li>
  3. q一步Enrich响应消息Q如d公共字段、Session信息、Cookie信息Q甚臛_全改变响应消息等?/li>
如何实现q样的需求,同时保持可扩展性、可重用性、可配置、移植性?

问题解决

要实现这U需求,最直观的方法就是在每个h处理q程中添加所有这些逻辑Qؓ了减代码重复,可以所有这些检查提取成ҎQ这样在每个处理Ҏ中调用即可:
public Response service1(Request request) {
    validate(request);
    request 
= transform(request);
    Response response 
= process1(request);
    
return transform(response);
}
此时Q如果出现service2ҎQ依焉要拷贝service1中的实现Q然后将process1换成process2卛_。这个时候我们发现很多重复代码,l箋对它重构Q比如提取公共逻辑到基cL模版ҎQ这U用承的方式会引起子cd父类的耦合Q如果要让某些模块变的可配置需要有太多的判断逻辑Q代码变的臃肿;因而可以更q一步,所有处理逻辑抽象Z个Processor接口Q然后用Decorate模式Q即引用优于l承Q:
public interface Processor {
    Response process(Request request);
}
public class CoreProcessor implements Processor {
    
public Response process(Request request) {
        
// do process/calculation
    }
}
public class DecoratedProcessor implements Processor {
    
private final Processor innerProcessor;
    
public DecoratedProcessor(Processor processor) {
        
this.innerProcessor = processor;
    }

    
public Response process(Request request) {
        request 
= preProcess(request);
        Response response 
= innerProcessor.process(request);
        response 
= postProcess(response);
        
return response;
    }

    
protected Request preProcess(Request request) {
        
return request;
    }
    
protected Response postProcess(Response response) {
        
return response;
    }
}

public void Transformer extends DecoratedProcessor {
    
public Transformer(Processor processor) {
        
super(processor);
    }

    
protected Request preProcess(Request request) {
        
return transformRequest(request);
    }
    
protected Response postProcess(Response response) {
        
return transformResponse(response);
    }
}
此时Q如果需要在真正的处理逻辑之前加入其他的预处理逻辑Q只需要承DecoratedProcessorQ实现preProcess或postProcessҎQ分别在h处理之前和请求处理之后横向切入一些逻辑Q也是所谓的AOP~程Q面向切面的~程Q然后只需要根据需求构个链条:
Processor processor = new MissingExceptionCatcher(new Debugger(new Transformer(new CoreProcessor());
Response response 
= processor.process(request);
......
q已l是相对比较好的设计了,每个Processor只需要关注自q实现逻辑卛_Q代码变的简z;q且每个Processor各自独立Q可重用性好Q测试方便;整条链上能实现的功能只是取决于链的构造,因而只需要有一U方法配|链的构造即可,可配|性也变得灉|Q然而很多时候引用是一U静态的依赖Q而无法满_态的需求。要构造这条链Q每个前|Processor需要知道其后的ProcessorQ这在某些情况下q不是在起初q道的。此Ӟ我们需要引入Intercepting Filter模式来实现动态的改变条链?br />

Intercepting Filter模式

在前文已l构Z一条由引用而成的Processor链,然而这是一条静态链Qƈ且需要一开始就能构造出q条链,Z解决q个限制Q我们可以引入一个ProcessorChain来维护这条链Qƈ且这条链可以动态的构徏?br />
有多U方式可以实现ƈ控制q个链:
  1. 在存储上Q可以用数l来存储所有的ProcessorQProcessor在数l中的位|表C个Processor在链条中的位|;也可以用链表来存储所有的ProcessorQ此时Processor在这个链表中的位|即是在链中的位|?/li>
  2. 在抽象上Q可以所有的逻辑都封装在Processor中,也可以将核心逻辑使用Processor抽象Q而外围逻辑使用Filter抽象?/li>
  3. 在流E控制上Q一般通过在Processor实现Ҏ中直接用ProcessorChain实例(通过参数掺入)来控制流E,利用Ҏ调用的进栈出栈的Ҏ实现preProcess()和postProcess()处理?/li>
在实际中使用q个模式的有QServlet的Filter机制、Netty的ChannelPipeline中、Structs2中的Interceptor中都实现了这个模式?br />

Intercepting Filter模式在Servlet的Filter中的实现QJetty版本Q?/h2>其中Servlet的Filter在Jetty的实C使用数组存储FilterQFilter末尾可以使用Servlet实例处理真正的业务逻辑Q在程控制上,使用FilterChain的doFilterҎ来实现。如FilterChain在Jetty中的实现Q?br />
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
   
// pass to next filter
    if (_filter < LazyList.size(_chain)) {
        FilterHolder holder
= (FilterHolder)LazyList.get(_chain, _filter++);
        Filter filter= holder.getFilter();
        filter.doFilter(request, response, this);                   
       
return;
    }

   
// Call servlet
    HttpServletRequest srequest = (HttpServletRequest)request;
   
if (_servletHolder != null) {
        _servletHolder.handle(_baseRequest,request, response);

    }
}
q里Q_chain实际上是一个Filter的ArrayListQ由FilterChain调用doFilter()启动调用W一个Filter的doFilter()ҎQ在实际的Filter实现中,需要手动的调用FilterChain.doFilter()Ҏ来启动下一个Filter的调用,利用Ҏ调用的进栈出栈的Ҏ实现Request的pre-process和Response的post-process处理。如果不调用FilterChain.doFilter()ҎQ则表示不需要调用之后的FilterQ流E从当前Filterq回Q在它之前的Filter的FilterChain.doFilter()调用之后的逻辑反向处理直到W一个Filter处理完成而返回?br />
public class MyFilter implements Filter {
    
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        
// pre-process ServletRequest
        chain.doFilter(request, response);
        
// post-process Servlet Response
    }
}
整个Filter铄处理程如下Q?br />

Intercepting Filter模式在Netty3中的实现

Netty3在DefaultChannelPipeline中实CIntercepting Filter模式Q其中ChannelHandler是它的Filter。在Netty3的DefaultChannelPipeline中,使用一个以ChannelHandlerContext点的双向链表来存储ChannelHandlerQ所有的横切面逻辑和实际业务逻辑都用ChannelHandler表达Q在控制程上用ChannelHandlerContext的sendDownstream()和sendUpstream()Ҏ来控制流E。不同于Servlet的FilterQChannelHandler有两个子接口QChannelUpstreamHandler和ChannelDownstreamHandler分别用来hq入时的处理程和响应出L的处理流E。对于Client的请求,从DefaultChannelPipeline的sendUpstream()Ҏ入口Q?br />
public void sendDownstream(ChannelEvent e) {
    DefaultChannelHandlerContext tail 
= getActualDownstreamContext(this.tail);
   
if (tail == null) {
       
try {
            getSink().eventSunk(
this, e);
           
return;
        } 
catch (Throwable t) {
            notifyHandlerException(e, t);
           
return;
        }
    }
    sendDownstream(tail, e);
}
void sendDownstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
   
if (e instanceof UpstreamMessageEvent) {
       
throw new IllegalArgumentException("cannot send an upstream event to downstream");
    }
   
try {
        ((ChannelDownstreamHandler) ctx.getHandler()).handleDownstream(ctx, e)
     } 
catch (Throwable t) {
        e.getFuture().setFailure(t);
        notifyHandlerException(e, t);
    }
}
如果有响应消息,该消息从DefaultChannelPipeline的sendDownstream()Ҏ为入口:
public void sendUpstream(ChannelEvent e) {
    DefaultChannelHandlerContext head 
= getActualUpstreamContext(this.head);
   
if (head == null) {
        return;
    }
    sendUpstream(head, e);
}
void sendUpstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
   
try {
        ((ChannelUpstreamHandler) ctx.getHandler()).handleUpstream(ctx, e);
    } 
catch (Throwable t) {
        notifyHandlerException(e, t);
    }
}
在实际实现ChannelUpstreamHandler或ChannelDownstreamHandlerӞ调用ChannelHandlerContext中的sendUpstream或sendDownstreamҎ控制流E交l下一个ChannelUpstreamHandler或下一个ChannelDownstreamHandlerQ或调用Channel中的writeҎ发送响应消息?br />
public class MyChannelUpstreamHandler implements ChannelUpstreamHandler {
    
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
        
// handle current logic, use Channel to write response if needed.
        
// ctx.getChannel().write(message);
        ctx.sendUpstream(e);
    }
}

public class MyChannelDownstreamHandler implements ChannelDownstreamHandler {
    
public void handleDownstream(
            ChannelHandlerContext ctx, ChannelEvent e) 
throws Exception {
        
// handle current logic
        ctx.sendDownstream(e);
    }
}
当ChannelHandler向ChannelPipelineContext发送事件时Q其内部从当前ChannelPipelineContext 节点出发扑ֈ下一个ChannelUpstreamHandler或ChannelDownstreamHandler实例Qƈ向其发? ChannelEventQ对于Downstream链,如果到达铑ְQ则ChannelEvent发送给ChannelSinkQ?br />
public void sendDownstream(ChannelEvent e) {
    DefaultChannelHandlerContext prev 
= getActualDownstreamContext(this.prev);
   
if (prev == null) {
       
try {
            getSink().eventSunk(DefaultChannelPipeline.
this, e);
        } 
catch (Throwable t) {
            notifyHandlerException(e, t);
        }
    } 
else {
        DefaultChannelPipeline.
this.sendDownstream(prev, e);
    }
}

public void sendUpstream(ChannelEvent e) {
    DefaultChannelHandlerContext next 
= getActualUpstreamContext(this.next);
   
if (next != null) {
        DefaultChannelPipeline.
this.sendUpstream(next, e);
    }
}
正是因ؓq个实现Q如果在一个末ChannelUpstreamHandler中先U除自己Q在向末添加一个新的ChannelUpstreamHandlerQ它是无效的Q因为它的next已经在调用前固定设|ؓnull了?br />
在DefaultChannelPipeline的ChannelHandler链条的处理流EؓQ?br />
在这个实CQ不像Servlet的Filter实现利用Ҏ调用栈的q出栈来完成pre-process和post-processQ而是在进ȝ铑֒出来的链各自调用handleUpstream()和handleDownstream()ҎQ这样会引v调用栈其实是两条铄dQ因而需要注意这条链的总长度。这样做的好处是q条ChannelHandler的链不依赖于Ҏ调用栈,而是在DefaultChannelPipeline内部本n的链Q因而在handleUpstream()或handleDownstream()可以随时执行流E{发给其他U程或线E池Q只需要保留ChannelPipelineContext引用Q在处理完成后用q个ChannelPipelineContext重新向这条链的后一个节点发送ChannelEventQ然而由于Servlet的Filter依赖于方法的调用栈,因而方法返回意味着所有执行完成,q种限制在异步编E中会引起问题,因而Servlet?.0后引入了Async的支持?br />

Intercepting Filter模式的缺?/h2>单提一下这个模式的~点Q?br />1. 相对传统的编E模型,q个模式有一定的学习曲线Q需要很好的理解该模式后才能灉|的应用它来编E?br />2. 需要划分不同的逻辑C同的Filter中,q有些时候ƈ不是那么Ҏ?br />3. 各个Filter之间׃n数据变得困难。在Netty3中可以自定义自己的ChannelEvent来实现自定义消息的传输,或者用ChannelPipelineContext的Attachment字段来实现消息传输,而Servlet中的Filter则没有提供类似的机制Q如果不是可以配|的数据在Config中传递,其他时候的数据׃n需要其他机刉合完成?br />

参?/h2>Core J2EE Pattern - Intercepting Filter

DLevin 2015-09-03 22:14 发表评论
]]>Reactor模式详解http://m.tkk7.com/DLevin/archive/2015/09/02/427045.htmlDLevinDLevinWed, 02 Sep 2015 07:14:00 GMThttp://m.tkk7.com/DLevin/archive/2015/09/02/427045.htmlhttp://m.tkk7.com/DLevin/comments/427045.htmlhttp://m.tkk7.com/DLevin/archive/2015/09/02/427045.html#Feedback2http://m.tkk7.com/DLevin/comments/commentRss/427045.htmlhttp://m.tkk7.com/DLevin/services/trackbacks/427045.html 前记

W一ơ听到Reactor模式是三q前的某个晚上,一个室友突然跑q来问我什么是Reactor模式Q我上网查了一下,很多人都是给出NIO中的 Selector的例子,而且是NIO里Selector多\复用模型Q只是给它v了一个比较fancy的名字而已Q虽然它引入了EventLoop? 念,q对我来说是新的概念Q但是代码实现却是一LQ因而我q没有很在意q个模式。然而最q开始读Netty源码Q而Reactor模式是很多介lNetty的文章中被大肆宣传的模式Q因而我再次问自己,什么是Reactor模式Q本文就是对q个问题关于我的一些理解和试着来解{?br />

什么是Reactor模式

要回{这个问题,首先当然是求助Google或WikipediaQ其中Wikipedia上说Q?#8220;The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。从q个描述中,我们知道Reactor模式首先?strong>事g驱动的,有一个或多个q发输入源,有一个Service HandlerQ有多个Request Handlers
Q这个Service Handler会同步的输入的hQEventQ多路复用的分发l相应的Request Handler。如果用图来表达Q?br />
从结构上Q这有点cM生者消费者模式,x一个或多个生者将事g攑օ一个Queue中,而一个或多个消费者主动的从这个Queue中Poll事g来处理;而Reactor模式则ƈ没有Queue来做~冲Q每当一个Event输入到Service Handler之后Q该Service Handler会主动的Ҏ不同的Eventcd其分发l对应的Request Handler来处理?br />
更学术的Q这文章(Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous EventsQ上_“The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. Each service in an application may consistent of several methods and is represented by a separate event handler that is responsible for dispatching service-specific requests. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. Also known as Dispatcher, Notifier”。这D|q和Wikipedia上的描述cMQ有多个输入源,有多个不同的EventHandlerQRequestHandlerQ来处理不同的请求,Initiation Dispatcher用于理EventHanderQEventHandler首先要注册到Initiation Dispatcher中,然后Initiation DispatcherҎ输入的Event分发l注册的EventHandlerQ然而Initiation Dispatcherq不监听Event的到来,q个工作交给Synchronous Event Demultiplexer来处理?br />

Reactor模式l构

在解决了什么是Reactor模式后,我们来看看Reactor模式是由什么模块构成。图是一U比较简zŞ象的表现方式Q因而先上一张图来表辑֐个模块的名称和他们之间的关系Q?br />
HandleQ?/strong>x作系l中的句柄,是对资源在操作系l层面上的一U抽象,它可以是打开的文件、一个连?Socket)、Timer{。由于Reactor模式一般用在|络~程中,因而这里一般指Socket HandleQ即一个网l连接(ConnectionQ在Java NIO中的ChannelQ。这个Channel注册到Synchronous Event Demultiplexer中,以监听Handle中发生的事gQ对ServerSocketChannnel可以是CONNECT事gQ对SocketChannel可以是READ、WRITE、CLOSE事g{?br />Synchronous Event DemultiplexerQ?/strong>d{待一pd的Handle中的事g到来Q如果阻塞等待返回,卌C在q回的Handle中可以不d的执行返回的事gcd。这个模块一般用操作系l的select来实现。在Java NIO中用Selector来封装,当Selector.select()q回Ӟ可以调用Selector的selectedKeys()Ҏ获取Set<SelectionKey>Q一个SelectionKey表达一个有事g发生的Channel以及该Channel上的事gcd。上囄“Synchronous Event Demultiplexer ---notifies--> Handle”的流E如果是对的Q那内部实现应该是select()Ҏ在事件到来后会先讄Handle的状态,然后q回。不了解内部实现机制Q因而保留原图?br />Initiation DispatcherQ?/strong>用于理Event HandlerQ即EventHandler的容器,用以注册、移除EventHandler{;另外Q它q作为Reactor模式的入口调用Synchronous Event Demultiplexer的selectҎ以阻塞等待事件返回,当阻塞等待返回时Q根据事件发生的Handle其分发l对应的Event Handler处理Q即回调EventHandler中的handle_event()Ҏ?br />Event HandlerQ?/strong>定义事g处理ҎQhandle_event()Q以供InitiationDispatcher回调使用?br />Concrete Event HandlerQ?/strong>事gEventHandler接口Q实现特定事件处理逻辑?br />

Reactor模式模块之间的交?/h2> 单描qC下Reactor各个模块之间的交互流E,先从序列囑ּ始:

1. 初始化InitiationDispatcherQƈ初始化一个Handle到EventHandler的Map?br />2. 注册EventHandler到InitiationDispatcher中,每个EventHandler包含对相应Handle的引用,从而徏立Handle到EventHandler的映(MapQ?br />3. 调用InitiationDispatcher的handle_events()Ҏ以启动Event Loop。在Event Loop中,调用select()ҎQSynchronous Event DemultiplexerQ阻塞等待Event发生?br />4. 当某个或某些Handle的Event发生后,select()Ҏq回QInitiationDispatcherҎq回的Handle扑ֈ注册的EventHandlerQƈ回调该EventHandler的handle_events()Ҏ?br />5. 在EventHandler的handle_events()Ҏ中还可以向InitiationDispatcher中注册新的EventhandlerQ比如对AcceptorEventHandler来,当有新的clientq接Ӟ它会产生新的EventHandler以处理新的连接,q注册到InitiationDispatcher中?br />

Reactor模式实现

?a >Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server来分析Reactor模式Q这个Logging Server的实现完全遵循这里对Reactor描述Q因而放在这里以做参考。Logging Server中的Reactor模式实现分两个部分:Clientq接到Logging Server和Client向Logging Server写Log。因而对它的描述分成q两个步骤?br />Clientq接到Logging Server

1. Logging Server注册LoggingAcceptor到InitiationDispatcher?br />2. Logging Server调用InitiationDispatcher的handle_events()Ҏ启动?br />3. InitiationDispatcher内部调用select()ҎQSynchronous Event DemultiplexerQ,d{待Clientq接?br />4. Clientq接到Logging Server?br />5. InitiationDisptcher中的select()Ҏq回Qƈ通知LoggingAcceptor有新的连接到来?
6. LoggingAcceptor调用acceptҎacceptq个新连接?br />7. LoggingAcceptor创徏新的LoggingHandler?br />8. 新的LoggingHandler注册到InitiationDispatcher?同时也注册到Synchonous Event Demultiplexer?Q等待Client发v写logh?br />Client向Logging Server写Log

1. Client发送log到Logging server?br />2. InitiationDispatcher监测到相应的Handle中有事g发生Q返回阻塞等待,Ҏq回的Handle扑ֈLoggingHandlerQƈ回调LoggingHandler中的handle_event()Ҏ?br />3. LoggingHandler中的handle_event()Ҏ中读取Handle中的log信息?br />4. 接收到的log写入到日志文件、数据库{设备中?br />3.4步骤循环直到当前日志处理完成?br />5. q回到InitiationDispatcher{待下一ơ日志写h?br />
?a >Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events有对Reactor模式的C++的实现版本,多年不用C++Q因而略q?nbsp;

Java NIO对Reactor的实?/h2>在Java的NIO中,对Reactor模式有无~的支持Q即使用Selectorcd装了操作pȝ提供的Synchronous Event Demultiplexer功能。这个Doug Lea已经?a >Scalable IO In Java中有非常深入的解释了Q因而不再赘qͼ另外q篇文章对Doug Lea?a >Scalable IO In Java有一些简单解释,臛_它的代码格式比Doug Lea的PPT要整z一些?br />
需要指出的是,不同q里使用InitiationDispatcher来管理EventHandlerQ在Doug Lea的版本中使用SelectionKey中的Attachment来存储对应的EventHandlerQ因而不需要注册EventHandlerq个步骤Q或者设|Attachment是q里的注册。而且在这文章中QDoug Lea从单U程的Reactor、Acceptor、Handler实现q个模式出发Q演化ؓHandler中的处理逻辑多线E化Q实现类似Proactor模式Q此时所有的IO操作q是单线E的Q因而再演化Z个Main Reactor来处理CONNECT事g(Acceptor)Q而多个Sub Reactor来处理READ、WRITE{事?Handler)Q这些Sub Reactor可以分别再自qU程中执行,从而IO操作也多U程化。这个最后一个模型正是Netty中用的模型。ƈ且在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events?.5 Determine the Number of Initiation Dispatchers in an Application中也有相应的描述?br />

EventHandler接口定义

对EventHandler的定义有两种设计思\Qsingle-method设计和multi-method设计Q?br />A single-method interfaceQ?/strong>它将Event装成一个Event ObjectQEventHandler只定义一个handle_event(Event event)Ҏ。这U设计的好处是有利于扩展Q可以后来方便的d新的EventcdQ然而在子类的实CQ需要判断不同的Eventcd而再ơ扩展成 不同的处理方法,从这个角度上来说Q它又不利于扩展。另外在Netty3的用过E中Q由于它不停的创建ChannelEventc,因而会引vGC的不E_?br />A multi-method interfaceQ?/strong>q种设计是将不同的Eventcd? EventHandler中定义相应的Ҏ。这U设计就是Netty4中用的{略Q其中一个目的是避免ChannelEvent创徏引v的GC不稳定, 另外一个好处是它可以避免在EventHandler实现时判断不同的Eventcd而有不同的实玎ͼ然而这U设计会l扩展新的Eventcd时带来非? 大的ȝQ因为它需要该接口?br />
关于Netty4对Netty3的改q可以参?a >q里Q?br />
ChannelHandler with no event objectIn 3.x, every I/O operation created a ChannelEvent object. For each read / write, it additionally created a new ChannelBuffer. It simplified the internals of Netty quite a lot because it delegates resource management and buffer pooling to the JVM. However, it often was the root cause of GC pressure and uncertainty which are sometimes observed in a Netty-based application under high load.

4.0 removes event object creation almost completely by replacing the event objects with strongly typed method invocations. 3.x had catch-all event handler methods such as handleUpstream() and handleDownstream(), but this is not the case anymore. Every event type has its own handler method now:

Z么用Reactor模式

归功与Netty和Java NIO对Reactor的宣传,本文慕名而学习的Reactor模式Q因而已l默认Reactorh非常优秀的性能Q然而慕名归慕名Q到q里Q我q是要不得不问自己Reactor模式的好处在哪里Q即Z么要使用q个Reactor模式Q在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中是q么说的Q?br />
Reactor Pattern优点

Separation of concerns: The Reactor pattern decouples application-independent demultiplexing and dispatching mechanisms from application-specific hook method functionality. The application-independent mechanisms become reusable components that know how to demultiplex events and dispatch the appropriate hook methods defined by Event Handlers. In contrast, the application-specific functionality in a hook method knows how to perform a particular type of service.

Improve modularity, reusability, and configurability of event-driven applications: The pattern decouples application functionality into separate classes. For instance, there are two separate classes in the logging server: one for establishing connections and another for receiving and processing logging records. This decoupling enables the reuse of the connection establishment class for different types of connection-oriented services (such as file transfer, remote login, and video-on-demand). Therefore, modifying or extending the functionality of the logging server only affects the implementation of the logging handler class.

Improves application portability: The Initiation Dispatcher’s interface can be reused independently of the OS system calls that perform event demultiplexing. These system calls detect and report the occurrence of one or more events that may occur simultaneously on multiple sources of events. Common sources of events may in- clude I/O handles, timers, and synchronization objects. On UNIX platforms, the event demultiplexing system calls are called select and poll [1]. In the Win32 API [16], the WaitForMultipleObjects system call performs event demultiplexing.

Provides coarse-grained concurrency control: The Reactor pattern serializes the invocation of event handlers at the level of event demultiplexing and dispatching within a process or thread. Serialization at the Initiation Dispatcher level often eliminates the need for more complicated synchronization or locking within an application process.

q些貌似是很多模式的共性:解耦、提升复用性、模块化、可UL性、事仉动、细力度的ƈ发控制等Q因而ƈ不能很好的说明什么,特别是它鼓吹的对性能的提升,q里q没有体现出来。当然在q篇文章的开头有描述q另一U直观的实现QThread-Per-ConnectionQ即传统的实玎ͼ提到了这个传l实现的以下问题Q?br />
Thread Per Connection~点

Efficiency: Threading may lead to poor performance due to context switching, synchronization, and data movement [2];

Programming simplicity: Threading may require complex concurrency control schemes;

Portability: Threading is not available on all OS platforms.
对于性能Q它其实是W一点关于Efficiency的描qͼ即线E的切换、同步、数据的Ud会引h能问题。也是说从性能的角度上Q它最大的提升是减少了性能的用,即不需要每个Client对应一个线E。我的理解,其他业务逻辑处理很多时候也会用到相同的U程QIOd操作相对CPU的操作还是要慢很多,即Reactor机制中每ơ读写已l能保证非阻塞读写,q里可以减少一些线E的使用Q但是这减少的线E用对性能有那么大的媄响吗Q答案貌似是肯定的,q篇论文(SEDA: Staged Event-Driven Architecture - An Architecture for Well-Conditioned, Scalable Internet Service)寚w着U程的增长带来性能降低做了一个统计:

在这个统计中Q每个线E从盘中读8KB数据Q每个线E读同一个文Ӟ因而数据本w是~存在操作系l内部的Q即减少IO的媄响;所有线E是事先分配的,不会有线E启动的影响Q所有Q务在试内部产生Q因而不会有|络的媄响。该l计数据q行环境QLinux 2.2.14Q?GB内存Q?-way 500MHz Pentium III。从图中可以看出Q随着U程的增长,吞吐量在U程Cؓ8个左右的时候开始线性下降,q且?4个以后而迅速下降,其相应事件也在线E达?56个后指数上升。即1+1<2Q因为线E切换、同步、数据移动会有性能损失Q线E数增加C定数量时Q这U性能影响效果会更加明显?br />
对于q点Q还可以参?a >C10K ProblemQ用以描q同时有10K个Client发vq接的问题,?010q的时候已l出?0M Problem了?br />
当然也有Q?a >Threads are expensive are no longer valid.在不久的来可能又会发生不同的变化,或者这个变化正在、已l发生着Q没有做q比较仔l的试Q因而不敢随便断a什么,然而本点,即ɾU程变的影响q没有以前那么大Q用Reactor模式Q甚xSEDA模式来减线E的使用Q再加上其他解耦、模块化、提升复用性等优点Q还是值得使用的?br />

Reactor模式的缺?/h2>Reactor模式的缺点貌g是显而易见的Q?br />1. 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛Qƈ且不易于调试?br />2. Reactor模式需要底层的Synchronous Event Demultiplexer支持Q比如Java中的Selector支持Q操作系l的selectpȝ调用支持Q如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效?br />3. Reactor模式在IOd数据时还是在同一个线E中实现的,即使用多个Reactor机制的情况下Q那些共享一个Reactor的Channel如果出现一个长旉的数据读写,会媄响这个Reactor中其他Channel的相应时_比如在大文g传输ӞIO操作׃影响其他Client的相应时_因而对q种操作Q用传l的Thread-Per-Connection或许是一个更好的选择Q或则此时用Proactor模式?br />

参?/h2> Reactor Pattern WikiPedia
Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events
Scalable IO In Java
C10K Problem WikiPedia


DLevin 2015-09-02 15:14 发表评论
]]>深入HBase架构解析Q二Q?/title><link>http://m.tkk7.com/DLevin/archive/2015/08/22/426950.html</link><dc:creator>DLevin</dc:creator><author>DLevin</author><pubDate>Sat, 22 Aug 2015 11:40:00 GMT</pubDate><guid>http://m.tkk7.com/DLevin/archive/2015/08/22/426950.html</guid><wfw:comment>http://m.tkk7.com/DLevin/comments/426950.html</wfw:comment><comments>http://m.tkk7.com/DLevin/archive/2015/08/22/426950.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.tkk7.com/DLevin/comments/commentRss/426950.html</wfw:commentRss><trackback:ping>http://m.tkk7.com/DLevin/services/trackbacks/426950.html</trackback:ping><description><![CDATA[<h2> 前言</h2>q是<a href="http://m.tkk7.com/DLevin/archive/2015/08/22/426877.html">《深入HBase架构解析Q一Q?/a>的箋Q不多废话,l箋。。。?br /><h2>HBaseȝ实现</h2>通过前文的描qͼ我们知道在HBase写时Q相同Cell(RowKey/ColumnFamily/Column相同)q不保证在一P甚至删除一个Cell也只是写入一个新的CellQ它含有Delete标记Q而不一定将一个Cell真正删除了,因而这引起了一个问题,如何实现ȝ问题Q要解决q个问题Q我们先来分析一下相同的Cell可能存在的位|:首先Ҏ写入的CellQ它会存在于MemStore中;然后对之前已lFlush到HDFS中的CellQ它会存在于某个或某些StoreFile(HFile)中;最后,对刚dq的CellQ它可能存在于BlockCache中。既然相同的Cell可能存储在三个地方,在读取的时候只需要扫瞄这三个地方Q然后将l果合ƈ卛_(Merge Read)Q在HBase中扫瞄的序依次是:BlockCache、MemStore、StoreFile(HFile)。其中StoreFile的扫瞄先会用Bloom Filterqo那些不可能符合条件的HFileQ然后用Block Index快速定位CellQƈ其加蝲到BlockCache中,然后从BlockCache中读取。我们知道一个HStore可能存在多个StoreFile(HFile)Q此旉要扫瞄多个HFileQ如果HFileq多又是会引h能问题?br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig16.png" height="278" width="769" /><br /><h2>Compaction</h2>MemStore每次Flush会创建新的HFileQ而过多的HFile会引赯的性能问题Q那么如何解册个问题呢QHBase采用Compaction机制来解册个问题,有点cMJava中的GC机制Qv初Java不停的申请内存而不释放Q增加性能Q然而天下没有免费的午餐Q最l我们还是要在某个条件下L集垃圾,很多时候需要Stop-The-WorldQ这UStop-The-World有些时候也会引起很大的问题Q比如参考本人写?a href="http://m.tkk7.com/DLevin/archive/2015/08/01/426418.html">q篇文章</a>Q因而设计是一U权衡,没有完美的。还是类似Java中的GCQ在HBase中Compaction分ؓ两种QMinor Compaction和Major Compaction?br /><ol><li>Minor Compaction是指选取一些小的、相ȝStoreFile他们合q成一个更大的StoreFileQ在q个q程中不会处理已lDeleted或Expired的Cell。一ơMinor Compaction的结果是更少q且更大的StoreFile。(q个是对的吗QBigTable中是q样描述Minor Compaction?span style="font-size: 10.000000pt; font-family: 'Times'">QAs write operations execute, the size of the memtable in- creases. When the memtable size reaches a threshold, the memtable is frozen, a new memtable is created, and the frozen memtable is converted to an SSTable and written to GFS. This </span><span style="font-size: 10.000000pt; font-family: 'Times'; font-style: italic">minor compaction </span><span style="font-size: 10.000000pt; font-family: 'Times'">process has two goals: it shrinks the memory usage of the tablet server, and it reduces the amount of data that has to be read from the commit log during recovery if this server dies. Incom- ing read and write operations can continue while com- pactions occur. </span>也就是说它将memtable的数据flush的一个HFile/SSTableUCؓ一ơMinor CompactionQ?/li><li>Major Compaction是指所有的StoreFile合ƈ成一个StoreFileQ在q个q程中,标记为Deleted的Cell会被删除Q而那些已lExpired的Cell会被丢弃Q那些已l超q最多版本数的Cell会被丢弃。一ơMajor Compaction的结果是一个HStore只有一个StoreFile存在。Major Compaction可以手动或自动触发,然而由于它会引起很多的IO操作而引h能问题Q因而它一般会被安排在周末、凌晨等集群比较闲的旉?br /></li></ol>更Ş象一点,如下面两张图分别表示Minor Compaction和Major Compaction?br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig18.png" height="329" width="723" /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig19.png" height="339" width="653" /><br /><h2>HRegion Split</h2>最初,一个Table只有一个HRegionQ随着数据写入增加Q如果一个HRegion到达一定的大小Q就需要Split成两个HRegionQ这个大由hbase.hregion.max.filesize指定Q默认ؓ10GB。当splitӞ两个新的HRegion会在同一个HRegionServer中创建,它们各自包含父HRegion一半的数据Q当Split完成后,父HRegion会下U,而新的两个子HRegion会向HMaster注册上线Q处于负载均衡的考虑Q这两个新的HRegion可能会被HMaster分配到其他的HRegionServer中。关于Split的详l信息,可以参考这文章:<a >《Apache HBase Region Splitting and Merging?/a>?br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig21.png" height="361" width="675" /><br /><h2>HRegion负蝲均衡</h2>在HRegion Split后,两个新的HRegion最初会和之前的父HRegion在相同的HRegionServer上,Z负蝲均衡的考虑QHMaster可能会将其中的一个甚至两个重新分配的其他的HRegionServer中,此时会引h些HRegionServer处理的数据在其他节点上,直到下一ơMajor Compaction数据从q端的节点移动到本地节点?br /><br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig22.png" height="358" width="714" /><br /><h2>HRegionServer Recovery</h2>当一台HRegionServer宕机Ӟ׃它不再发送HeartbeatlZooKeeper而被监测刎ͼ此时ZooKeeper会通知HMasterQHMaster会检到哪台HRegionServer宕机Q它宕机的HRegionServer中的HRegion重新分配l其他的HRegionServerQ同时HMaster会把宕机的HRegionServer相关的WAL拆分分配l相应的HRegionServer(拆分出的WAL文g写入对应的目的HRegionServer的WAL目录中,qƈ写入对应的DataNode中)Q从而这些HRegionServer可以Replay分到的WAL来重建MemStore?br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig25.png" height="368" width="708" /><br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig26.png" height="378" width="724" /><br /><h2>HBase架构单ȝ</h2>在NoSQL中,存在著名的CAP理论Q即Consistency、Availability、Partition Tolerance不可全得Q目前市Z基本上的NoSQL都采用Partition Tolerance以实现数据得水^扩展Q来处理Relational DataBase遇到的无法处理数据量太大的问题,或引L性能问题。因而只有剩下C和A可以选择。HBase在两者之间选择了ConsistencyQ然后用多个HMaster以及支持HRegionServer的failure监控、ZooKeeper引入作ؓ协调者等各种手段来解决Availability问题Q然而当|络的Split-Brain(Network Partition)发生Ӟ它还是无法完全解决Availability的问题。从q个角度上,Cassandra选择了AQ即它在|络Split-Brain时还是能正常写,而用其他技术来解决Consistency的问题,如读的时候触发Consistency判断和处理。这是设计上的限制?br /><br />从实C的优点:<br /><ol><li>HBase采用Z致性模型,在一个写q回后,保证所有的读都d相同的数据?/li><li>通过HRegion动态Split和Merge实现自动扩展Qƈ使用HDFS提供的多个数据备份功能,实现高可用性?/li><li>采用HRegionServer和DataNodeq行在相同的服务器上实现数据的本地化Q提升读写性能Qƈ减少|络压力?/li><li>内徏HRegionServer的宕动恢复。采用WAL来Replayq未持久化到HDFS的数据?/li><li>可以无缝的和Hadoop/MapReduce集成?br /></li></ol>实现上的~点Q?br /><ol><li>WAL的Replayq程可能会很慢?/li><li>N恢复比较复杂Q也会比较慢?/li><li>Major Compaction会引起IO Storm?/li><li>。。。?br /></li></ol><h2>参考:</h2> https://www.mapr.com/blog/in-depth-look-hbase-architecture#.VdNSN6Yp3qx<br /> http://jimbojw.com/wiki/index.php?title=Understanding_Hbase_and_BigTable<br /> http://hbase.apache.org/book.html <br /> http://www.searchtb.com/2011/01/understanding-hbase.html <br /> http://research.google.com/archive/bigtable-osdi06.pdf<img src ="http://m.tkk7.com/DLevin/aggbug/426950.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.tkk7.com/DLevin/" target="_blank">DLevin</a> 2015-08-22 19:40 <a href="http://m.tkk7.com/DLevin/archive/2015/08/22/426950.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入HBase架构解析Q一Q?/title><link>http://m.tkk7.com/DLevin/archive/2015/08/22/426877.html</link><dc:creator>DLevin</dc:creator><author>DLevin</author><pubDate>Sat, 22 Aug 2015 09:44:00 GMT</pubDate><guid>http://m.tkk7.com/DLevin/archive/2015/08/22/426877.html</guid><wfw:comment>http://m.tkk7.com/DLevin/comments/426877.html</wfw:comment><comments>http://m.tkk7.com/DLevin/archive/2015/08/22/426877.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.tkk7.com/DLevin/comments/commentRss/426877.html</wfw:commentRss><trackback:ping>http://m.tkk7.com/DLevin/services/trackbacks/426877.html</trackback:ping><description><![CDATA[<h2>前记</h2> 公司内部使用的是MapR版本的Hadoop生态系l,因而从MapR的官|看Cq篇文文章:<a >An In-Depth Look at the HBase Architecture</a>Q原本想译全文Q然而如果翻译就需要各U咬文嚼字,太麻烦,因而本文大部分使用了自q语言Qƈ且加入了其他资源的参考理解以及本p源码时对其的理解Q属于半译、半原创吧?br /> <h2>HBase架构l成</h2> HBase采用Master/Slave架构搭徏集群Q它隶属于Hadoop生态系l,׃下类型节点组成:HMaster节点、HRegionServer节点、ZooKeeper集群Q而在底层Q它数据存储于HDFS中,因而涉及到HDFS的NameNode、DataNode{,Ml构如下Q?br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArch1.jpg" height="389" width="603" /><br /> 其中<strong>HMaster节点</strong>用于Q?br /> <ol> <li>理HRegionServerQ实现其负蝲均衡?/li> <li>理和分配HRegionQ比如在HRegion split时分配新的HRegionQ在HRegionServer退出时q移其内的HRegion到其他HRegionServer上?/li> <li>实现DDL操作QData Definition LanguageQnamespace和table的增删改Qcolumn familiy的增删改{)?/li> <li>理namespace和table的元数据Q实际存储在HDFS上)?/li> <li>权限控制QACLQ?/li> </ol> <strong>HRegionServer节点</strong>用于Q?br /> <ol> <li>存放和管理本地HRegion?/li> <li>dHDFSQ管理Table中的数据?/li> <li>Client直接通过HRegionServerd数据Q从HMaster中获取元数据Q找到RowKey所在的HRegion/HRegionServer后)?/li> </ol> <strong>ZooKeeper集群是协调系l?/strong>Q用于:<br /> <ol> <li>存放整个 HBase集群的元数据以及集群的状态信息?/li> <li>实现HMasterM节点的failover?/li> </ol> HBase Client通过RPC方式和HMaster、HRegionServer通信Q一个HRegionServer可以存放1000个HRegionQ底层Table数据存储于HDFS中,而HRegion所处理的数据尽量和数据所在的DataNode在一P实现数据的本地化Q数据本地化q不是总能实现Q比如在HRegionUd(如因Split)Ӟ需要等下一ơCompact才能l箋回到本地化?br /> <br /> 本着半翻译的原则Q再贴一个《An In-Depth Look At The HBase Architecture》的架构图:<br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig1.png" height="343" width="632" /><br /> q个架构图比较清晰的表达了HMaster和NameNode都支持多个热备䆾Q用ZooKeeper来做协调QZooKeeperq不是云般神U,它一般由三台机器l成一个集,内部使用PAXOS法支持三台Server中的一台宕机,也有使用五台机器的,此时则可以支持同时两台宕机,既少于半数的宕机Q然而随着机器的增加,它的性能也会下降QRegionServer和DataNode一般会攑֜相同的Server上实现数据的本地化?br /> <h2>HRegion</h2> HBase使用RowKey表水^切割成多个HRegionQ从HMaster的角度,每个HRegion都纪录了它的StartKey和EndKeyQ第一个HRegion的StartKey为空Q最后一个HRegion的EndKey为空Q,׃RowKey是排序的Q因而Client可以通过HMaster快速的定位每个RowKey在哪个HRegion中。HRegion由HMaster分配到相应的HRegionServer中,然后由HRegionServer负责HRegion的启动和理Q和Client的通信Q负责数据的?使用HDFS)。每个HRegionServer可以同时理1000个左右的HRegionQ这个数字怎么来的Q没有从代码中看到限ӞN是出于经验?过1000个会引v性能问题Q?strong>来回{这个问?/strong>Q感觉这?000的数字是从BigTable的论文中来的Q? Implementation节)QEach tablet server manages a set of tablets(typically we have somewhere between ten to a thousand tablets per tablet server)Q?br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig2.png" height="337" width="724" /><br /> <h2>HMaster</h2> HMaster没有单点故障问题Q可以启动多个HMasterQ通过ZooKeeper的Master Election机制保证同时只有一个HMasterZActive状态,其他的HMaster则处于热备䆾状态。一般情况下会启动两个HMasterQ非Active的HMaster会定期的和Active HMaster通信以获取其最新状态,从而保证它是实时更新的Q因而如果启动了多个HMaster反而增加了Active HMaster的负担。前文已l介l过了HMaster的主要用于HRegion的分配和理QDDL(Data Definition LanguageQ既Table的新建、删除、修改等)的实现等Q既它主要有两方面的职责Q?br /> <ol> <li>协调HRegionServer <ol> <li>启动时HRegion的分配,以及负蝲均衡和修复时HRegion的重新分配?/li> <li>监控集群中所有HRegionServer的状?通过Heartbeat和监听ZooKeeper中的状??br /> </li> </ol> </li> <li>Admin职能 <ol> <li>创徏、删除、修改Table的定义?br /> </li> </ol> </li> </ol> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig3.png" /><br /> <h2> ZooKeeperQ协调?/h2> ZooKeeper为HBase集群提供协调服务Q它理着HMaster和HRegionServer的状?available/alive{?Qƈ且会在它们宕机时通知lHMasterQ从而HMaster可以实现HMaster之间的failoverQ或对宕机的HRegionServer中的HRegion集合的修?它们分配给其他的HRegionServer)。ZooKeeper集群本n使用一致性协?PAXOS协议)保证每个节点状态的一致性?br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig4.png" height="318" width="703" /><br /> <h2>How The Components Work Together</h2> ZooKeeper协调集群所有节点的׃n信息Q在HMaster和HRegionServerq接到ZooKeeper后创建Ephemeral节点Qƈ使用Heartbeat机制l持q个节点的存zȝ态,如果某个Ephemeral节点实效Q则HMaster会收到通知Qƈ做相应的处理?br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig5.png" height="329" width="722" /><br /> 另外QHMaster通过监听ZooKeeper中的Ephemeral节点(默认Q?hbase/rs/*)来监控HRegionServer的加入和宕机。在W一个HMasterq接到ZooKeeper时会创徏Ephemeral节点(默认Q?hbasae/master)来表CActive的HMasterQ其后加q来的HMaster则监听该Ephemeral节点Q如果当前Active的HMaster宕机Q则该节Ҏ失,因而其他HMaster得到通知Q而将自n转换成Active的HMasterQ在变ؓActive的HMaster之前Q它会创建在/hbase/back-masters/下创qEphemeral节点?br /> <h3> HBase的第一ơ读?/h3> 在HBase 0.96以前QHBase有两个特D的TableQ?ROOT-?META.Q如<a >BigTable</a>中的设计Q,其中-ROOT- Table的位|存储在ZooKeeperQ它存储?META. Table的RegionInfo信息Qƈ且它只能存在一个HRegionQ?META. Table则存储了用户Table的RegionInfo信息Q它可以被切分成多个HRegionQ因而对W一ơ访问用户TableӞ首先从ZooKeeper中读?ROOT- Table所在HRegionServerQ然后从该HRegionServer中根据请求的TableNameQRowKeyd.META. Table所在HRegionServerQ最后从该HRegionServer中读?META. Table的内容而获取此ơ请求需要访问的HRegion所在的位置Q然后访问该HRegionSever获取h的数据,q需要三ơ请求才能找到用户Table所在的位置Q然后第四次h开始获取真正的数据。当然ؓ了提升性能Q客L会缓?ROOT- Table位置以及-ROOT-/.META. Table的内宏V如下图所C:<br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/image0030.jpg" height="228" width="399" /><br /> 可是即客户端有~存Q在初始阶段需要三ơ请求才能直到用户Table真正所在的位置也是性能低下的,而且真的有必要支持那么多的HRegion吗?或许对Googleq样的公司来说是需要的Q但是对一般的集群来说好像q没有这个必要。在BigTable的论文中_每行METADATA存储1KB左右数据Q中{大的Tablet(HRegion)?28MB左右Q?层位|的Schema设计可以支持2^34个Tablet(HRegion)。即使去?ROOT- TableQ也q可以支?^17(131072)个HRegionQ?如果每个HRegionq是128MBQ那是16TBQ这个貌g够大Q但是现在的HRegion的最大大都会设|的比较大,比如我们讄?GBQ此时支持的大小则变成了4PBQ对一般的集群来说已经够了Q因而在HBase 0.96以后L?ROOT- TableQ只剩下q个Ҏ的目录表叫做Meta Table(hbase:meta)Q它存储了集中所有用户HRegion的位|信息,而ZooKeeper的节点中(/hbase/meta-region-server)存储的则直接是这个Meta Table的位|,q且q个Meta Table如以前的-ROOT- Table一h不可split的。这P客户端在W一ơ访问用户Table的流E就变成了:<br /> <ol> <li>从ZooKeeper(/hbase/meta-region-server)中获取hbase:meta的位|(HRegionServer的位|)Q缓存该位置信息?/li> <li>从HRegionServer中查询用户Table对应h的RowKey所在的HRegionServerQ缓存该位置信息?/li> <li>从查询到HRegionServer中读取Row?/li> </ol> 从这个过E中Q我们发现客户会~存q些位置信息Q然而第二步它只是缓存当前RowKey对应的HRegion的位|,因而如果下一个要查的RowKey不在同一个HRegion中,则需要l查询hbase:meta所在的HRegionQ然而随着旉的推U,客户端缓存的位置信息来多Q以至于不需要再ơ查找hbase:meta Table的信息,除非某个HRegion因ؓ宕机或Split被移动,此时需要重新查询ƈ且更新缓存?br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig6.png" height="356" width="590" /><br /> <h3> hbase:meta?/h3> hbase:meta表存储了所有用户HRegion的位|信息,它的RowKey是:tableName,regionStartKey,regionId,replicaId{,它只有info列族Q这个列族包含三个列Q他们分别是Qinfo:regioninfo列是RegionInfo的proto格式QregionId,tableName,startKey,endKey,offline,split,replicaIdQinfo:server格式QHRegionServer对应的server:portQinfo:serverstartcode格式是HRegionServer的启动时间戳?br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig7.png" height="362" width="736" /><br /> <h2>HRegionServer详解</h2> HRegionServer一般和DataNode在同一台机器上q行Q实现数据的本地性。HRegionServer包含多个HRegionQ由WAL(HLog)、BlockCache、MemStore、HFilel成?br /> <ol> <li><strong>WAL即Write Ahead Log</strong>Q在早期版本中称为HLogQ它是HDFS上的一个文Ӟ如其名字所表示的,所有写操作都会先保证将数据写入q个Log文g后,才会真正更新MemStoreQ最后写入HFile中。采用这U模式,可以保证HRegionServer宕机后,我们依然可以从该Log文g中读取数据,Replay所有的操作Q而不至于数据丢失。这个Log文g会定期Roll出新的文件而删除旧的文?那些已持久化到HFile中的Log可以删除)。WAL文g存储?hbase/WALs/${HRegionServer_Name}的目录中(?.94之前Q存储在/hbase/.logs/目录?Q一般一个HRegionServer只有一个WAL实例Q也是说一个HRegionServer的所有WAL写都是串行的(像log4j的日志写也是串行?Q这当然会引h能问题Q因而在HBase 1.0之后Q通过<a >HBASE-5699</a>实现了多个WALq行?MultiWAL)Q该实现采用HDFS的多个管道写Q以单个HRegion为单位。关于WAL可以参考Wikipedia?a >Write-Ahead Logging</a>。顺便吐槽一句,英文版的l基癄竟然能毫无压力的正常讉K了,q是某个GFW的疏忽还是以后的常态?</li> <li><strong>BlockCache是一个读~存</strong>Q即“引用局部?#8221;原理Q也应用于CPUQ?a >分空间局部性和旉局部?/a>Q空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时d需要的数据在其附近Q时间局部性是指某个数据在被访问过一ơ后Q它有很大的概率在不久的来会被再次的访问)Q将数据预读取到内存中,以提升读的性能。HBase中提供两UBlockCache的实玎ͼ默认on-heap LruBlockCache和BucketCache(通常是off-heap)。通常BucketCache的性能要差于LruBlockCacheQ然而由于GC的媄响,LruBlockCache的gq会变的不稳定,而BucketCache׃是自q理BlockCacheQ而不需要GCQ因而它的gq通常比较E_Q这也是有些时候需要选用BucketCache的原因。这文?a >BlockCache101</a>对on-heap和off-heap的BlockCache做了详细的比较?/li><strong> </strong><li><strong>HRegion是一个Table中的一个Region在一个HRegionServer中的表达</strong>。一个Table可以有一个或多个RegionQ他们可以在一个相同的HRegionServer上,也可以分布在不同的HRegionServer上,一个HRegionServer可以有多个HRegionQ他们分别属于不同的Table。HRegion由多个Store(HStore)构成Q每个HStore对应了一个Table在这个HRegion中的一个Column FamilyQ即每个Column Family是一个集中的存储单元Q因而最好将h相近IOҎ的Column存储在一个Column FamilyQ以实现高效d(数据局部性原理,可以提高~存的命中率)。HStore是HBase中存储的核心Q它实现了读写HDFS功能Q一个HStore׃个MemStore ?个或多个StoreFilel成?br /> <ol> <li><strong>MemStore是一个写~存</strong>(In Memory Sorted Buffer)Q所有数据的写在完成WAL日志写后Q会 写入MemStore中,由MemStoreҎ一定的法数据Flush到地层HDFS文g?HFile)Q通常每个HRegion中的每个 Column Family有一个自qMemStore?/li> <li><strong>HFile(StoreFile) 用于存储HBase的数?Cell/KeyValue)</strong>。在HFile中的数据是按RowKey、Column Family、Column排序Q对相同的Cell(卌三个值都一?Q则按timestamp倒序排列?/li> </ol> </li> </ol> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig8.png" /><br /> 虽然上面q张囑ֱ现的是最新的HRegionServer的架?但是q不是那么的_)Q但是我一直比较喜Ƣ看以下q张图,即它展现的应该?.94以前的架构?br /> <img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/image0060.jpg" height="347" width="553" /><br /> <h3> HRegionServer中数据写程图解</h3> 当客L发v一个PuthӞ首先它从hbase:meta表中查出该Put数据最l需要去的HRegionServer。然后客LPuth发送给相应的HRegionServerQ在HRegionServer中它首先会将该Put操作写入WAL日志文g?Flush到磁盘中)?br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig9.png" height="363" width="716" /><br /> 写完WAL日志文g后,HRegionServerҎPut中的TableName和RowKey扑ֈ对应的HRegionQƈҎColumn Family扑ֈ对应的HStoreQƈPut写入到该HStore的MemStore中。此时写成功Qƈq回通知客户端?br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig10.png" height="298" width="664" /><br /><h3>MemStore Flush<br /></h3>MemStore是一个In Memory Sorted BufferQ在每个HStore中都有一个MemStoreQ即它是一个HRegion的一个Column Family对应一个实例。它的排列顺序以RowKey、Column Family、Column的顺序以及Timestamp的倒序Q如下所C:<br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig11.png" height="351" width="719" /><br />每一ơPut/Deleteh都是先写入到MemStore中,当MemStore满后会Flush成一个新的StoreFile(底层实现是HFile)Q即一个HStore(Column Family)可以?个或多个StoreFile(HFile)。有以下三种情况可以触发MemStore的Flush动作Q?strong>需要注意的是MemStore的最Flush单元是HRegion而不是单个MemStore</strong>。据说这是Column Family有个数限制的其中一个原因,估计是因为太多的Column Family一起Flush会引h能问题Q具体原因有待考证?br /><ol><li>当一个HRegion中的所有MemStore的大d过了hbase.hregion.memstore.flush.size的大,默认128MB。此时当前的HRegion中所有的MemStore会Flush到HDFS中?/li><li>当全局MemStore的大超q了hbase.regionserver.global.memstore.upperLimit的大,默认40Q的内存使用量。此时当前HRegionServer中所有HRegion中的MemStore都会Flush到HDFS中,Flush序是MemStore大小的倒序Q一个HRegion中所有MemStored作ؓ该HRegion的MemStore的大还是选取最大的MemStore作ؓ参考?有待考证Q,直到M的MemStore使用量低于hbase.regionserver.global.memstore.lowerLimitQ默?8%的内存用量?/li><li>当前HRegionServer中WAL的大超q了hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs的数量,当前HRegionServer中所有HRegion中的MemStore都会Flush到HDFS中,Flush使用旉序Q最早的MemStore先Flush直到WAL的数量少于hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs?a >q里</a>说这两个怹的默认大是2GBQ查代码Qhbase.regionserver.max.logs默认值是32Q而hbase.regionserver.hlog.blocksize是HDFS的默认blocksizeQ?2MB。但不管怎么P因ؓq个大小过限制引v的Flush不是一件好事,可能引v长时间的延迟Q因而这文章给的徏议:“<strong style="color: #339966; font-family: STHeiti; font-size: medium; font-style: normal; font-variant: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 1; word-spacing: 0px; -webkit-text-stroke-width: 0px;">Hint</strong><span style="color: #339966; font-family: STHeiti; font-size: medium; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 1; word-spacing: 0px; -webkit-text-stroke-width: 0px; display: inline !important; float: none;">: keep hbase.regionserver.hlog.blocksize * hbase.regionserver.maxlogs just a bit above hbase.regionserver.global.memstore.lowerLimit * HBASE_HEAPSIZE.</span>”。ƈ且需要注意,<a >q里</a>l的描述是有错的(虽然它是官方的文??br /></li></ol>在MemStore Flushq程中,q会在尾部追加一些meta数据Q其中就包括Flush时最大的WAL sequence|以告诉HBaseq个StoreFile写入的最新数据的序列Q那么在Recover时就直到从哪里开始。在HRegion启动Ӟq个sequence会被dQƈ取最大的作ؓ下一ơ更新时的v始sequence?br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig12.png" height="248" width="622" /><br /><h2> HFile格式</h2>HBase的数据以KeyValue(Cell)的Ş式顺序的存储在HFile中,在MemStore的Flushq程中生成HFileQ由于MemStore中存储的Cell遵@相同的排列顺序,因而Flushq程是顺序写Q我们直到磁盘的序写性能很高Q因Z需要不停的Ud盘指针?br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig13.png" height="351" width="698" /><br />HFile参考BigTable的SSTable和Hadoop?a >TFile</a>实现Q从HBase开始到现在QHFilel历了三个版本,其中V2?.92引入QV3?.98引入。首先我们来看一下V1的格式:<br /><img src="http://m.tkk7.com/images/blogjava_net/dlevin/image0080.jpg" alt="" height="160" border="0" width="554" /><br />V1的HFile由多个Data Block、Meta Block、FileInfo、Data Index、Meta Index、Trailerl成Q其中Data Block是HBase的最存储单元,在前文中提到的BlockCache是ZData Block的缓存的。一个Data Block׃个魔数和一pd的KeyValue(Cell)l成Q魔数是一个随机的数字Q用于表C是一个Data BlockcdQ以快速监这个Data Block的格式,防止数据的破坏。Data Block的大可以在创徏Column Family时设|?HColumnDescriptor.setBlockSize())Q默认值是64KBQ大LBlock有利于顺序ScanQ小号Block利于随机查询Q因而需要权衡。Meta块是可选的QFileInfo是固定长度的块,它纪录了文g的一些Meta信息Q例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY{。Data Index和Meta IndexU录了每个Data块和Meta块的其实炏V未压羃时大、Key(起始RowKeyQ?{。TrailerU录了FileInfo、Data Index、Meta Index块的起始位置QData Index和Meta Index索引的数量等。其中FileInfo和Trailer是固定长度的?br /><br />HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项Qƈ且有固定的结构。我们来看看里面的具体结构:<br /><img src="http://m.tkk7.com/images/blogjava_net/dlevin/image0090.jpg" alt="" height="93" border="0" width="553" /><br />开始是两个固定长度的数|分别表示Key的长度和Value的长度。紧接着是KeyQ开始是固定长度的数|表示RowKey的长度,紧接着? RowKeyQ然后是固定长度的数|表示Family的长度,然后是FamilyQ接着是QualifierQ然后是两个固定长度的数|表示Time Stamp和Key TypeQPut/DeleteQ。Value部分没有q么复杂的结构,是Ua的二q制数据了?strong>随着HFile版本q移QKeyValue(Cell)的格式ƈ未发生太多变化,只是在V3版本Q尾部添加了一个可选的Tag数组</strong>?br /> <br />HFileV1版本的在实际使用q程中发现它占用内存多,q且Bloom File和Block Index会变的很大,而引起启动时间变ѝ其中每个HFile的Bloom Filter可以增长?00MBQ这在查询时会引h能问题Q因为每ơ查询时需要加载ƈ查询Bloom FilterQ?00MB的Bloom Filer会引起很大的延迟Q另一个,Block Index在一个HRegionServer可能会增长到d6GBQHRegionServer在启动时需要先加蝲所有这些Block IndexQ因而增加了启动旉。ؓ了解册些问题,?.92版本中引入HFileV2版本Q?br /><img src="http://m.tkk7.com/images/blogjava_net/dlevin/hfilev2.png" alt="" height="418" border="0" width="566" /><br />在这个版本中QBlock Index和Bloom FilterdCData Block中间Q而这U设计同时也减少了写的内存用量Q另外,Z提升启动速度Q在q个版本中还引入了gq读的功能,卛_HFile真正被用时才对其进行解析?br /><br />FileV3版本基本和V2版本相比Qƈ没有太大的改变,它在KeyValue(Cell)层面上添加了Tag数组的支持;q在FileInfol构中添加了和Tag相关的两个字Dc关于具体HFile格式演化介绍Q可以参?a >q里</a>?br /><br />对HFileV2格式具体分析Q它是一个多层的cB+树烦引,采用q种设计Q可以实现查找不需要读取整个文Ӟ<br /><img alt="" src="http://m.tkk7.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig14.png" height="349" width="688" /><br />Data Block中的Cell都是升序排列Q每个block都有它自qLeaf-IndexQ每个Block的最后一个Key被放入Intermediate-Index中,Root-Index指向Intermediate-Index。在HFile的末还有Bloom Filter用于快速定位那么没有在某个Data Block中的RowQTimeRange信息用于l那些用时间查询的参考。在HFile打开Ӟq些索引信息都被加蝲q保存在内存中,以增加以后的d性能?br /><br />q篇先写到q里Q未完待l。。。?br /><br /> <h2>参考:</h2> https://www.mapr.com/blog/in-depth-look-hbase-architecture#.VdNSN6Yp3qx<br /> http://jimbojw.com/wiki/index.php?title=Understanding_Hbase_and_BigTable<br /> http://hbase.apache.org/book.html <br /> http://www.searchtb.com/2011/01/understanding-hbase.html <br /> http://research.google.com/archive/bigtable-osdi06.pdf<img src ="http://m.tkk7.com/DLevin/aggbug/426877.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.tkk7.com/DLevin/" target="_blank">DLevin</a> 2015-08-22 17:44 <a href="http://m.tkk7.com/DLevin/archive/2015/08/22/426877.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss> <footer> <div class="friendship-link"> <p>лǵվܻԴȤ</p> <a href="http://m.tkk7.com/" title="亚洲av成人片在线观看">亚洲av成人片在线观看</a> <div class="friend-links"> </div> </div> </footer> վ֩ģ壺 <a href="http://aierphoto.com" target="_blank">99ƵоƷƵѹۿ</a>| <a href="http://mabaolu.com" target="_blank">ѵƵ</a>| <a href="http://zjlxblog.com" target="_blank">Դ̼ƬƵ</a>| <a href="http://xx16xx.com" target="_blank">˳޾Ʒ</a>| <a href="http://changfafangzhi.com" target="_blank">AƬƵ߹ۿ</a>| <a href="http://www84847.com" target="_blank">JIZZJIZZйٸ</a>| <a href="http://7272004.com" target="_blank">Ʒާѡ2021</a>| <a href="http://qzllw.com" target="_blank">ѴƬ߹ۿվ</a>| <a href="http://a8g8.com" target="_blank">99ƵƷƵ76</a>| <a href="http://xx2015.com" target="_blank">Ůһ</a>| <a href="http://abab14.com" target="_blank">ҹѸþӰԺ</a>| <a href="http://bdgxhome.com" target="_blank">޹Ѽһ</a>| <a href="http://see13.com" target="_blank">պavþþƷ</a>| <a href="http://wwwnewhtbook.com" target="_blank">޾߹ۿ</a>| <a href="http://cuitccol.com" target="_blank">ëƬ߹ۿ</a>| <a href="http://btztjxc.com" target="_blank">AVþWWW</a>| <a href="http://5gi555.com" target="_blank">AV뾫Ʒ</a>| <a href="http://xianfeng-motor.com" target="_blank">һaƵ</a>| <a href="http://khushkhush.com" target="_blank">ղϵ</a>| <a href="http://tttui.com" target="_blank">պѵӰַ</a>| <a href="http://bixnu.com" target="_blank">Ʒþþþþ޾Ʒ</a>| <a href="http://yangguang882.com" target="_blank">ˬָ߳Ƶ</a>| <a href="http://8x8xbu.com" target="_blank">ɫַ</a>| <a href="http://44jjy.com" target="_blank">AVһ</a>| <a href="http://xjdz8.com" target="_blank">߹ۿվ</a>| <a href="http://miliwo.com" target="_blank">޳츾߳XXXXX</a>| <a href="http://701807.com" target="_blank">޾Ʒרþͬ</a>| <a href="http://huakangweicai.com" target="_blank">ȫ߹ۿ</a>| <a href="http://sswg2.com" target="_blank">www.޳</a>| <a href="http://2255325.com" target="_blank">Av߹ۿɫ</a>| <a href="http://bjbf99.com" target="_blank">99Ƶ߹ۿ</a>| <a href="http://cqtjqcc.com" target="_blank">޹պ߳ѿ</a>| <a href="http://lcqkp.com" target="_blank">СƵѹۿ</a>| <a href="http://6132423.com" target="_blank">Ʒվ</a>| <a href="http://gzltchem.com" target="_blank">ձɫվwwwþ </a>| <a href="http://jomashopcn.com" target="_blank">޾Ʒ</a>| <a href="http://wwwby1378.com" target="_blank">պѸƬ</a>| <a href="http://ahzlgj.com" target="_blank">͵޾Ʒ͵һ</a>| <a href="http://www998xe.com" target="_blank">avƷ</a>| <a href="http://kencery.com" target="_blank">ձһ</a>| <a href="http://laochedao.com" target="_blank">þþþþùƷͬ </a>| <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> </body>