ï»??xml version="1.0" encoding="utf-8" standalone="yes"?>99亚洲一区二区,国产大片精品免费永久看nba,国产小视频在线观看http://www.aygfsteel.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://www.aygfsteel.com/DLevin/archive/2015/09/04/427031.htmlDLevinDLevinFri, 04 Sep 2015 01:40:00 GMThttp://www.aygfsteel.com/DLevin/archive/2015/09/04/427031.htmlhttp://www.aygfsteel.com/DLevin/comments/427031.htmlhttp://www.aygfsteel.com/DLevin/archive/2015/09/04/427031.html#Feedback0http://www.aygfsteel.com/DLevin/comments/commentRss/427031.htmlhttp://www.aygfsteel.com/DLevin/services/trackbacks/427031.html前记很早以前ž®±æœ‰è¯»Netty源码的打½Ž—了åQŒç„¶è€Œç¬¬ä¸€‹Æ¡å°è¯•的时候从Netty4开始,一直抓不到核心的框架流½E‹ï¼ŒåŽæ¥å› äØ“(f¨´)其他事情忙着ž®±æ”¾ä¸‹äº†ã€‚è¿™‹Æ¡è¶ç€ä¼‘假重新捡è“v˜q™ä¸ª¼‹¬éª¨å¤ß_(d¨¢)¼Œå› äØ“(f¨´)Netty3现在˜q˜åœ¨è¢«å¾ˆå¤šé¡¹ç›®ä‹É用,因而这‹Æ¡å†³å®šå…ˆä»ŽNetty3入手åQŒçž¬é—´å‘现Netty3的代码比Netty4中规中矩的多åQŒå¾ˆå¤šæ¦‚念在代码本èín中都有清晰的表达åQŒæ‰€ä»¥åŠå¤©å°±æŠŠæ•´ä¸ªæ¡†æž¶çš„骨架搞清楚了。再è¯?a >Netty4对Netty3的改˜q›æ€È»“åQŒå›žåŽ»è¯»Netty4的源码,反而觉得轻松了åQŒä¸€¿Uè±ç„¶å¼€æœ—的感觉ã€?br />
è®°å¾—åŽÕd¹´è¯»Jettyæºç çš„æ—¶å€™ï¼Œå› äØ“(f¨´)代码太庞大,òq¶ä¸”自己的HTTP Server的了解太ž®‘,因而只能自底向上的一个一个模块的叠加åQŒç›´åˆ°æœ€åŽæŠŠæ‰€ä»¥çš„æ¨¡å—˜qžæŽ¥åœ¨ä¸€èµ¯‚€Œçœ‹æ¸…它的真正核心骨架。现在读源码åQŒå¼€å§‹ä¹ (f¨¤n)惯先把骨架理清,然后延äŽ×åˆîC¸åŒçš„器官、血肉而看清整个äh体ã€?br />
本文从Reactor模式在Netty3中的应用åQŒå¼•出Netty3的整体架构以å?qi¨¢ng)控制流½E‹ï¼›ç„¶è€Œé™¤äº†Reactor模式åQŒNetty3˜q˜åœ¨ChannelPipeline中ä‹É用了Intercepting Filter模式åQŒè¿™ä¸ªæ¨¡å¼ä¹Ÿåœ¨Servletçš„Filter中成功ä‹É用,因而本文还ä¼?x¨¬)从Intercepting Filter模式出发详细介绍ChannelPipeline的设计理å¿üc(di¨£n)€‚本文假设读者已¾lå¯¹Netty有一定的了解åQŒå› è€Œä¸ä¼?x¨¬)包含过多入门介¾lï¼Œä»¥åŠ(qi¨¢ng)帮Netty做宣传的文字ã€?br />

Netty3中的Reactor模式

Reactor模式在Netty中应用非常成功,因而它也是在Netty中受大肆宣传的模式,关于Reactor模式可以详细参考本人的另一½‹‡æ–‡ç«?a href="http://www.aygfsteel.com/DLevin/archive/2015/09/02/427045.html">《Reactor模式详解ã€?/a>åQŒå¯¹Reactor模式的实现是Netty3的基本骨æžÓž¼Œå› è€Œæœ¬ž®èŠ‚ä¼?x¨¬)详¾l†ä»‹¾lReactor模式如何应用Netty3中ã€?br />
如果诅R€ŠReactor模式详解》,我们知道Reactor模式由Handle、Synchronous Event Demultiplexer、Initiation Dispatcher、Event Handler、Concrete Event Handler构成åQŒåœ¨Java的实现版本中åQŒChannel对应HandleåQŒSelector对应Synchronous Event DemultiplexeråQŒåƈ且Netty3˜q˜ä‹É用了两层ReactoråQšMain Reactor用于处理Client的连接请求,Sub Reactor用于处理和Client˜qžæŽ¥åŽçš„è¯Õd†™è¯äh±‚åQˆå…³äºŽè¿™ä¸ªæ¦‚念还可以参考Doug Lea的这½‹‡PPTåQ?a >Scalable IO In JavaåQ‰ã€‚所以我们先要解决Netty3中ä‹É用什么类实现所有的上述模块òq¶æŠŠä»–们联系在一èµïL(f¨¥ng)š„åQŒä»¥NIO实现方式ä¸ÞZ¾‹åQ?br />
模式是一¿UæŠ½è±¡ï¼Œä½†æ˜¯åœ¨å®žçŽîC¸­åQŒç»å¸æ€¼š(x¨¬)因䨓(f¨´)语言ç‰ÒŽ(gu¨©)€§ã€æ¡†æž¶å’Œæ€§èƒ½éœ€è¦è€Œåšä¸€äº›æ”¹å˜ï¼Œå› è€ŒNetty3对Reactor模式的实现有一套自å·Þqš„设计åQ?br />1. ChannelEventåQ?/strong>Reactor是基于事件编½E‹çš„åQŒå› è€Œåœ¨Netty3中ä‹É用ChannelEvent抽象的表达Netty3内部可以产生的各¿Uäº‹ä»Óž¼Œæ‰€æœ‰è¿™äº›äº‹ä»¶å¯¹è±¡åœ¨Channels帮助¾cÖM¸­äº§ç”ŸåQŒåƈ且由它将事äšg推入到ChannelPipeline中,ChannelPipeline构徏ChannelHandler½Ž¡é“åQŒChannelEvent‹¹ç»˜q™ä¸ª½Ž¡é“实现所有的业务逻辑处理。ChannelEvent对应的事件有åQšChannelStateEvent表示Channel状态的变化事äšgåQŒè€Œå¦‚果当前Channel存在Parent ChannelåQŒåˆ™è¯¥äº‹ä»¶è¿˜ä¼?x¨¬)传递到Parent Channelçš„ChannelPipeline中,如OPEN、BOUND、CONNECTED、INTEREST_OPS½{‰ï¼Œè¯¥äº‹ä»¶å¯ä»¥åœ¨å„种不同实现的Channel、ChannelSink中äñ”生;MessageEvent表示从Socket中读取数据完成、需要向Socket写数据或ChannelHandler对当前Message解析(如Decoder、Encoder)后触发的事äšgåQŒå®ƒç”±NioWorker、需要对Message做进一步处理的ChannelHandler产生åQ›WriteCompletionEvent表示写完成而触发的事äšgåQŒå®ƒç”±NioWorker产生åQ›ExceptionEvent表示在处理过½E‹ä¸­å‡ºçŽ°çš„ExceptionåQŒå®ƒå¯ä»¥å‘生在各个构件中åQŒå¦‚Channel、ChannelSink、NioWorker、ChannelHandler中;IdleStateEventç”±IdleStateHandler触发åQŒè¿™ä¹Ÿæ˜¯ä¸€ä¸ªChannelEvent可以无缝扩展的例子。注åQšåœ¨Netty4后,已经没有ChannelEvent¾c»ï¼Œæ‰€æœ‰ä¸åŒäº‹ä»‰™ƒ½ç”¨å¯¹åº”方法表达,˜q™ä¹Ÿæ„å‘³˜q™C(j¨©)hannelEvent不可扩展åQŒNetty4采用在ChannelInboundHandler中加入userEventTriggered()æ–ÒŽ(gu¨©)³•来实现这¿Uæ‰©å±•,具体可以参è€?a >˜q™é‡Œã€?br />2. ChannelHandleråQ?/strong>在Netty3中,ChannelHandler用于表示Reactor模式中的EventHandler。ChannelHandler只是一个标记接口,它有两个子接口:(x¨¬)ChannelDownstreamHandlerå’ŒChannelUpstreamHandleråQŒå…¶ä¸­ChannelDownstreamHandler表示从用户应用程序流向Netty3内部直到向Socket写数据的½Ž¡é“åQŒåœ¨Netty4ä¸­æ”¹åäØ“(f¨´)ChannelOutboundHandleråQ›ChannelUpstreamHandler表示数据从Socket˜q›å…¥Netty3内部向用户应用程序做数据处理的管道,在Netty4ä¸­æ”¹åäØ“(f¨´)ChannelInboundHandlerã€?br />3. ChannelPipelineåQ?/strong>用于½Ž¡ç†ChannelHandler的管道,每个Channel一个ChannelPipeline实例åQŒå¯ä»¥è¿è¡Œè¿‡½E‹ä¸­åŠ¨æ€çš„å‘è¿™ä¸ªç®¡é“ä¸­æ·ÕdŠ ã€åˆ é™¤ChannelHandleråQˆç”±äºŽå®žçŽ°çš„é™åˆ¶åQŒåœ¨æœ€æœ«ç«¯çš„ChannelHandler向后æ·ÕdŠ æˆ–åˆ é™¤ChannelHandler不一定在当前执行‹¹ç¨‹ä¸­è“v效,参è€?a >˜q™é‡ŒåQ‰ã€‚ChannelPipeline内部¾l´æŠ¤ä¸€ä¸ªChannelHandler的双向链表,它以Upstream(Inbound)方向为正向,Downstream(Outbound)方向为方向。ChannelPipeline采用Intercepting Filter模式实现åQŒå…·ä½“可以参è€?a href="http://www.aygfsteel.com/DLevin/archive/2015/09/03/427086.html">˜q™é‡ŒåQŒè¿™ä¸ªæ¨¡å¼çš„实现在后一节中˜q˜æ˜¯è¯¦ç»†ä»‹ç»ã€?br />4. NioSelectoråQ?/strong>Netty3使用NioSelector来存放SelectoråQˆSynchronous Event DemultiplexeråQ‰ï¼Œæ¯ä¸ªæ–îCñ”生的NIO Channel都向˜q™ä¸ªSelector注册自己以让˜q™ä¸ªSelector监听˜q™ä¸ªNIO Channel中发生的事äšgåQŒå½“事äšg发生æ—Óž¼Œè°ƒç”¨å¸®åŠ©¾c»Channels中的æ–ÒŽ(gu¨©)³•生成ChannelEvent实例åQŒå°†è¯¥äº‹ä»¶å‘送到˜q™ä¸ªNetty Channel对应的ChannelPipeline中,而交¾l™å„¾U§ChannelHandler处理。其中在向Selector注册NIO Channelæ—Óž¼ŒNetty Channel实例以Attachmentçš„åŞ式传入,该Netty Channel在其内部的NIO Channel事äšg发生æ—Óž¼Œä¼?x¨¬)以Attachmentçš„åŞ式存在于SelectionKey中,因而每个事件可以直接从˜q™ä¸ªAttachment中获取相关链的Netty ChannelåQŒåƈ从Netty Channel中获取与之相兌™”çš„ChannelPipelineåQŒè¿™ä¸ªå®žçŽ°å’ŒDoug Leaçš?a >Scalable IO In Java一模一栗÷€‚另外Netty3˜q˜é‡‡ç”¨äº†Scalable IO In Java中相同的Main Reactorå’ŒSub Reactor设计åQŒå…¶ä¸­NioSelector的两个实玎ͼš(x¨¬)Bosså³äØ“(f¨´)Main ReactoråQŒNioWorker为Sub Reactor。Boss用来处理新连接加入的事äšgåQŒNioWorker用来处理各个˜qžæŽ¥å¯¹Socket的读写事ä»Óž¼Œå…¶ä¸­Boss通过NioWorkerPool获取NioWorker实例åQŒNetty3模式使用RoundRobin方式攑֛žNioWorker实例。更形象一点的åQŒå¯ä»¥é€šè¿‡Scalable IO In Java的这张图表达åQ?br />
若与Ractor模式对应åQŒNioSelector中包含了Synchronous Event DemultiplexeråQŒè€ŒChannelPipeline中管理着所有EventHandleråQŒå› è€ŒNioSelectorå’ŒChannelPipeline共同构成了Initiation Dispatcherã€?br />5. ChannelSinkåQ?/strong>在ChannelHandler处理完成所有逻辑需要向客户端写响应数据æ—Óž¼Œä¸€èˆ¬ä¼š(x¨¬)调用Netty Channel中的writeæ–ÒŽ(gu¨©)³•åQŒç„¶è€Œåœ¨˜q™ä¸ªwriteæ–ÒŽ(gu¨©)³•实现中,它不是直接向其内部的Socket写数据,而是交给C(j¨©)hannels帮助¾c»ï¼Œå†…部创徏DownstreamMessageEventåQŒåå‘从ChannelPipeline的管道中‹¹è¿‡åŽ»ï¼Œç›´åˆ°½W¬ä¸€ä¸ªChannelHandler处理完毕åQŒæœ€åŽäº¤¾l™C(j¨©)hannelSink处理åQŒä»¥é¿å…é˜Õd¡žå†™è€Œåª„响程序的吞吐量。ChannelSinkž®†è¿™ä¸ªMessageEvent提交¾l™Netty Channel中的writeBufferQueueåQŒæœ€åŽNioWorkerä¼?x¨¬)等到这个NIO Channel已经可以处理写事件时无阻塞的向这个NIO Channel写数据。这ž®±æ˜¯ä¸Šå›¾çš„send是从SubReactor直接出发的原因ã€?br />6. ChannelåQ?/strong>Netty有自å·Þqš„Channel抽象åQŒå®ƒæ˜¯ä¸€ä¸ªèµ„源的容器åQŒåŒ…含了所有一个连接涉å?qi¨¢ng)到的所有资源的饮用åQŒå¦‚ž®è£…NIO Channel、ChannelPipeline、Boss、NioWorkerPool½{‰ã€‚另外它˜q˜æä¾›äº†å‘内部NIO Channel写响应数据的接口write、连æŽ?¾l‘定到某个地址的connect/bind接口½{‰ï¼Œä¸ªäh感觉虽然对Channel本èín来说åQŒå› ä¸ºå®ƒ?y¨­u)®è£…了NIO ChannelåQŒå› è€Œè¿™äº›æŽ¥å£å®šä¹‰åœ¨˜q™é‡Œæ˜¯åˆç†çš„åQŒä½†æ˜¯å¦‚果考虑到Netty的架构,它的Channel只是一个资源容器,有这个Channel实例ž®±å¯ä»¥å¾—到和它相关的基本所有资源,因而这¿Uwrite、connect、bind动作不应该再由它负责åQŒè€Œæ˜¯åº”该由其他类来负责,比如在Netty4中就在ChannelHandlerContextæ·ÕdŠ äº†writeæ–ÒŽ(gu¨©)³•åQŒè™½ç„¶netty4òq¶æ²¡æœ‰åˆ é™¤Channel中的write接口ã€?br />

Netty3中的Intercepting Filter模式

如果说Reactor模式是Netty3的骨æžÓž¼Œé‚£ä¹ˆIntercepting Filter模式则是Netty的中枢。Reactor模式主要应用在Netty3的内部实玎ͼŒå®ƒæ˜¯Netty3å…ähœ‰è‰¯å¥½æ€§èƒ½çš„基¼‹€åQŒè€ŒIntercepting Filter模式则是ChannelHandler¾l„合实现一个应用程序逻辑的基¼‹€åQŒåªæœ‰å¾ˆå¥½çš„理解了这个模式才能ä‹É用好NettyåQŒç”šè‡Œ™ƒ½å¾—心应手ã€?br />
关于Intercepting Filter模式的详¾l†ä»‹¾lå¯ä»¥å‚è€?a href="http://www.aygfsteel.com/DLevin/archive/2015/09/03/427086.html">˜q™é‡ŒåQŒæœ¬èŠ‚ä¸»è¦ä»‹¾lNetty3中对Intercepting Filter模式的实玎ͼŒå…¶å®žž®±æ˜¯DefaultChannelPipeline对Intercepting Filter模式的实现。在上文有提到Netty3çš„ChannelPipeline是ChannelHandler的容器,用于存储与管理ChannelHandleråQŒåŒæ—¶å®ƒåœ¨Netty3中也起到桥梁的作用,卛_®ƒæ˜¯è¿žæŽ¥Netty3内部到所有ChannelHandler的桥梁。作为ChannelPipeline的实现者DefaultChannelPipelineåQŒå®ƒä½¿ç”¨ä¸€ä¸ªChannelHandler的双向链表来存储åQŒä»¥DefaultChannelPipelineContextä½œäØ“(f¨´)节点å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以及(qi¨¢ng)ChannelHandler链表的head、tailåQŒæ‰€æœ‰ChannelEvent通过sendUpstream、sendDownstream为入口流¾læ•´ä¸ªé“¾è¡¨ï¼š(x¨¬)
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事äšgåQŒå‘后找到所有实çŽîCº†ChannelUpstreamHandler接口的ChannelHandler¾l„成链(getActualUpstreamContext()åQ?/span>åQŒè€Œå¯¹Downstream事äšgåQŒå‘前找到所有实çŽîCº†ChannelDownstreamHandler接口的ChannelHandler¾l„成链(getActualDownstreamContext()åQ‰ï¼š(x¨¬)
    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æ–ÒŽ(gu¨©)³•ž®†æŽ§åˆ¶æµ½E‹äº¤¾l™ä¸‹ä¸€ä¸? ChannelUpstreamHandler或下一个ChannelDownstreamHandleråQŒæˆ–调用Channel中的writeæ–ÒŽ(gu¨©)³•发é€? 响应消息ã€?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Œåƈ向其发送ChannelEventåQŒå¯¹äºŽDownstream链,如果到达铑ְ¾åQŒåˆ™ž®†ChannelEvent发送给C(j¨©)hannelSinkåQ?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);
    }
}
æ­£æ˜¯å› äØ“(f¨´)˜q™ä¸ªå®žçްåQŒå¦‚果在一个末ž®„¡š„ChannelUpstreamHandler中先¿U»é™¤è‡ªå·±åQŒåœ¨å‘末ž®¾æ·»åŠ ä¸€ä¸ªæ–°çš„ChannelUpstreamHandleråQŒå®ƒæ˜¯æ— æ•ˆçš„åQŒå› ä¸ºå®ƒçš„next已经在调用前ž®±å›ºå®šè®¾¾|®äØ“(f¨´)null了ã€?br />
ChannelPipelineä½œäØ“(f¨´)ChannelHandler的容器,它还提供了各¿Uå¢žã€åˆ ã€æ”¹ChannelHandler链表中的æ–ÒŽ(gu¨©)³•åQŒè€Œä¸”如果某个ChannelHandler˜q˜å®žçŽîCº†LifeCycleAwareChannelHandleråQŒåˆ™è¯¥ChannelHandler在被æ·ÕdŠ ˜q›ChannelPipeline或从中删除时都会(x¨¬)得到同志å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‹äØ“(f¨´)åQ?br />

参考:(x¨¬)

《Netty主页�/a>
《Netty源码解读åQˆå››åQ‰Netty与Reactor模式ã€?/a>
《Netty代码分析�/a>
Scalable IO In Java
Intercepting Filter Pattern


]]>
Intercepting Filter模式详解http://www.aygfsteel.com/DLevin/archive/2015/09/03/427086.htmlDLevinDLevinThu, 03 Sep 2015 14:14:00 GMThttp://www.aygfsteel.com/DLevin/archive/2015/09/03/427086.htmlhttp://www.aygfsteel.com/DLevin/comments/427086.htmlhttp://www.aygfsteel.com/DLevin/archive/2015/09/03/427086.html#Feedback0http://www.aygfsteel.com/DLevin/comments/commentRss/427086.htmlhttp://www.aygfsteel.com/DLevin/services/trackbacks/427086.html问题描述在服务器¾~–程中,通常需要处理多¿Uä¸åŒçš„è¯äh±‚åQŒåœ¨æ­£å¼å¤„理è¯äh±‚之前åQŒéœ€è¦å¯¹è¯äh±‚做一些预处理åQŒå¦‚åQ?br />
  1. ¾Uªå½•每个Client的每‹Æ¡è®¿é—®ä¿¡æ¯ã€?/li>
  2. 对Client˜q›è¡Œè®¤è¯å’ŒæŽˆæƒæ£€æŸ¥ï¼ˆAuthentication and AuthorizationåQ‰ã€?/li>
  3. ‹‚€æŸ¥å½“前Session是否合法ã€?/li>
  4. ‹‚€æŸ¥Clientçš„IP地址是否可信赖或不可信赖åQˆIP地址白名单、黑名单åQ‰ã€?/li>
  5. è¯äh±‚数据是否先要解压或解码ã€?/li>
  6. 是否支持Clientè¯äh±‚的类型、Browser版本½{‰ã€?/li>
  7. æ·ÕdŠ æ€§èƒ½ç›‘æŽ§ä¿¡æ¯ã€?/li>
  8. æ·ÕdŠ è°ƒè¯•ä¿¡æ¯ã€?/li>
  9. 保证所有异帔Rƒ½è¢«æ­£¼‹®æ•获到åQŒå¯¹æœªé¢„料到的异常做通用处理åQŒé˜²æ­¢ç»™C(j¨©)lient看到内部堆栈信息ã€?br />

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

  1. 对响应消息编码或压羃�/li>
  2. 为所有响应添加公共头、尾½{‰æ¶ˆæ¯ã€?/li>
  3. ˜q›ä¸€æ­¥Enrich响应消息åQŒå¦‚æ·ÕdŠ å…¬å…±å­—æ®µã€Session信息、Cookie信息åQŒç”šè‡›_®Œå…¨æ”¹å˜å“åº”消息等ã€?/li>
如何实现˜q™æ ·çš„需求,同时保持可扩展性、可重用性、可配置、移植性?

问题解决

要实现这¿Uéœ€æ±‚,最直观的方法就是在每个è¯äh±‚处理˜q‡ç¨‹ä¸­æ·»åŠ æ‰€æœ‰è¿™äº›é€»è¾‘åQŒäØ“(f¨´)了减ž®‘代码重复,可以ž®†æ‰€æœ‰è¿™äº›æ£€æŸ¥æå–成æ–ÒŽ(gu¨©)³•åQŒè¿™æ ·åœ¨æ¯ä¸ªå¤„理æ–ÒŽ(gu¨©)³•中调用即可:(x¨¬)
public Response service1(Request request) {
    validate(request);
    request 
= transform(request);
    Response response 
= process1(request);
    
return transform(response);
}
此时åQŒå¦‚果出现service2æ–ÒŽ(gu¨©)³•åQŒä¾ç„‰™œ€è¦æ‹·è´service1中的实现åQŒç„¶åŽå°†process1换成process2卛_¯ã€‚这个时候我们发现很多重复代码,¾l§ç®‹å¯¹å®ƒé‡æž„åQŒæ¯”如提取公共逻辑到基¾cÀLˆæ¨¡ç‰ˆæ–ÒŽ(gu¨©)³•åQŒè¿™¿Uä‹É用ç‘ô承的方式ä¼?x¨¬)引起子¾cÕd¯¹çˆ¶ç±»çš„耦合åQŒå¦‚果要让某些模块变的可配置需要有太多的判断逻辑åQŒä»£ç å˜çš„臃肿;因而可以更˜q›ä¸€æ­¥ï¼Œž®†æ‰€æœ‰å¤„理逻辑抽象å‡ÞZ¸€ä¸ªProcessor接口åQŒç„¶åŽä‹É用Decorate模式åQˆå³å¼•用优于¾l§æ‰¿åQ‰ï¼š(x¨¬)
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Œåªéœ€è¦ç‘ô承DecoratedProcessoråQŒå®žçްpreProcess或postProcessæ–ÒŽ(gu¨©)³•åQŒåˆ†åˆ«åœ¨è¯äh±‚处理之前和请求处理之后横向切入一些逻辑åQŒä¹Ÿž®±æ˜¯æ‰€è°“çš„AOP¾~–程åQšé¢å‘切面的¾~–程åQŒç„¶åŽåªéœ€è¦æ ¹æ®éœ€æ±‚æž„å»ø™¿™ä¸ªé“¾æ¡ï¼š(x¨¬)
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需要知道其后的ProcessoråQŒè¿™åœ¨æŸäº›æƒ…况下òq¶ä¸æ˜¯åœ¨èµ·åˆž®ÞqŸ¥é“的。此æ—Óž¼Œæˆ‘们需要引入Intercepting Filter模式来实现动态的改变条链ã€?br />

Intercepting Filter模式

在前文已¾læž„å»ÞZº†ä¸€æ¡ç”±å¼•用而成的Processor链,然而这是一条静态链åQŒåƈ且需要一开始就能构造出˜q™æ¡é“¾ï¼Œä¸ÞZº†è§£å†³˜q™ä¸ªé™åˆ¶åQŒæˆ‘们可以引入一个ProcessorChain来维护这条链åQŒåƈ且这条链可以动态的构徏ã€?br />
有多¿Uæ–¹å¼å¯ä»¥å®žçŽ°åÆˆæŽ§åˆ¶˜q™ä¸ªé“¾ï¼š(x¨¬)
  1. 在存储上åQŒå¯ä»¥ä‹É用数¾l„来存储所有的ProcessoråQŒProcessor在数¾l„中的位¾|®è¡¨½Cø™¿™ä¸ªProcessor在链条中的位¾|®ï¼›ä¹Ÿå¯ä»¥ç”¨é“¾è¡¨æ¥å­˜å‚¨æ‰€æœ‰çš„ProcessoråQŒæ­¤æ—¶Processor在这个链表中的位¾|®å³æ˜¯åœ¨é“¾ä¸­çš„位¾|®ã€?/li>
  2. 在抽象上åQŒå¯ä»¥æ‰€æœ‰çš„逻辑都封装在Processor中,也可以将核心逻辑使用Processor抽象åQŒè€Œå¤–围逻辑使用Filter抽象ã€?/li>
  3. 在流½E‹æŽ§åˆ¶ä¸ŠåQŒä¸€èˆ¬é€šè¿‡åœ¨Processor实现æ–ÒŽ(gu¨©)³•中直接ä‹É用ProcessorChain实例(通过参数掺入)来控制流½E‹ï¼Œåˆ©ç”¨æ–ÒŽ(gu¨©)³•调用的进栈出栈的ç‰ÒŽ(gu¨©)€§å®žçްpreProcess()å’ŒpostProcess()处理ã€?/li>
在实际中使用˜q™ä¸ªæ¨¡å¼çš„æœ‰åQšServletçš„Filter机制、Nettyçš„ChannelPipeline中、Structs2中的Interceptor中都实现了这个模式ã€?br />

Intercepting Filter模式在Servletçš„Filter中的实现åQˆJetty版本åQ?/h2>其中Servletçš„Filter在Jetty的实çŽîC¸­ä½¿ç”¨æ•°ç»„存储FilteråQŒFilter末尾可以使用Servlet实例处理真正的业务逻辑åQŒåœ¨‹¹ç¨‹æŽ§åˆ¶ä¸Šï¼Œä½¿ç”¨FilterChainçš„doFilteræ–ÒŽ(gu¨©)³•来实现。如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çš„ArrayListåQŒç”±FilterChain调用doFilter()启动调用½W¬ä¸€ä¸ªFilterçš„doFilter()æ–ÒŽ(gu¨©)³•åQŒåœ¨å®žé™…çš„Filter实现中,需要手动的调用FilterChain.doFilter()æ–ÒŽ(gu¨©)³•来启动下一个Filter的调用,利用æ–ÒŽ(gu¨©)³•调用的进栈出栈的ç‰ÒŽ(gu¨©)€§å®žçްRequestçš„pre-processå’ŒResponseçš„post-process处理。如果不调用FilterChain.doFilter()æ–ÒŽ(gu¨©)³•åQŒåˆ™è¡¨ç¤ºä¸éœ€è¦è°ƒç”¨ä¹‹åŽçš„FilteråQŒæµ½E‹ä»Žå½“前Filter˜q”回å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中实çŽîCº†Intercepting Filter模式åQŒå…¶ä¸­ChannelHandler是它的Filter。在Netty3çš„DefaultChannelPipeline中,使用一个以ChannelHandlerContext䏸™Š‚点的双向链表来存储ChannelHandleråQŒæ‰€æœ‰çš„æ¨ªåˆ‡é¢é€»è¾‘和实际业务逻辑都用ChannelHandler表达åQŒåœ¨æŽ§åˆ¶‹¹ç¨‹ä¸Šä‹É用ChannelHandlerContextçš„sendDownstream()å’ŒsendUpstream()æ–ÒŽ(gu¨©)³•来控制流½E‹ã€‚不同于Servletçš„FilteråQŒChannelHandler有两个子接口åQšChannelUpstreamHandlerå’ŒChannelDownstreamHandler分别用来è¯äh±‚˜q›å…¥æ—¶çš„处理‹¹ç¨‹å’Œå“åº”出åŽÀL—¶çš„处理流½E‹ã€‚对于Client的请求,从DefaultChannelPipelineçš„sendUpstream()æ–ÒŽ(gu¨©)³•入口å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()æ–ÒŽ(gu¨©)³•为入口:(x¨¬)
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æ–ÒŽ(gu¨©)³•ž®†æŽ§åˆ¶æµ½E‹äº¤¾l™ä¸‹ä¸€ä¸ªChannelUpstreamHandler或下一个ChannelDownstreamHandleråQŒæˆ–调用Channel中的writeæ–ÒŽ(gu¨©)³•发送响应消息ã€?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Œåƈ向其发é€? ChannelEventåQŒå¯¹äºŽDownstream链,如果到达铑ְ¾åQŒåˆ™ž®†ChannelEvent发送给C(j¨©)hannelSinkåQ?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);
    }
}
æ­£æ˜¯å› äØ“(f¨´)˜q™ä¸ªå®žçްåQŒå¦‚果在一个末ž®„¡š„ChannelUpstreamHandler中先¿U»é™¤è‡ªå·±åQŒåœ¨å‘末ž®¾æ·»åŠ ä¸€ä¸ªæ–°çš„ChannelUpstreamHandleråQŒå®ƒæ˜¯æ— æ•ˆçš„åQŒå› ä¸ºå®ƒçš„next已经在调用前ž®±å›ºå®šè®¾¾|®äØ“(f¨´)null了ã€?br />
在DefaultChannelPipelineçš„ChannelHandler链条的处理流½E‹äØ“(f¨´)åQ?br />
在这个实çŽîC¸­åQŒä¸åƒServletçš„Filter实现利用æ–ÒŽ(gu¨©)³•调用栈的˜q›å‡ºæ ˆæ¥å®Œæˆpre-processå’Œpost-processåQŒè€Œæ˜¯åœ¨è¿›åŽÈš„é“‘Ö’Œå‡ºæ¥çš„链各自调用handleUpstream()å’ŒhandleDownstream()æ–ÒŽ(gu¨©)³•åQŒè¿™æ ·ä¼š(x¨¬)引è“v调用栈其实是两条铄¡š„æ€Õd’ŒåQŒå› è€Œéœ€è¦æ³¨æ„è¿™æ¡é“¾çš„æ€»é•¿åº¦ã€‚这样做的好处是˜q™æ¡ChannelHandler的链不依赖于æ–ÒŽ(gu¨©)³•调用栈,而是在DefaultChannelPipeline内部本èín的链åQŒå› è€Œåœ¨handleUpstream()或handleDownstream()可以随时ž®†æ‰§è¡Œæµ½E‹è{发给其他¾U¿ç¨‹æˆ–线½E‹æ± åQŒåªéœ€è¦ä¿ç•™C(j¨©)hannelPipelineContext引用åQŒåœ¨å¤„理完成后用˜q™ä¸ªChannelPipelineContext重新向这条链的后一个节点发送ChannelEventåQŒç„¶è€Œç”±äºŽServletçš„Filter依赖于方法的调用栈,因而方法返回意味着所有执行完成,˜q™ç§é™åˆ¶åœ¨å¼‚步编½E‹ä¸­ä¼?x¨¬)引起问题,因而Servletåœ?.0后引入了Async的支持ã€?br />

Intercepting Filter模式的缺ç‚?/h2>½Ž€å•提一下这个模式的¾~ºç‚¹åQ?br />1. 相对传统的编½E‹æ¨¡åž‹ï¼Œ˜q™ä¸ªæ¨¡å¼æœ‰ä¸€å®šçš„学习(f¨¤n)曲线åQŒéœ€è¦å¾ˆå¥½çš„理解该模式后才能灉|´»çš„应用它来编½E‹ã€?br />2. 需要划分不同的逻辑åˆîC¸åŒçš„Filter中,˜q™æœ‰äº›æ—¶å€™åƈ不是那么å®ÒŽ(gu¨©)˜“ã€?br />3. 各个Filter之间å…׃ín数据ž®†å˜å¾—困难。在Netty3中可以自定义自己的ChannelEvent来实现自定义消息的传输,或者ä‹É用ChannelPipelineContextçš„Attachment字段来实现消息传输,而Servlet中的Filter则没有提供类似的机制åQŒå¦‚果不是可以配¾|®çš„æ•°æ®åœ¨Config中传递,其他时候的数据å…׃ín需要其他机刉™…åˆå®Œæˆã€?br />

参�/h2>Core J2EE Pattern - Intercepting Filter

]]>Reactor模式详解http://www.aygfsteel.com/DLevin/archive/2015/09/02/427045.htmlDLevinDLevinWed, 02 Sep 2015 07:14:00 GMThttp://www.aygfsteel.com/DLevin/archive/2015/09/02/427045.htmlhttp://www.aygfsteel.com/DLevin/comments/427045.htmlhttp://www.aygfsteel.com/DLevin/archive/2015/09/02/427045.html#Feedback2http://www.aygfsteel.com/DLevin/comments/commentRss/427045.htmlhttp://www.aygfsteel.com/DLevin/services/trackbacks/427045.html 前记

½W¬ä¸€‹Æ¡å¬åˆ°Reactor模式是三òq´å‰çš„æŸä¸ªæ™šä¸Šï¼Œä¸€ä¸ªå®¤å‹çªç„¶è·‘˜q‡æ¥é—®æˆ‘什么是Reactor模式åQŸæˆ‘上网查了一下,很多人都是给出NIO中的 Selector的例子,而且ž®±æ˜¯NIO里Selector多èµ\复用模型åQŒåªæ˜¯ç»™å®ƒè“v了一个比较fancy的名字而已åQŒè™½ç„¶å®ƒå¼•入了EventLoopæ¦? 念,˜q™å¯¹æˆ‘来说是新的概念åQŒä½†æ˜¯ä»£ç å®žçŽ°å´æ˜¯ä¸€æ ïL(f¨¥ng)š„åQŒå› è€Œæˆ‘òq¶æ²¡æœ‰å¾ˆåœ¨æ„˜q™ä¸ªæ¨¡å¼ã€‚然而最˜q‘开始读Netty源码åQŒè€ŒReactor模式是很多介¾lNetty的文章中被大肆宣传的模式åQŒå› è€Œæˆ‘再次问自己,什么是Reactor模式åQŸæœ¬æ–‡å°±æ˜¯å¯¹˜q™ä¸ªé—®é¢˜å…³äºŽæˆ‘的一些理解和ž®è¯•着来解½{”ã€?br />

什么是Reactor模式

要回½{”这个问题,首先当然是求助Google或WikipediaåQŒå…¶ä¸­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 HandleråQŒæœ‰å¤šä¸ªRequest Handlers
åQ›è¿™ä¸ªService Handlerä¼?x¨¬)同步的ž®†è¾“入的è¯äh±‚åQˆEventåQ‰å¤šè·¯å¤ç”¨çš„分发¾l™ç›¸åº”çš„Request Handler。如果用图来表达åQ?br />
从结构上åQŒè¿™æœ‰ç‚¹¾cÖM¼¼ç”Ÿäñ”者消费者模式,åÏxœ‰ä¸€ä¸ªæˆ–多个生äñ”者将事äšg攑օ¥ä¸€ä¸ªQueue中,而一个或多个消费者主动的从这个Queue中Poll事äšg来处理;而Reactoræ¨¡å¼åˆ™åÆˆæ²¡æœ‰Queue来做¾~“冲åQŒæ¯å½“一个Event输入到Service Handler之后åQŒè¯¥Service Handlerä¼?x¨¬)主动的æ Ò?gu¨©)®ä¸åŒçš„Event¾cÕdž‹ž®†å…¶åˆ†å‘¾l™å¯¹åº”çš„Request Handler来处理ã€?br />
更学术的åQŒè¿™½‹‡æ–‡ç« ï¼ˆReactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous EventsåQ‰ä¸Šè¯ß_(d¨¢)¼š(x¨¬)“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°å’ŒW(xu¨¦)ikipedia上的描述¾cÖM¼¼åQŒæœ‰å¤šä¸ªè¾“入源,有多个不同的EventHandleråQˆRequestHandleråQ‰æ¥å¤„理不同的请求,Initiation Dispatcher用于½Ž¡ç†EventHanderåQŒEventHandler首先要注册到Initiation Dispatcher中,然后Initiation Dispatcheræ ÒŽ(gu¨©)®è¾“入的Event分发¾l™æ³¨å†Œçš„EventHandleråQ›ç„¶è€ŒInitiation Dispatcheròq¶ä¸ç›‘听Event的到来,˜q™ä¸ªå·¥ä½œäº¤ç»™Synchronous Event Demultiplexer来处理ã€?br />

Reactor模式¾l“æž„

在解决了什么是Reactor模式后,我们来看看Reactor模式是由什么模块构成。图是一¿Uæ¯”较简‹zåŞ象的表现方式åQŒå› è€Œå…ˆä¸Šä¸€å¼ å›¾æ¥è¡¨è¾‘֐„个模块的名称和他们之间的关系åQ?br />
HandleåQ?/strong>åÏx“ä½œç³»¾lŸä¸­çš„句柄,是对资源在操作系¾lŸå±‚面上的一¿UæŠ½è±¡ï¼Œå®ƒå¯ä»¥æ˜¯æ‰“开的文件、一个连æŽ?Socket)、Timer½{‰ã€‚由于Reactor模式一般ä‹É用在¾|‘络¾~–程中,因而这里一般指Socket HandleåQŒå³ä¸€ä¸ªç½‘¾lœè¿žæŽ¥ï¼ˆConnectionåQŒåœ¨Java NIO中的ChannelåQ‰ã€‚这个Channel注册到Synchronous Event Demultiplexer中,以监听Handle中发生的事äšgåQŒå¯¹ServerSocketChannnel可以是CONNECT事äšgåQŒå¯¹SocketChannel可以是READ、WRITE、CLOSE事äšg½{‰ã€?br />Synchronous Event DemultiplexeråQ?/strong>é˜Õd¡ž½{‰å¾…一¾pÕdˆ—çš„Handle中的事äšg到来åQŒå¦‚果阻塞等待返回,卌™¡¨½Cºåœ¨˜q”回的Handle中可以不é˜Õd¡žçš„æ‰§è¡Œè¿”回的事äšg¾cÕdž‹ã€‚这个模块一般ä‹É用操作系¾lŸçš„select来实现。在Java NIO中用Selector来封装,当Selector.select()˜q”回æ—Óž¼Œå¯ä»¥è°ƒç”¨Selectorçš„selectedKeys()æ–ÒŽ(gu¨©)³•获取Set<SelectionKey>åQŒä¸€ä¸ªSelectionKey表达一个有事äšg发生的Channel以及(qi¨¢ng)该Channel上的事äšg¾cÕdž‹ã€‚上囄¡š„“Synchronous Event Demultiplexer ---notifies--> Handle”的流½E‹å¦‚果是对的åQŒé‚£å†…部实现应该是select()æ–ÒŽ(gu¨©)³•在事件到来后ä¼?x¨¬)先讄¡½®Handle的状态,然后˜q”回。不了解内部实现机制åQŒå› è€Œä¿ç•™åŽŸå›¾ã€?br />Initiation DispatcheråQ?/strong>用于½Ž¡ç†Event HandleråQŒå³EventHandler的容器,用以注册、移除EventHandler½{‰ï¼›å¦å¤–åQŒå®ƒ˜q˜ä½œä¸ºReactor模式的入口调用Synchronous Event Demultiplexerçš„selectæ–ÒŽ(gu¨©)³•以阻塞等待事件返回,当阻塞等待返回时åQŒæ ¹æ®äº‹ä»¶å‘生的Handlež®†å…¶åˆ†å‘¾l™å¯¹åº”çš„Event Handler处理åQŒå³å›žè°ƒEventHandler中的handle_event()æ–ÒŽ(gu¨©)³•ã€?br />Event HandleråQ?/strong>定义事äšg处理æ–ÒŽ(gu¨©)³•åQšhandle_event()åQŒä»¥ä¾›InitiationDispatcher回调使用ã€?br />Concrete Event HandleråQ?/strong>事äšgEventHandler接口åQŒå®žçŽ°ç‰¹å®šäº‹ä»¶å¤„ç†é€»è¾‘ã€?br />

Reactor模式模块之间的交äº?/h2> ½Ž€å•描˜qîC¸€ä¸‹Reactor各个模块之间的交互流½E‹ï¼Œå…ˆä»Žåºåˆ—囑ּ€å§‹ï¼š(x¨¬)

1. 初始化InitiationDispatcheråQŒåƈ初始化一个Handle到EventHandlerçš„Mapã€?br />2. 注册EventHandler到InitiationDispatcher中,每个EventHandler包含对相应Handle的引用,从而徏立Handle到EventHandler的映ž®„(MapåQ‰ã€?br />3. 调用InitiationDispatcherçš„handle_events()æ–ÒŽ(gu¨©)³•以启动Event Loop。在Event Loop中,调用select()æ–ÒŽ(gu¨©)³•åQˆSynchronous Event DemultiplexeråQ‰é˜»å¡žç­‰å¾…Event发生ã€?br />4. 当某个或某些Handleçš„Event发生后,select()æ–ÒŽ(gu¨©)³•˜q”回åQŒInitiationDispatcheræ ÒŽ(gu¨©)®˜q”回的Handle扑ֈ°æ³¨å†Œçš„EventHandleråQŒåƈ回调该EventHandlerçš„handle_events()æ–ÒŽ(gu¨©)³•ã€?br />5. 在EventHandlerçš„handle_events()æ–ÒŽ(gu¨©)³•中还可以向InitiationDispatcher中注册新的EventhandleråQŒæ¯”如对AcceptorEventHandler来,当有新的client˜qžæŽ¥æ—Óž¼Œå®ƒä¼š(x¨¬)产生新的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模式实现分两个部分:(x¨¬)Client˜qžæŽ¥åˆ°Logging Serverå’ŒClient向Logging Server写Log。因而对它的描述分成˜q™ä¸¤ä¸ªæ­¥éª¤ã€?br />Client˜qžæŽ¥åˆ°Logging Server

1. Logging Server注册LoggingAcceptor到InitiationDispatcherã€?br />2. Logging Server调用InitiationDispatcherçš„handle_events()æ–ÒŽ(gu¨©)³•启动ã€?br />3. InitiationDispatcher内部调用select()æ–ÒŽ(gu¨©)³•åQˆSynchronous Event DemultiplexeråQ‰ï¼Œé˜Õd¡ž½{‰å¾…Client˜qžæŽ¥ã€?br />4. Client˜qžæŽ¥åˆ°Logging Serverã€?br />5. InitiationDisptcher中的select()æ–ÒŽ(gu¨©)³•˜q”回åQŒåƈ通知LoggingAcceptor有新的连接到来ã€?
6. LoggingAcceptor调用acceptæ–ÒŽ(gu¨©)³•accept˜q™ä¸ªæ–°è¿žæŽ¥ã€?br />7. LoggingAcceptor创徏新的LoggingHandlerã€?br />8. æ–°çš„LoggingHandler注册到InitiationDispatcherä¸?同时也注册到Synchonous Event Demultiplexerä¸?åQŒç­‰å¾…Client发è“v写logè¯äh±‚ã€?br />Client向Logging Server写Log

1. Client发送log到Logging serverã€?br />2. InitiationDispatcher监测到相应的Handle中有事äšg发生åQŒè¿”回阻塞等待,æ ÒŽ(gu¨©)®˜q”回的Handle扑ֈ°LoggingHandleråQŒåƈ回调LoggingHandler中的handle_event()æ–ÒŽ(gu¨©)³•ã€?br />3. LoggingHandler中的handle_event()æ–ÒŽ(gu¨©)³•中读取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Œå³ä½¿ç”¨Selector¾cÕd°è£…了操作¾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来管理EventHandleråQŒåœ¨Doug Lea的版本中使用SelectionKey中的Attachment来存储对应的EventHandleråQŒå› è€Œä¸éœ€è¦æ³¨å†ŒEventHandler˜q™ä¸ªæ­¥éª¤åQŒæˆ–者设¾|®Attachmentž®±æ˜¯˜q™é‡Œçš„æ³¨å†Œã€‚而且在这½‹‡æ–‡ç« ä¸­åQŒDoug Lea从单¾U¿ç¨‹çš„Reactor、Acceptor、Handler实现˜q™ä¸ªæ¨¡å¼å‡ºå‘åQ›æ¼”åŒ–äØ“(f¨´)ž®†Handler中的处理逻辑多线½E‹åŒ–åQŒå®žçŽ°ç±»ä¼¼Proactor模式åQŒæ­¤æ—¶æ‰€æœ‰çš„IO操作˜q˜æ˜¯å•线½E‹çš„åQŒå› è€Œå†æ¼”化å‡ÞZ¸€ä¸ªMain Reactor来处理CONNECT事äšg(Acceptor)åQŒè€Œå¤šä¸ªSub Reactor来处理READ、WRITE½{‰äº‹ä»?Handler)åQŒè¿™äº›Sub Reactor可以分别再自å·Þqš„¾U¿ç¨‹ä¸­æ‰§è¡Œï¼Œä»Žè€Œ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的定义有两种设计思èµ\åQšsingle-method设计和multi-method设计åQ?br />A single-method interfaceåQ?/strong>它将Eventž®è£…成一个Event ObjectåQŒEventHandler只定义一个handle_event(Event event)æ–ÒŽ(gu¨©)³•。这¿Uè®¾è®¡çš„好处是有利于扩展åQŒå¯ä»¥åŽæ¥æ–¹ä¾¿çš„æ·ÕdŠ æ–°çš„Event¾cÕdž‹åQŒç„¶è€Œåœ¨å­ç±»çš„实çŽîC¸­åQŒéœ€è¦åˆ¤æ–­ä¸åŒçš„Event¾cÕdž‹è€Œå†‹Æ¡æ‰©å±•成 不同的处理方法,从这个角度上来说åQŒå®ƒåˆä¸åˆ©äºŽæ‰©å±•。另外在Netty3çš„ä‹É用过½E‹ä¸­åQŒç”±äºŽå®ƒä¸åœçš„创建ChannelEvent¾c»ï¼Œå› è€Œä¼š(x¨¬)引è“vGC的不½E›_®šã€?br />A multi-method interfaceåQ?/strong>˜q™ç§è®¾è®¡æ˜¯å°†ä¸åŒçš„Event¾cÕdž‹åœ? EventHandler中定义相应的æ–ÒŽ(gu¨©)³•。这¿Uè®¾è®¡å°±æ˜¯Netty4中ä‹É用的½{–ç•¥åQŒå…¶ä¸­ä¸€ä¸ªç›®çš„æ˜¯é¿å…ChannelEvent创徏引è“vçš„GC不稳定, 另外一个好处是它可以避免在EventHandler实现时判断不同的Event¾cÕdž‹è€Œæœ‰ä¸åŒçš„实玎ͼŒç„¶è€Œè¿™¿Uè®¾è®¡ä¼š(x¨¬)¾l™æ‰©å±•æ–°çš„Event¾cÕdž‹æ—¶å¸¦æ¥éžå¸? 大的éºÈƒ¦å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的宣传,本文慕名而学ä¹?f¨¤n)çš„Reactor模式åQŒå› è€Œå·²¾lé»˜è®¤Reactorå…ähœ‰éžå¸¸ä¼˜ç§€çš„æ€§èƒ½å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™äº›è²Œä¼¼æ˜¯å¾ˆå¤šæ¨¡å¼çš„共性:(x¨¬)解耦、提升复用性、模块化、可¿UÀL¤æ€§ã€äº‹ä»‰™©±åŠ¨ã€ç»†åŠ›åº¦çš„åÆˆå‘æŽ§åˆ¶ç­‰åQŒå› è€Œåƈ不能很好的说明什么,特别是它鼓吹的对性能的提升,˜q™é‡Œòq¶æ²¡æœ‰ä½“现出来。当然在˜q™ç¯‡æ–‡ç« çš„开头有描述˜q‡å¦ä¸€¿Uç›´è§‚的实现åQšThread-Per-ConnectionåQŒå³ä¼ ç»Ÿçš„实玎ͼŒæåˆ°äº†è¿™ä¸ªä¼ ¾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‹çš„切换、同步、数据的¿UÕdЍä¼?x¨¬)引èµäh€§èƒ½é—®é¢˜ã€‚也ž®±æ˜¯è¯´ä»Žæ€§èƒ½çš„角度上åQŒå®ƒæœ€å¤§çš„æå‡ž®±æ˜¯å‡å°‘了性能的ä‹É用,即不需要每个Client对应一个线½E‹ã€‚我的理解,其他业务逻辑处理很多时候也ä¼?x¨¬)用到相同的¾U¿ç¨‹åQŒIOè¯Õd†™æ“ä½œç›¸å¯¹CPU的操作还是要慢很多,即ä‹ÉReactor机制中每‹Æ¡è¯»å†™å·²¾lèƒ½ä¿è¯éžé˜»å¡žè¯»å†™ï¼Œ˜q™é‡Œå¯ä»¥å‡å°‘一些线½E‹çš„使用åQŒä½†æ˜¯è¿™å‡å°‘的线½E‹ä‹É用对性能有那么大的媄响吗åQŸç­”案貌似是肯定的,˜q™ç¯‡è®ºæ–‡(SEDA: Staged Event-Driven Architecture - An Architecture for Well-Conditioned, Scalable Internet Service)寚wšç€¾U¿ç¨‹çš„增长带来性能降低做了一个统计:(x¨¬)

在这个统计中åQŒæ¯ä¸ªçº¿½E‹ä»Ž¼‚ç›˜ä¸­è¯»8KB数据åQŒæ¯ä¸ªçº¿½E‹è¯»åŒä¸€ä¸ªæ–‡ä»Óž¼Œå› è€Œæ•°æ®æœ¬íw«æ˜¯¾~“存在操作系¾lŸå†…部的åQŒå³å‡å°‘IO的媄响;所有线½E‹æ˜¯äº‹å…ˆåˆ†é…çš„,不会(x¨¬)有线½E‹å¯åŠ¨çš„å½±å“åQ›æ‰€æœ‰ä“Q务在‹¹‹è¯•内部产生åQŒå› è€Œä¸ä¼?x¨¬)有¾|‘络的媄响。该¾lŸè®¡æ•°æ®˜qè¡ŒçŽ¯å¢ƒåQšLinux 2.2.14åQ?GB内存åQ?-way 500MHz Pentium III。从图中可以看出åQŒéšç€¾U¿ç¨‹çš„增长,吞吐量在¾U¿ç¨‹æ•îCØ“(f¨´)8个左右的时候开始线性下降,òq¶ä¸”åˆ?4个以后而迅速下降,其相应事件也在线½E‹è¾¾åˆ?56个后指数上升。即1+1<2åQŒå› ä¸ºçº¿½E‹åˆ‡æ¢ã€åŒæ­¥ã€æ•°æ®ç§»åŠ¨ä¼š(x¨¬)有性能损失åQŒçº¿½E‹æ•°å¢žåŠ åˆîC¸€å®šæ•°é‡æ—¶åQŒè¿™¿Uæ€§èƒ½å½±å“æ•ˆæžœä¼?x¨¬)更加明显ã€?br />
对于˜q™ç‚¹åQŒè¿˜å¯ä»¥å‚è€?a >C10K ProblemåQŒç”¨ä»¥æ˜q°åŒæ—¶æœ‰10K个Client发è“v˜qžæŽ¥çš„问题,åˆ?010òq´çš„æ—¶å€™å·²¾lå‡ºçŽ?0M Problem了ã€?br />
å½“ç„¶ä¹Ÿæœ‰äºø™¯´åQ?a >Threads are expensive are no longer valid.在不久的ž®†æ¥å¯èƒ½åˆä¼š(x¨¬)发生不同的变化,或者这个变化正在、已¾lå‘生着åQŸæ²¡æœ‰åš˜q‡æ¯”较仔¾l†çš„‹¹‹è¯•åQŒå› è€Œä¸æ•¢éšä¾¿æ–­­a€ä»€ä¹ˆï¼Œç„¶è€Œæœ¬äºø™§‚点,即ä‹É¾U¿ç¨‹å˜çš„影响òq¶æ²¡æœ‰ä»¥å‰é‚£ä¹ˆå¤§åQŒä‹É用Reactor模式åQŒç”šè‡Ïx—¶SEDA模式来减ž®‘线½E‹çš„使用åQŒå†åŠ ä¸Šå…¶ä»–è§£è€¦ã€æ¨¡å—åŒ–ã€æå‡å¤ç”¨æ€§ç­‰ä¼˜ç‚¹åQŒè¿˜æ˜¯å€¼å¾—使用的ã€?br />

Reactor模式的缺ç‚?/h2>Reactor模式的缺点貌ä¼ég¹Ÿæ˜¯æ˜¾è€Œæ˜“见的åQ?br />1. 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛åQŒåƈ且不易于调试ã€?br />2. Reactor模式需要底层的Synchronous Event Demultiplexer支持åQŒæ¯”如Java中的Selector支持åQŒæ“ä½œç³»¾lŸçš„select¾pȝ»Ÿè°ƒç”¨æ”¯æŒåQŒå¦‚果要自己实现Synchronous Event Demultiplexer可能不会(x¨¬)有那么高效ã€?br />3. Reactor模式在IOè¯Õd†™æ•°æ®æ—¶è¿˜æ˜¯åœ¨åŒä¸€ä¸ªçº¿½E‹ä¸­å®žçŽ°çš„ï¼Œå³ä‹É使用多个Reactor机制的情况下åQŒé‚£äº›å…±äº«ä¸€ä¸ªReactorçš„Channel如果出现一个长旉™—´çš„æ•°æ®è¯»å†™ï¼Œä¼?x¨¬)媄响这个Reactor中其他Channel的相应时é—ß_(d¨¢)¼Œæ¯”如在大文äšg传输æ—Óž¼ŒIO操作ž®×ƒ¼š(x¨¬)影响其他Client的相应时é—ß_(d¨¢)¼Œå› è€Œå¯¹˜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


]]>深入HBase架构解析åQˆäºŒåQ?/title><link>http://www.aygfsteel.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://www.aygfsteel.com/DLevin/archive/2015/08/22/426950.html</guid><wfw:comment>http://www.aygfsteel.com/DLevin/comments/426950.html</wfw:comment><comments>http://www.aygfsteel.com/DLevin/archive/2015/08/22/426950.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.aygfsteel.com/DLevin/comments/commentRss/426950.html</wfw:commentRss><trackback:ping>http://www.aygfsteel.com/DLevin/services/trackbacks/426950.html</trackback:ping><description><![CDATA[<h2> 前言</h2>˜q™æ˜¯<a href="http://www.aygfsteel.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也只是写入一个新的CellåQŒå®ƒå«æœ‰Delete标记åQŒè€Œä¸ä¸€å®šå°†ä¸€ä¸ªCell真正删除了,因而这ž®±å¼•起了一个问题,如何实现è¯Èš„问题åQŸè¦è§£å†³˜q™ä¸ªé—®é¢˜åQŒæˆ‘们先来分析一下相同的Cell可能存在的位¾|®ï¼š(x¨¬)首先å¯ÒŽ(gu¨©)–°å†™å…¥çš„CellåQŒå®ƒä¼?x¨¬)存在于MemStore中;然后对之前已¾lFlush到HDFS中的CellåQŒå®ƒä¼?x¨¬)存在于某个或某些StoreFile(HFile)中;最后,对刚è¯Õd–˜q‡çš„CellåQŒå®ƒå¯èƒ½å­˜åœ¨äºŽBlockCache中。既然相同的Cell可能存储在三个地方,在读取的时候只需要扫瞄这三个地方åQŒç„¶åŽå°†¾l“æžœåˆåÆˆå›_¯(Merge Read)åQŒåœ¨HBase中扫瞄的™åºåºä¾æ¬¡æ˜¯ï¼š(x¨¬)BlockCache、MemStore、StoreFile(HFile)。其中StoreFile的扫瞄先ä¼?x¨¬)ä‹É用Bloom Filter˜q‡æ×o(h¨´)那些不可能符合条件的HFileåQŒç„¶åŽä‹É用Block Index快速定位CellåQŒåƈž®†å…¶åŠ è²åˆ°BlockCache中,然后从BlockCache中读取。我们知道一个HStore可能存在多个StoreFile(HFile)åQŒæ­¤æ—‰™œ€è¦æ‰«çž„多个HFileåQŒå¦‚æžœHFile˜q‡å¤šåˆæ˜¯ä¼?x¨¬)引èµäh€§èƒ½é—®é¢˜ã€?br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig16.png" height="278" width="769" /><br /><h2>Compaction</h2>MemStore每次Flushä¼?x¨¬)创建新的HFileåQŒè€Œè¿‡å¤šçš„HFileä¼?x¨¬)引赯‚¯»çš„æ€§èƒ½é—®é¢˜åQŒé‚£ä¹ˆå¦‚何解册™¿™ä¸ªé—®é¢˜å‘¢åQŸHBase采用Compaction机制来解册™¿™ä¸ªé—®é¢˜ï¼Œæœ‰ç‚¹¾cÖM¼¼Java中的GC机制åQŒè“v初Java不停的申请内存而不释放åQŒå¢žåŠ æ€§èƒ½åQŒç„¶è€Œå¤©ä¸‹æ²¡æœ‰å…è´¹çš„午餐åQŒæœ€¾lˆæˆ‘们还是要在某个条件下åŽÀL”¶é›†åžƒåœ¾ï¼Œå¾ˆå¤šæ—¶å€™éœ€è¦Stop-The-WorldåQŒè¿™¿UStop-The-World有些时候也ä¼?x¨¬)引起很大的问题åQŒæ¯”如参考本人写çš?a href="http://www.aygfsteel.com/DLevin/archive/2015/08/01/426418.html">˜q™ç¯‡æ–‡ç« </a>åQŒå› è€Œè®¾è®¡æ˜¯ä¸€¿Uæƒè¡¡ï¼Œæ²¡æœ‰å®Œç¾Žçš„。还是类似Java中的GCåQŒåœ¨HBase中Compactionåˆ†äØ“(f¨´)两种åQšMinor Compactionå’ŒMajor Compactionã€?br /><ol><li>Minor Compaction是指选取一些小的、相é‚?c¨¨)š„StoreFilež®†ä»–们合òq¶æˆä¸€ä¸ªæ›´å¤§çš„StoreFileåQŒåœ¨˜q™ä¸ª˜q‡ç¨‹ä¸­ä¸ä¼?x¨¬)处理已¾lDeleted或Expiredçš„Cell。一‹Æ¡Minor Compaction的结果是更少òq¶ä¸”更大的StoreFile。(˜q™ä¸ªæ˜¯å¯¹çš„吗åQŸBigTable中是˜q™æ ·æè¿°Minor Compactionçš?span style="font-size: 10.000000pt; font-family: 'Times'">åQšAs 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/SSTable¿UîCØ“(f¨´)一‹Æ¡Minor CompactionåQ?/li><li>Major Compaction是指ž®†æ‰€æœ‰çš„StoreFileåˆåÆˆæˆä¸€ä¸ªStoreFileåQŒåœ¨˜q™ä¸ª˜q‡ç¨‹ä¸­ï¼Œæ ‡è®°ä¸ºDeletedçš„Cellä¼?x¨¬)被删除åQŒè€Œé‚£äº›å·²¾lExpiredçš„Cellä¼?x¨¬)被丢弃åQŒé‚£äº›å·²¾lè¶…˜q‡æœ€å¤šç‰ˆæœ¬æ•°çš„Cellä¼?x¨¬)被丢弃。一‹Æ¡Major Compaction的结果是一个HStore只有一个StoreFile存在。Major Compaction可以手动或自动触发,然而由于它ä¼?x¨¬)引起很多的IO操作而引èµäh€§èƒ½é—®é¢˜åQŒå› è€Œå®ƒä¸€èˆ¬ä¼š(x¨¬)被安排在周末、凌晨等集群比较闲的旉™—´ã€?br /></li></ol>æ›´åŞ象一点,如下面两张图分别表示Minor Compactionå’ŒMajor Compactionã€?br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig18.png" height="329" width="723" /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig19.png" height="339" width="653" /><br /><h2>HRegion Split</h2>最初,一个Table只有一个HRegionåQŒéšç€æ•°æ®å†™å…¥å¢žåŠ åQŒå¦‚果一个HRegion到达一定的大小åQŒå°±éœ€è¦Split成两个HRegionåQŒè¿™ä¸ªå¤§ž®ç”±hbase.hregion.max.filesize指定åQŒé»˜è®¤äØ“(f¨´)10GB。当splitæ—Óž¼Œä¸¤ä¸ªæ–°çš„HRegionä¼?x¨¬)在同一个HRegionServer中创建,它们各自包含父HRegion一半的数据åQŒå½“Split完成后,父HRegionä¼?x¨¬)下¾U¿ï¼Œè€Œæ–°çš„两个子HRegionä¼?x¨¬)向HMaster注册上线åQŒå¤„于负载均衡的考虑åQŒè¿™ä¸¤ä¸ªæ–°çš„HRegion可能ä¼?x¨¬)被HMaster分配到其他的HRegionServer中。关于Split的详¾l†ä¿¡æ¯ï¼Œå¯ä»¥å‚考这½‹‡æ–‡ç« ï¼š(x¨¬)<a >《Apache HBase Region Splitting and Mergingã€?/a>ã€?br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig21.png" height="361" width="675" /><br /><h2>HRegion负蝲均衡</h2>在HRegion Split后,两个新的HRegion最初会(x¨¬)和之前的父HRegion在相同的HRegionServer上,å‡ÞZºŽè´Ÿè²å‡è¡¡çš„考虑åQŒHMaster可能ä¼?x¨¬)将其中的一个甚至两个重新分配的其他的HRegionServer中,此时ä¼?x¨¬)引èµähœ‰äº›HRegionServer处理的数据在其他节点上,直到下一‹Æ¡Major Compactionž®†æ•°æ®ä»Ž˜qœç«¯çš„节点移动到本地节点ã€?br /><br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig22.png" height="358" width="714" /><br /><h2>HRegionServer Recovery</h2>当一台HRegionServer宕机æ—Óž¼Œç”׃ºŽå®ƒä¸å†å‘送Heartbeat¾l™ZooKeeper而被监测刎ͼŒæ­¤æ—¶ZooKeeperä¼?x¨¬)通知HMasteråQŒHMasterä¼?x¨¬)检‹¹‹åˆ°å“ªå°HRegionServer宕机åQŒå®ƒ?y¨­u)®†å®•机的HRegionServer中的HRegion重新分配¾l™å…¶ä»–çš„HRegionServeråQŒåŒæ—¶HMasterä¼?x¨¬)把宕机的HRegionServer相关的WAL拆分分配¾l™ç›¸åº”çš„HRegionServer(ž®†æ‹†åˆ†å‡ºçš„WALæ–‡äšg写入对应的目的HRegionServerçš„WAL目录中,òq¶åƈ写入对应的DataNode中)åQŒä»Žè€Œè¿™äº›HRegionServer可以Replay分到的WAL来重建MemStoreã€?br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig25.png" height="368" width="708" /><br /><img alt="" src="http://www.aygfsteel.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(f¨¥ng)š„æ€§èƒ½é—®é¢˜ã€‚因而只有剩下Cå’ŒA可以选择。HBase在两者之间选择了ConsistencyåQŒç„¶åŽä‹É用多个HMaster以及(qi¨¢ng)支持HRegionServerçš„failure监控、ZooKeeperå¼•å…¥ä½œäØ“(f¨´)协调者等各种手段来解决Availability问题åQŒç„¶è€Œå½“¾|‘络的Split-Brain(Network Partition)发生æ—Óž¼Œå®ƒè¿˜æ˜¯æ— æ³•完全解决Availability的问题。从˜q™ä¸ªè§’度上,Cassandra选择了AåQŒå³å®ƒåœ¨¾|‘络Split-Brain时还是能正常写,而ä‹É用其他技术来解决Consistency的问题,如读的时候触发Consistency判断和处理。这是设计上的限制ã€?br /><br />从实çŽîC¸Šçš„优点:(x¨¬)<br /><ol><li>HBase采用å¼ÞZ¸€è‡´æ€§æ¨¡åž‹ï¼Œåœ¨ä¸€ä¸ªå†™˜q”回后,保证所有的读都è¯Õdˆ°ç›¸åŒçš„æ•°æ®ã€?/li><li>通过HRegion动态Splitå’ŒMerge实现自动扩展åQŒåƈ使用HDFS提供的多个数据备份功能,实现高可用性ã€?/li><li>采用HRegionServerå’ŒDataNode˜qè¡Œåœ¨ç›¸åŒçš„æœåŠ¡å™¨ä¸Šå®žçŽ°æ•°æ®çš„æœ¬åœ°åŒ–åQŒæå‡è¯»å†™æ€§èƒ½åQŒåƈ减少¾|‘络压力ã€?/li><li>内徏HRegionServerçš„å®•æœø™‡ªåŠ¨æ¢å¤ã€‚é‡‡ç”¨WAL来Replay˜q˜æœªæŒä¹…化到HDFS的数据ã€?/li><li>可以无缝的和Hadoop/MapReduce集成ã€?br /></li></ol>实现上的¾~ºç‚¹åQ?br /><ol><li>WALçš„Replay˜q‡ç¨‹å¯èƒ½ä¼?x¨¬)很慢ã€?/li><li>çùN𾿁¢å¤æ¯”较复杂åQŒä¹Ÿä¼?x¨¬)比较慢ã€?/li><li>Major Compactionä¼?x¨¬)引起IO Stormã€?/li><li>。。。ã€?br /></li></ol><h2>参考:(x¨¬)</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://www.aygfsteel.com/DLevin/aggbug/426950.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.aygfsteel.com/DLevin/" target="_blank">DLevin</a> 2015-08-22 19:40 <a href="http://www.aygfsteel.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://www.aygfsteel.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://www.aygfsteel.com/DLevin/archive/2015/08/22/426877.html</guid><wfw:comment>http://www.aygfsteel.com/DLevin/comments/426877.html</wfw:comment><comments>http://www.aygfsteel.com/DLevin/archive/2015/08/22/426877.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.aygfsteel.com/DLevin/comments/commentRss/426877.html</wfw:commentRss><trackback:ping>http://www.aygfsteel.com/DLevin/services/trackbacks/426877.html</trackback:ping><description><![CDATA[<h2>前记</h2> 公司内部使用的是MapR版本的Hadoop生态系¾lŸï¼Œå› è€Œä»ŽMapR的官¾|‘看åˆîCº†˜q™ç¯‡æ–‡æ–‡ç« ï¼š(x¨¬)<a >An In-Depth Look at the HBase Architecture</a>åQŒåŽŸæœ¬æƒ³¾˜»è¯‘全文åQŒç„¶è€Œå¦‚果翻译就需要各¿Uå’¬æ–‡åš¼å­—,太麻烦,因而本文大部分使用了自å·Þqš„语言åQŒåƈ且加入了其他资源的参考理解以å?qi¨¢ng)æœ¬äºø™‡ªå·Þp¯»æºç æ—¶å¯¹å…¶çš„理解åQŒå±žäºŽåŠ¾˜»è¯‘、半原创吧ã€?br /> <h2>HBaseæž¶æž„¾l„成</h2> HBase采用Master/Slave架构搭徏集群åQŒå®ƒéš¶å±žäºŽHadoop生态系¾lŸï¼Œç”׃¸€ä¸‹ç±»åž‹èŠ‚ç‚¹ç»„æˆï¼š(x¨¬)HMaster节点、HRegionServer节点、ZooKeeper集群åQŒè€Œåœ¨åº•层åQŒå®ƒ?y¨­u)®†æ•°æ®å­˜å‚¨äºŽHDFS中,因而涉å?qi¨¢ng)到HDFSçš„NameNode、DataNode½{‰ï¼Œæ€ÖM½“¾l“构如下åQ?br /> <img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArch1.jpg" height="389" width="603" /><br /> 其中<strong>HMaster节点</strong>用于åQ?br /> <ol> <li>½Ž¡ç†HRegionServeråQŒå®žçŽ°å…¶è´Ÿè²å‡è¡¡ã€?/li> <li>½Ž¡ç†å’Œåˆ†é…HRegionåQŒæ¯”如在HRegion split时分配新的HRegionåQ›åœ¨HRegionServer退出时˜qç§»å…¶å†…çš„HRegion到其他HRegionServer上ã€?/li> <li>实现DDL操作åQˆData Definition LanguageåQŒnamespaceå’Œtable的增删改åQŒcolumn familiy的增删改½{‰ï¼‰ã€?/li> <li>½Ž¡ç†namespaceå’Œtable的元数据åQˆå®žé™…存储在HDFS上)ã€?/li> <li>权限控制åQˆACLåQ‰ã€?/li> </ol> <strong>HRegionServer节点</strong>用于åQ?br /> <ol> <li>存放和管理本地HRegionã€?/li> <li>è¯Õd†™HDFSåQŒç®¡ç†Table中的数据ã€?/li> <li>Client直接通过HRegionServerè¯Õd†™æ•°æ®åQˆä»ŽHMaster中获取元数据åQŒæ‰¾åˆ°RowKey所在的HRegion/HRegionServer后)ã€?/li> </ol> <strong>ZooKeeper集群是协调系¾l?/strong>åQŒç”¨äºŽï¼š(x¨¬)<br /> <ol> <li>存放整个 HBase集群的元数据以及(qi¨¢ng)集群的状态信息ã€?/li> <li>实现HMasterä¸ÖM»ŽèŠ‚ç‚¹çš„failoverã€?/li> </ol> HBase Client通过RPC方式和HMaster、HRegionServer通信åQ›ä¸€ä¸ªHRegionServer可以存放1000个HRegionåQ›åº•层Table数据存储于HDFS中,而HRegion所处理的数据尽量和数据所在的DataNodeåœ¨ä¸€èµøP¼Œå®žçŽ°æ•°æ®çš„æœ¬åœ°åŒ–åQ›æ•°æ®æœ¬åœ°åŒ–òq¶ä¸æ˜¯æ€»èƒ½å®žçްåQŒæ¯”如在HRegion¿UÕdЍ(如因Split)æ—Óž¼Œéœ€è¦ç­‰ä¸‹ä¸€‹Æ¡Compact才能¾l§ç®‹å›žåˆ°æœ¬åœ°åŒ–ã€?br /> <br /> 本着半翻译的原则åQŒå†è´´ä¸€ä¸ªã€ŠAn In-Depth Look At The HBase Architecture》的架构图:(x¨¬)<br /> <img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig1.png" height="343" width="632" /><br /> ˜q™ä¸ªæž¶æž„图比较清晰的表达了HMasterå’ŒNameNode都支持多个热备䆾åQŒä‹É用ZooKeeper来做协调åQ›ZooKeeperòq¶ä¸æ˜¯äº‘般神¿U˜ï¼Œå®ƒä¸€èˆ¬ç”±ä¸‰å°æœºå™¨¾l„成一个集¾Ÿ¤ï¼Œå†…部使用PAXOS½Ž—法支持三台Server中的一台宕机,也有使用五台机器的,此时则可以支持同时两台宕机,既少于半数的宕机åQŒç„¶è€Œéšç€æœºå™¨çš„增加,它的性能也会(x¨¬)下降åQ›RegionServerå’ŒDataNode一般会(x¨¬)攑֜¨ç›¸åŒçš„Server上实现数据的本地化ã€?br /> <h2>HRegion</h2> HBase使用RowKeyž®†è¡¨æ°´åã^切割成多个HRegionåQŒä»ŽHMaster的角度,每个HRegion都纪录了它的StartKeyå’ŒEndKeyåQˆç¬¬ä¸€ä¸ªHRegionçš„StartKey为空åQŒæœ€åŽä¸€ä¸ªHRegionçš„EndKey为空åQ‰ï¼Œç”׃ºŽRowKey是排序的åQŒå› è€ŒClient可以通过HMaster快速的定位每个RowKey在哪个HRegion中。HRegionç”±HMaster分配到相应的HRegionServer中,然后由HRegionServerè´Ÿè´£HRegion的启动和½Ž¡ç†åQŒå’ŒClient的通信åQŒè´Ÿè´£æ•°æ®çš„è¯?使用HDFS)。每个HRegionServer可以同时½Ž¡ç†1000个左右的HRegionåQˆè¿™ä¸ªæ•°å­—怎么来的åQŸæ²¡æœ‰ä»Žä»£ç ä¸­çœ‹åˆ°é™åˆÓž¼ŒéšùN“是出于经验?­‘…过1000个会(x¨¬)引è“v性能问题åQ?strong>来回½{”这个问é¢?/strong>åQšæ„Ÿè§‰è¿™ä¸?000的数字是从BigTable的论文中来的åQ? Implementation节)åQšEach 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://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig2.png" height="337" width="724" /><br /> <h2>HMaster</h2> HMaster没有单点故障问题åQŒå¯ä»¥å¯åŠ¨å¤šä¸ªHMasteråQŒé€šè¿‡ZooKeeperçš„Master Election机制保证同时只有一个HMasterå‡ÞZºŽActive状态,其他的HMaster则处于热备䆾状态。一般情况下ä¼?x¨¬)启动两个HMasteråQŒéžActiveçš„HMasterä¼?x¨¬)定期的和Active HMaster通信以获取其最新状态,从而保证它是实时更新的åQŒå› è€Œå¦‚果启动了多个HMaster反而增加了Active HMaster的负担。前文已¾lä»‹¾lè¿‡äº†HMaster的主要用于HRegion的分配和½Ž¡ç†åQŒDDL(Data Definition LanguageåQŒæ—¢Table的新建、删除、修改等)的实现等åQŒæ—¢å®ƒä¸»è¦æœ‰ä¸¤æ–¹é¢çš„职责åQ?br /> <ol> <li>协调HRegionServer <ol> <li>启动时HRegion的分配,以及(qi¨¢ng)负蝲均衡和修复时HRegion的重新分配ã€?/li> <li>监控集群中所有HRegionServer的状æ€?通过Heartbeat和监听ZooKeeper中的状æ€?ã€?br /> </li> </ol> </li> <li>Admin职能 <ol> <li>创徏、删除、修改Table的定义ã€?br /> </li> </ol> </li> </ol> <img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig3.png" /><br /> <h2> ZooKeeperåQšåè°ƒè€?/h2> ZooKeeper为HBase集群提供协调服务åQŒå®ƒ½Ž¡ç†ç€HMasterå’ŒHRegionServer的状æ€?available/alive½{?åQŒåƈ且会(x¨¬)在它们宕机时通知¾l™HMasteråQŒä»Žè€ŒHMaster可以实现HMaster之间的failoveråQŒæˆ–对宕机的HRegionServer中的HRegion集合的修å¤?ž®†å®ƒä»¬åˆ†é…ç»™å…¶ä»–çš„HRegionServer)。ZooKeeper集群本èín使用一致性协è®?PAXOS协议)保证每个节点状态的一致性ã€?br /> <img alt="" src="http://www.aygfsteel.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å’ŒHRegionServer˜qžæŽ¥åˆ°ZooKeeper后创建Ephemeral节点åQŒåƈ使用Heartbeat机制¾l´æŒ˜q™ä¸ªèŠ‚ç‚¹çš„å­˜?g¨°u)zÈŠ¶æ€ï¼Œå¦‚æžœæŸä¸ªEphemeral节点实效åQŒåˆ™HMasterä¼?x¨¬)收到通知åQŒåƈ做相应的处理ã€?br /> <img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig5.png" height="329" width="722" /><br /> 另外åQŒHMaster通过监听ZooKeeper中的Ephemeral节点(默认åQ?hbase/rs/*)来监控HRegionServer的加入和宕机。在½W¬ä¸€ä¸ªHMaster˜qžæŽ¥åˆ°ZooKeeper时会(x¨¬)创徏Ephemeral节点(默认åQ?hbasae/master)来表½CºActiveçš„HMasteråQŒå…¶åŽåŠ ˜q›æ¥çš„HMaster则监听该Ephemeral节点åQŒå¦‚果当前Activeçš„HMaster宕机åQŒåˆ™è¯¥èŠ‚ç‚ÒŽ(gu¨©)¶ˆå¤±ï¼Œå› è€Œå…¶ä»–HMaster得到通知åQŒè€Œå°†è‡ªèín转换成Activeçš„HMasteråQŒåœ¨å˜äØ“(f¨´)Activeçš„HMaster之前åQŒå®ƒä¼?x¨¬)创建åœ?hbase/back-masters/ä¸‹åˆ›å»ø™‡ªå·Þqš„Ephemeral节点ã€?br /> <h3> HBase的第一‹Æ¡è¯»å†?/h3> 在HBase 0.96以前åQŒHBase有两个特ŒDŠçš„TableåQ?ROOT-å’?META.åQˆå¦‚<a >BigTable</a>中的设计åQ‰ï¼Œå…¶ä¸­-ROOT- Table的位¾|®å­˜å‚¨åœ¨ZooKeeperåQŒå®ƒå­˜å‚¨äº?META. Tableçš„RegionInfo信息åQŒåƈ且它只能存在一个HRegionåQŒè€?META. Table则存储了用户Tableçš„RegionInfo信息åQŒå®ƒå¯ä»¥è¢«åˆ‡åˆ†æˆå¤šä¸ªHRegionåQŒå› è€Œå¯¹½W¬ä¸€‹Æ¡è®¿é—®ç”¨æˆ·Tableæ—Óž¼Œé¦–先从ZooKeeper中读å?ROOT- Table所在HRegionServeråQ›ç„¶åŽä»Žè¯¥HRegionServer中根据请求的TableNameåQŒRowKeyè¯Õd–.META. Table所在HRegionServeråQ›æœ€åŽä»Žè¯¥HRegionServer中读å?META. Table的内容而获取此‹Æ¡è¯·æ±‚需要访问的HRegion所在的位置åQŒç„¶åŽè®¿é—®è¯¥HRegionSever获取è¯äh±‚的数据,˜q™éœ€è¦ä¸‰‹Æ¡è¯·æ±‚才能找到用户Table所在的位置åQŒç„¶åŽç¬¬å››æ¬¡è¯äh±‚å¼€å§‹èŽ·å–çœŸæ­£çš„æ•°æ®ã€‚å½“ç„¶äØ“(f¨´)了提升性能åQŒå®¢æˆïL(f¨¥ng)«¯ä¼?x¨¬)缓å?ROOT- Table位置以及(qi¨¢ng)-ROOT-/.META. Table的内宏V€‚如下图所½Cºï¼š(x¨¬)<br /> <img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/image0030.jpg" height="228" width="399" /><br /> 可是即ä‹É客户端有¾~“å­˜åQŒåœ¨åˆå§‹é˜¶æ®µéœ€è¦ä¸‰‹Æ¡è¯·æ±‚才能直到用户Table真正所在的位置也是性能低下的,而且真的有必要支持那么多的HRegion吗?或许对Google˜q™æ ·çš„公司来说是需要的åQŒä½†æ˜¯å¯¹ä¸€èˆ¬çš„集群来说好像òq¶æ²¡æœ‰è¿™ä¸ªå¿…要。在BigTable的论文中è¯ß_(d¨¢)¼Œæ¯è¡ŒMETADATA存储1KB左右数据åQŒä¸­½{‰å¤§ž®çš„Tablet(HRegion)åœ?28MB左右åQ?层位¾|®çš„Schema设计可以支持2^34个Tablet(HRegion)。即使去æŽ?ROOT- TableåQŒä¹Ÿ˜q˜å¯ä»¥æ”¯æŒ?^17(131072)个HRegionåQ?如果每个HRegion˜q˜æ˜¯128MBåQŒé‚£ž®±æ˜¯16TBåQŒè¿™ä¸ªè²Œä¼ég¸å¤Ÿå¤§åQŒä½†æ˜¯çŽ°åœ¨çš„HRegion的最大大ž®éƒ½ä¼?x¨¬)设¾|®çš„æ¯”较大,比如我们讄¡½®äº?GBåQŒæ­¤æ—¶æ”¯æŒçš„大小则变成了4PBåQŒå¯¹ä¸€èˆ¬çš„集群来说已经够了åQŒå› è€Œåœ¨HBase 0.96以后åŽÀLމäº?ROOT- TableåQŒåªå‰©ä¸‹˜q™ä¸ªç‰ÒŽ(gu¨©)®Šçš„目录表叫做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‹å°±å˜æˆäº†ï¼š(x¨¬)<br /> <ol> <li>从ZooKeeper(/hbase/meta-region-server)中获取hbase:meta的位¾|®ï¼ˆHRegionServer的位¾|®ï¼‰åQŒç¼“存该位置信息ã€?/li> <li>从HRegionServer中查询用户Table对应è¯äh±‚çš„RowKey所在的HRegionServeråQŒç¼“存该位置信息ã€?/li> <li>从查询到HRegionServer中读取Rowã€?/li> </ol> 从这个过½E‹ä¸­åQŒæˆ‘们发现客户会(x¨¬)¾~“å­˜˜q™äº›ä½ç½®ä¿¡æ¯åQŒç„¶è€Œç¬¬äºŒæ­¥å®ƒåªæ˜¯ç¼“存当前RowKey对应的HRegion的位¾|®ï¼Œå› è€Œå¦‚果下一个要查的RowKey不在同一个HRegion中,则需要ç‘ô¾l­æŸ¥è¯¢hbase:meta所在的HRegionåQŒç„¶è€Œéšç€æ—‰™—´çš„æŽ¨¿U»ï¼Œå®¢æˆ·ç«¯ç¼“存的位置信息­‘Šæ¥­‘Šå¤šåQŒä»¥è‡³äºŽä¸éœ€è¦å†‹Æ¡æŸ¥æ‰¾hbase:meta Table的信息,除非某个HRegion因䨓(f¨´)宕机或Splitè¢«ç§»åŠ¨ï¼Œæ­¤æ—¶éœ€è¦é‡æ–°æŸ¥è¯¢åÆˆä¸”æ›´æ–°ç¼“å­˜ã€?br /> <img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig6.png" height="356" width="590" /><br /> <h3> hbase:metaè¡?/h3> hbase:meta表存储了所有用户HRegion的位¾|®ä¿¡æ¯ï¼Œå®ƒçš„RowKey是:(x¨¬)tableName,regionStartKey,regionId,replicaId½{‰ï¼Œå®ƒåªæœ‰info列族åQŒè¿™ä¸ªåˆ—族包含三个列åQŒä»–们分别是åQšinfo:regioninfo列是RegionInfoçš„proto格式åQšregionId,tableName,startKey,endKey,offline,split,replicaIdåQ›info:server格式åQšHRegionServer对应的server:portåQ›info:serverstartcode格式是HRegionServer的启动时间戳ã€?br /> <img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig7.png" height="362" width="736" /><br /> <h2>HRegionServer详解</h2> HRegionServer一般和DataNode在同一台机器上˜qè¡ŒåQŒå®žçŽ°æ•°æ®çš„æœ¬åœ°æ€§ã€‚HRegionServer包含多个HRegionåQŒç”±WAL(HLog)、BlockCache、MemStore、HFile¾l„成ã€?br /> <ol> <li><strong>WAL即Write Ahead Log</strong>åQŒåœ¨æ—©æœŸç‰ˆæœ¬ä¸­ç§°ä¸ºHLogåQŒå®ƒæ˜¯HDFS上的一个文ä»Óž¼Œå¦‚其名字所表示的,所有写操作都会(x¨¬)先保证将数据写入˜q™ä¸ªLogæ–‡äšg后,才会(x¨¬)真正更新MemStoreåQŒæœ€åŽå†™å…¥HFile中。采用这¿Uæ¨¡å¼ï¼Œå¯ä»¥ä¿è¯HRegionServer宕机后,我们依然可以从该Logæ–‡äšg中读取数据,Replay所有的操作åQŒè€Œä¸è‡³äºŽæ•°æ®ä¸¢å¤±ã€‚这个Logæ–‡äšgä¼?x¨¬)定期Roll出新的文件而删除旧的文ä»?那些已持久化到HFile中的Log可以删除)。WALæ–‡äšg存储åœ?hbase/WALs/${HRegionServer_Name}的目录中(åœ?.94之前åQŒå­˜å‚¨åœ¨/hbase/.logs/目录ä¸?åQŒä¸€èˆ¬ä¸€ä¸ªHRegionServer只有一个WAL实例åQŒä¹Ÿž®±æ˜¯è¯´ä¸€ä¸ªHRegionServer的所有WAL写都是串行的(ž®±åƒlog4j的日志写也是串行çš?åQŒè¿™å½“ç„¶ä¼?x¨¬)引èµäh€§èƒ½é—®é¢˜åQŒå› è€Œåœ¨HBase 1.0之后åQŒé€šè¿‡<a >HBASE-5699</a>实现了多个WALòq¶è¡Œå†?MultiWAL)åQŒè¯¥å®žçŽ°é‡‡ç”¨HDFS的多个管道写åQŒä»¥å•个HRegion为单位。关于WAL可以参考Wikipediaçš?a >Write-Ahead Logging</a>。顺便吐槽一句,英文版的¾l´åŸºç™„¡§‘竟然能毫无压力的正常讉K—®äº†ï¼Œ˜q™æ˜¯æŸä¸ªGFW的疏忽还是以后的常态?</li> <li><strong>BlockCache是一个读¾~“å­˜</strong>åQŒå³“引用局部æ€?#8221;原理åQˆä¹Ÿåº”用于CPUåQ?a >分空间局部性和旉™—´å±€éƒ¨æ€?/a>åQŒç©ºé—´å±€éƒ¨æ€§æ˜¯æŒ‡CPU在某一时刻需要某个数据,那么有很大的概率在一下时åˆÕd®ƒéœ€è¦çš„æ•°æ®åœ¨å…¶é™„è¿‘åQ›æ—¶é—´å±€éƒ¨æ€§æ˜¯æŒ‡æŸä¸ªæ•°æ®åœ¨è¢«è®¿é—®è¿‡ä¸€‹Æ¡åŽåQŒå®ƒæœ‰å¾ˆå¤§çš„æ¦‚率在不久的ž®†æ¥ä¼?x¨¬)被再次的访问)åQŒå°†æ•°æ®é¢„读取到内存中,以提升读的性能。HBase中提供两¿UBlockCache的实玎ͼš(x¨¬)默认on-heap LruBlockCacheå’ŒBucketCache(通常是off-heap)。通常BucketCache的性能要差于LruBlockCacheåQŒç„¶è€Œç”±äºŽGC的媄响,LruBlockCacheçš„åšg˜qŸä¼š(x¨¬)变的不稳定,而BucketCacheç”׃ºŽæ˜¯è‡ªå·Þq®¡ç†BlockCacheåQŒè€Œä¸éœ€è¦GCåQŒå› è€Œå®ƒçš„åšg˜qŸé€šå¸¸æ¯”较½E›_®šåQŒè¿™ä¹Ÿæ˜¯æœ‰äº›æ—¶å€™éœ€è¦é€‰ç”¨BucketCache的原因。这½‹‡æ–‡ç«?a >BlockCache101</a>对on-heapå’Œoff-heapçš„BlockCache做了详细的比较ã€?/li><strong> </strong><li><strong>HRegion是一个Table中的一个Region在一个HRegionServer中的表达</strong>。一个Table可以有一个或多个RegionåQŒä»–们可以在一个相同的HRegionServer上,也可以分布在不同的HRegionServer上,一个HRegionServer可以有多个HRegionåQŒä»–们分别属于不同的Table。HRegion由多个Store(HStore)构成åQŒæ¯ä¸ªHStore对应了一个Table在这个HRegion中的一个Column FamilyåQŒå³æ¯ä¸ªColumn Familyž®±æ˜¯ä¸€ä¸ªé›†ä¸­çš„存储单元åQŒå› è€Œæœ€å¥½å°†å…ähœ‰ç›¸è¿‘IOç‰ÒŽ(gu¨©)€§çš„Column存储在一个Column FamilyåQŒä»¥å®žçŽ°é«˜æ•ˆè¯Õd–(数据局部性原理,可以提高¾~“存的命中率)。HStore是HBase中存储的核心åQŒå®ƒå®žçŽ°äº†è¯»å†™HDFS功能åQŒä¸€ä¸ªHStoreç”׃¸€ä¸ªMemStore å’?个或多个StoreFile¾l„成ã€?br /> <ol> <li><strong>MemStore是一个写¾~“å­˜</strong>(In Memory Sorted Buffer)åQŒæ‰€æœ‰æ•°æ®çš„写在完成WAL日志写后åQŒä¼š(x¨¬) 写入MemStore中,由MemStoreæ ÒŽ(gu¨©)®ä¸€å®šçš„½Ž—法ž®†æ•°æ®Flush到地层HDFSæ–‡äšgä¸?HFile)åQŒé€šå¸¸æ¯ä¸ªHRegion中的每个 Column Family有一个自å·Þqš„MemStoreã€?/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://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig8.png" /><br /> 虽然上面˜q™å¼ å›‘Ö±•现的是最新的HRegionServer的架æž?但是òq¶ä¸æ˜¯é‚£ä¹ˆçš„¾_„¡¡®)åQŒä½†æ˜¯æˆ‘一直比较喜‹Æ¢çœ‹ä»¥ä¸‹˜q™å¼ å›¾ï¼Œå³ä‹É它展现的应该æ˜?.94以前的架构ã€?br /> <img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/image0060.jpg" height="347" width="553" /><br /> <h3> HRegionServer中数据写‹¹ç¨‹å›¾è§£</h3> 当客æˆïL(f¨¥ng)«¯å‘è“v一个Putè¯äh±‚æ—Óž¼Œé¦–先它从hbase:meta表中查出该Put数据最¾lˆéœ€è¦åŽ»çš„HRegionServer。然后客æˆïL(f¨¥ng)«¯ž®†Putè¯äh±‚发送给相应的HRegionServeråQŒåœ¨HRegionServer中它首先ä¼?x¨¬)将该Put操作写入WAL日志文äšgä¸?Flush到磁盘中)ã€?br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig9.png" height="363" width="716" /><br /> 写完W(xu¨¦)AL日志文äšg后,HRegionServeræ ÒŽ(gu¨©)®Put中的TableNameå’ŒRowKey扑ֈ°å¯¹åº”çš„HRegionåQŒåƈæ ÒŽ(gu¨©)®Column Family扑ֈ°å¯¹åº”çš„HStoreåQŒåƈž®†Put写入到该HStoreçš„MemStore中。此时写成功åQŒåƈ˜q”回通知客户端ã€?br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig10.png" height="298" width="664" /><br /><h3>MemStore Flush<br /></h3>MemStore是一个In Memory Sorted BufferåQŒåœ¨æ¯ä¸ªHStore中都有一个MemStoreåQŒå³å®ƒæ˜¯ä¸€ä¸ªHRegion的一个Column Family对应一个实例。它的排列顺序以RowKey、Column Family、Column的顺序以å?qi¨¢ng)Timestamp的倒序åQŒå¦‚下所½Cºï¼š(x¨¬)<br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig11.png" height="351" width="719" /><br />每一‹Æ¡Put/Deleteè¯äh±‚都是先写入到MemStore中,当MemStore满后ä¼?x¨¬)Flush成一个新的StoreFile(底层实现是HFile)åQŒå³ä¸€ä¸ªHStore(Column Family)可以æœ?个或多个StoreFile(HFile)。有以下三种情况可以触发MemStoreçš„Flush动作åQ?strong>需要注意的是MemStore的最ž®Flush单元是HRegion而不是单个MemStore</strong>。据说这是Column Family有个数限制的其中一个原因,估计是因为太多的Column Family一起Flushä¼?x¨¬)引èµäh€§èƒ½é—®é¢˜åQŸå…·ä½“原因有待考证ã€?br /><ol><li>当一个HRegion中的所有MemStore的大ž®æ€Õd’Œ­‘…过了hbase.hregion.memstore.flush.size的大ž®ï¼Œé»˜è®¤128MB。此时当前的HRegion中所有的MemStoreä¼?x¨¬)Flush到HDFS中ã€?/li><li>当全局MemStore的大ž®è¶…˜q‡äº†hbase.regionserver.global.memstore.upperLimit的大ž®ï¼Œé»˜è®¤40åQ…的内存使用量。此时当前HRegionServer中所有HRegion中的MemStore都会(x¨¬)Flush到HDFS中,Flush™åºåºæ˜¯MemStore大小的倒序åQˆä¸€ä¸ªHRegion中所有MemStoreæ€Õd’Œä½œäØ“(f¨´)该HRegionçš„MemStore的大ž®è¿˜æ˜¯é€‰å–最大的MemStoreä½œäØ“(f¨´)参考?有待考证åQ‰ï¼Œç›´åˆ°æ€ÖM½“çš„MemStore使用量低于hbase.regionserver.global.memstore.lowerLimitåQŒé»˜è®?8%的内存ä‹É用量ã€?/li><li>当前HRegionServer中WAL的大ž®è¶…˜q‡äº†hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs的数量,当前HRegionServer中所有HRegion中的MemStore都会(x¨¬)Flush到HDFS中,Flush使用旉™—´™åºåºåQŒæœ€æ—©çš„MemStoreå…ˆF(tu¨¢n)lush直到WAL的数量少于hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logsã€?a >˜q™é‡Œ</a>è¯´è¿™ä¸¤ä¸ªç›æ€¹˜çš„默认大ž®æ˜¯2GBåQŒæŸ¥ä»£ç åQŒhbase.regionserver.max.logs默认值是32åQŒè€Œhbase.regionserver.hlog.blocksize是HDFS的默认blocksizeåQ?2MBã€‚ä½†ä¸ç®¡æ€Žä¹ˆæ øP¼Œå› äØ“(f¨´)˜q™ä¸ªå¤§å°­‘…过限制引è“vçš„Flush不是一件好事,可能引è“v长时间的延迟åQŒå› è€Œè¿™½‹‡æ–‡ç« ç»™çš„徏议:(x¨¬)“<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 Flush˜q‡ç¨‹ä¸­ï¼Œ˜q˜ä¼š(x¨¬)在尾部追加一些meta数据åQŒå…¶ä¸­å°±åŒ…括F(xi¨¤n)lush时最大的WAL sequenceå€û|¼Œä»¥å‘Šè¯‰HBase˜q™ä¸ªStoreFile写入的最新数据的序列åQŒé‚£ä¹ˆåœ¨Recover时就直到从哪里开始。在HRegion启动æ—Óž¼Œ˜q™ä¸ªsequenceä¼?x¨¬)被è¯Õd–åQŒåÆˆå–æœ€å¤§çš„ä½œäØ“(f¨´)下一‹Æ¡æ›´æ–°æ—¶çš„è“vå§‹sequenceã€?br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig12.png" height="248" width="622" /><br /><h2> HFile格式</h2>HBase的数据以KeyValue(Cell)çš„åŞ式顺序的存储在HFile中,在MemStoreçš„Flush˜q‡ç¨‹ä¸­ç”ŸæˆHFileåQŒç”±äºŽMemStore中存储的Cell遵åó@相同的排列顺序,因而Flush˜q‡ç¨‹æ˜¯é¡ºåºå†™åQŒæˆ‘们直到磁盘的™åºåºå†™æ€§èƒ½å¾ˆé«˜åQŒå› ä¸ÞZ¸éœ€è¦ä¸åœçš„¿UÕdЍ¼‚ç›˜æŒ‡é’ˆã€?br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig13.png" height="351" width="698" /><br />HFile参考BigTableçš„SSTableå’ŒHadoopçš?a >TFile</a>实现åQŒä»ŽHBase开始到现在åQŒHFile¾låŽ†äº†ä¸‰ä¸ªç‰ˆæœ¬ï¼Œå…¶ä¸­V2åœ?.92引入åQŒV3åœ?.98引入。首先我们来看一下V1的格式:(x¨¬)<br /><img src="http://www.aygfsteel.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、Trailer¾l„成åQŒå…¶ä¸­Data Block是HBase的最ž®å­˜å‚¨å•元,在前文中提到的BlockCachež®±æ˜¯åŸÞZºŽData Block的缓存的。一个Data Blockç”׃¸€ä¸ªé­”数和一¾pÕdˆ—çš„KeyValue(Cell)¾l„成åQŒé­”数是一个随机的数字åQŒç”¨äºŽè¡¨½Cø™¿™æ˜¯ä¸€ä¸ªData Block¾cÕdž‹åQŒä»¥å¿«é€Ÿç›‘‹¹‹è¿™ä¸ªData Block的格式,防止数据的破坏。Data Block的大ž®å¯ä»¥åœ¨åˆ›å¾Column Family时设¾|?HColumnDescriptor.setBlockSize())åQŒé»˜è®¤å€¼æ˜¯64KBåQŒå¤§åïL(f¨¥ng)š„Block有利于顺序ScanåQŒå°å·Block利于随机查询åQŒå› è€Œéœ€è¦æƒè¡¡ã€‚Meta块是可选的åQŒFileInfo是固定长度的块,它纪录了文äšg的一些Meta信息åQŒä¾‹å¦‚:(x¨¬)AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY½{‰ã€‚Data Indexå’ŒMeta Index¾Uªå½•了每个Data块和Meta块的其实炏V€æœªåŽ‹ç¾ƒæ—¶å¤§ž®ã€Key(èµ·å§‹RowKeyåQ?½{‰ã€‚Trailer¾Uªå½•了FileInfo、Data Index、Meta Index块的起始位置åQŒData Indexå’ŒMeta Index索引的数量等。其中FileInfoå’ŒTrailer是固定长度的ã€?br /><br />HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项åQŒåƈ且有固定的结构。我们来看看里面的具体结构:(x¨¬)<br /><img src="http://www.aygfsteel.com/images/blogjava_net/dlevin/image0090.jpg" alt="" height="93" border="0" width="553" /><br />开始是两个固定长度的数å€û|¼Œåˆ†åˆ«è¡¨ç¤ºKey的长度和Value的长度。紧接着是KeyåQŒå¼€å§‹æ˜¯å›ºå®šé•¿åº¦çš„æ•°å€û|¼Œè¡¨ç¤ºRowKey的长度,紧接着æ˜? RowKeyåQŒç„¶åŽæ˜¯å›ºå®šé•¿åº¦çš„æ•°å€û|¼Œè¡¨ç¤ºFamily的长度,然后是FamilyåQŒæŽ¥ç€æ˜¯QualifieråQŒç„¶åŽæ˜¯ä¸¤ä¸ªå›ºå®šé•¿åº¦çš„æ•°å€û|¼Œè¡¨ç¤ºTime Stampå’ŒKey TypeåQˆPut/DeleteåQ‰ã€‚Value部分没有˜q™ä¹ˆå¤æ‚的结构,ž®±æ˜¯¾U¯çÑa(b¨³)的二˜q›åˆ¶æ•°æ®äº†ã€?strong>随着HFile版本˜qç§»åQŒKeyValue(Cell)çš„æ ¼å¼åÆˆæœªå‘ç”Ÿå¤ªå¤šå˜åŒ–ï¼Œåªæ˜¯åœ¨V3版本åQŒå°¾éƒ¨æ·»åŠ äº†ä¸€ä¸ªå¯é€‰çš„Tag数组</strong>ã€?br /> <br />HFileV1版本的在实际使用˜q‡ç¨‹ä¸­å‘现它占用内存多,òq¶ä¸”Bloom Fileå’ŒBlock Indexä¼?x¨¬)变的很大,而引起启动时间变é•ѝ€‚其中每个HFileçš„Bloom Filter可以增长åˆ?00MBåQŒè¿™åœ¨æŸ¥è¯¢æ—¶ä¼?x¨¬)引èµäh€§èƒ½é—®é¢˜åQŒå› ä¸ºæ¯‹Æ¡æŸ¥è¯¢æ—¶éœ€è¦åŠ è½½åÆˆæŸ¥è¯¢Bloom FilteråQ?00MBçš„Bloom Filerä¼?x¨¬)引起很大的延迟åQ›å¦ä¸€ä¸ªï¼ŒBlock Index在一个HRegionServer可能ä¼?x¨¬)增长到æ€Õd…±6GBåQŒHRegionServer在启动时需要先加蝲所有这些Block IndexåQŒå› è€Œå¢žåŠ äº†å¯åŠ¨æ—‰™—´ã€‚䨓(f¨´)了解册™¿™äº›é—®é¢˜ï¼Œåœ?.92版本中引入HFileV2版本åQ?br /><img src="http://www.aygfsteel.com/images/blogjava_net/dlevin/hfilev2.png" alt="" height="418" border="0" width="566" /><br />在这个版本中åQŒBlock Indexå’ŒBloom Filteræ·ÕdŠ åˆîCº†Data Block中间åQŒè€Œè¿™¿Uè®¾è®¡åŒæ—¶ä¹Ÿå‡å°‘了写的内存ä‹É用量åQ›å¦å¤–,ä¸ÞZº†æå‡å¯åŠ¨é€Ÿåº¦åQŒåœ¨˜q™ä¸ªç‰ˆæœ¬ä¸­è¿˜å¼•入了åšg˜qŸè¯»çš„功能,卛_œ¨HFile真正被ä‹É用时才对其进行解析ã€?br /><br />FileV3版本基本和V2版本相比åQŒåƈ没有太大的改变,它在KeyValue(Cell)层面上添加了Tag数组的支持;òq¶åœ¨FileInfo¾l“构中添加了和Tag相关的两个字ŒDüc(di¨£n)€‚关于具体HFile格式演化介绍åQŒå¯ä»¥å‚è€?a >˜q™é‡Œ</a>ã€?br /><br />对HFileV2格式具体分析åQŒå®ƒæ˜¯ä¸€ä¸ªå¤šå±‚çš„¾c»B+æ ?w¨¨i)烦引,采用˜q™ç§è®¾è®¡åQŒå¯ä»¥å®žçŽ°æŸ¥æ‰¾ä¸éœ€è¦è¯»å–æ•´ä¸ªæ–‡ä»Óž¼š(x¨¬)<br /><img alt="" src="http://www.aygfsteel.com/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig14.png" height="349" width="688" /><br />Data Block中的Cell都是升序排列åQŒæ¯ä¸ªblock都有它自å·Þqš„Leaf-IndexåQŒæ¯ä¸ªBlock的最后一个Key被放入Intermediate-Index中,Root-Index指向Intermediate-Index。在HFile的末ž®¾è¿˜æœ‰Bloom Filter用于快速定位那么没有在某个Data Block中的RowåQ›TimeRange信息用于¾l™é‚£äº›ä‹É用时间查询的参考。在HFile打开æ—Óž¼Œ˜q™äº›ç´¢å¼•信息都被加蝲òq¶ä¿å­˜åœ¨å†…存中,以增加以后的è¯Õd–性能ã€?br /><br />˜q™ç¯‡ž®±å…ˆå†™åˆ°˜q™é‡ŒåQŒæœªå®Œå¾…¾l­ã€‚。。ã€?br /><br /> <h2>参考:(x¨¬)</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://www.aygfsteel.com/DLevin/aggbug/426877.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.aygfsteel.com/DLevin/" target="_blank">DLevin</a> 2015-08-22 17:44 <a href="http://www.aygfsteel.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"> <a href="http://www.aygfsteel.com/" title="狠狠久久亚洲欧美专区_中文字幕亚洲综合久久202_国产精品亚洲第五区在线_日本免费网站视频">狠狠久久亚洲欧美专区_中文字幕亚洲综合久久202_国产精品亚洲第五区在线_日本免费网站视频</a> </div> </footer> Ö÷Õ¾Ö©Öë³ØÄ£°å£º <a href="http://" target="_blank">±±¾©ÊÐ</a>| <a href="http://" target="_blank">¹ÅÀËÏØ</a>| <a href="http://" target="_blank">°²¹úÊÐ</a>| <a href="http://" target="_blank">ÉêÔúÏØ</a>| <a href="http://" target="_blank">±£¾¸ÏØ</a>| <a href="http://" target="_blank">ËÕÄáÌØÓÒÆì</a>| <a href="http://" target="_blank">ÓôÄÏÏØ</a>| <a href="http://" target="_blank">ÐÇ×ÓÏØ</a>| <a href="http://" target="_blank">»ù¡ÊÐ</a>| <a href="http://" target="_blank">ÁèÔÆÏØ</a>| <a href="http://" target="_blank">Íò°²ÏØ</a>| <a href="http://" target="_blank">ººÖÐÊÐ</a>| <a href="http://" target="_blank">±±³½Çø</a>| <a href="http://" target="_blank">Ñô¹ÈÏØ</a>| <a href="http://" target="_blank">ÂåÂ¡ÏØ</a>| <a href="http://" target="_blank">׿ÄáÏØ</a>| <a href="http://" target="_blank">½ÌÓý</a>| <a href="http://" target="_blank">½­ÒõÊÐ</a>| <a href="http://" target="_blank">ÃÉÉ½ÏØ</a>| <a href="http://" target="_blank">ÔÆ°²ÏØ</a>| <a href="http://" target="_blank">´óÒ¦ÏØ</a>| <a href="http://" target="_blank">ÕѾõÏØ</a>| <a href="http://" target="_blank">Èô¶û¸ÇÏØ</a>| <a href="http://" target="_blank">¶¨ÐËÏØ</a>| <a href="http://" target="_blank">·ïÏèÏØ</a>| <a href="http://" target="_blank">ÑôË·ÏØ</a>| <a href="http://" target="_blank">ÃàÖñÊÐ</a>| <a href="http://" target="_blank">Èê³ÇÏØ</a>| <a href="http://" target="_blank">°ÙÉ«ÊÐ</a>| <a href="http://" target="_blank">ÉϲÌÏØ</a>| <a href="http://" target="_blank">¿ª·âÏØ</a>| <a href="http://" target="_blank">ÐÂÀÖÊÐ</a>| <a href="http://" target="_blank">óÞÁ¬ÏØ</a>| <a href="http://" target="_blank">¾²°²Çø</a>| <a href="http://" target="_blank">»áÔóÏØ</a>| <a href="http://" target="_blank">³¯ÑôÏØ</a>| <a href="http://" target="_blank">Û°³ÇÏØ</a>| <a href="http://" target="_blank">¸»ÄþÏØ</a>| <a href="http://" target="_blank">°²Æ½ÏØ</a>| <a href="http://" target="_blank">¶Ø»¯ÊÐ</a>| <a href="http://" 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>