Dubbo系列之 (七)鏈路層那些事(1)

wsMrLin發表於2020-09-14

輔助連結

Dubbo系列之 (一)SPI擴充套件

Dubbo系列之 (二)Registry註冊中心-註冊(1)

Dubbo系列之 (三)Registry註冊中心-註冊(2)

Dubbo系列之 (四)服務訂閱(1)

Dubbo系列之 (五)服務訂閱(2)

Dubbo系列之 (六)服務訂閱(3)

Dubbo系列之 (七)鏈路層那些事(1)

在講解dubboTCP端的設計時,先了解下一些類的關係圖。它們是如何組織在一起的,每個功能又是什麼,接著在進一步深入瞭解其內涵。

類簡介

1、Exchangers(交換器工具類) 用來建立TCP服務(bind)和建立客戶端連線(connect)輔助類

2、Transporters(資料流傳輸工具類)用來建立TCP服務(bind)和建立客戶端連線(connect)輔助類,Exchangers的底層內容依賴於Transporters,並且Transporters會根據SPI擴充套件,來適配合適的tcp通訊框架,比如netty,mina等。

3、Exchanger(交換器) 用來建立TCP連結,通過工具類Exchangers完成,該介面是一個SPI擴充套件,目前唯一僅有就是HeaderExchanger。從名字的含義可以得到,該協議是具有自定義協議頭的交換器,所以取名HeaderExchanger。

4、Transporter(資料傳輸層) 用來建立TCP連線,通過工具類Transporters完成。它也是一個SPI擴充套件,比如NettyTransporter,MinaTransporter。

5、ExchangeClient (交換器客戶端),Exchanger的connect()方法返回,即建立了TCP連線後,返回的客戶端,接著就是通過該客戶端與服務端通訊,例項有HeaderExchangeClient、LazyConnectExchangeClient、ReferenceCountExchangeClient。之後分別講解這3個,Exchangers工具類建立的連線客戶端是HeaderExchangeClient。

6、ExchangeServer (交換器服務端端) Exchanger的bind()方法返回,即服務端監聽的服務端例項,它監聽這某個具體的tcp埠。預設實現是HeaderExchangeServer。

7、RemotingServer(遠端的TCP服務端),ExchangeServer類也實現了該介面,代表其也是一個遠端伺服器,具體的實現有NettyServer,由Transporter的bind()方法返回,具體的Transporter返回相應的遠端服務端。比如NettyTransporter#bind()返回NettyServer。

8、Client(TCP客戶端),ExchangeClient類也實現了該介面,代表其也是一個TCP客戶端,具體實現有NettyClient,由Transporter的connect()方法返回,具體的Transporter返回相應的TCP客戶端。比如NettyTransporter#connect()返回NettyClient。

9、Channel (通訊通道) ,每建立一個TCP連結就相應建立一個Channel。比如Netty建立連線後,就有一個Channel。這裡的Channel指的是dubbo自己定義的一個channel。它與netty的channel建立關聯,通過NettyChannel類,框架操作的是NettyChannel,而NettyChannel內部持有一個netty的channel物件。

10、HeaderExchangeChannel(交換器Channel,ExchangeChannel屬於交換器Channel),它被HeaderExchangeClient客戶端所持有,客戶端就是通過HeaderExchangeChannel進行通訊的,HeaderExchangeChannel內部持有一個具體的Channel。

11、ChannelHandler (通道處理器) 用來處理建立連線、傳送請求、結束請求等操作的具體抽象。

12、ChannelHandlers(通道處理器工具類) 主要用來包裹封裝具體的Channel,它的作用是通過訊息型別,根據Dispatcher返回不同的

13、Dispatcher(訊息派發器)

型別 Dispatcher Channelhandler 作用
All AllDispatcher AllChannelHandler 所有的訊息型別全部通過業務執行緒池處理
Connection ConnectionOrderedDispatcher ConnectionOrderedChannelHandler 連線、斷開訊息單獨通過一個執行緒池池來處理,其他的讀寫等訊息通過業務執行緒池處理
Direct DirectDispatcher DirectChannelHandler 所有的訊息都通過IO執行緒池處理,不放到業務執行緒池中
Execution ExecutionDispatcher ExecutionChannelHandler 請求訊息在業務執行緒池處理,其他訊息在IO執行緒池。
Message MessageOnlyDispatcher MessageOnlyChannelHandler 請求和響應訊息在業務執行緒池處理,其他心跳,連線等訊息在IO執行緒池處理

類關係圖

試一把,Netty操作--客戶端多執行緒,單鏈路(TCP)

1、定義傳輸訊息

@Data
@ToString
public class SampleMessage {

    private String threadName;
    
    private String id;

    private String desc;
}

2、編寫編碼器

public class SampleEncoder extends MessageToByteEncoder<SampleMessage> {

    protected void encode(ChannelHandlerContext channelHandlerContext, SampleMessage sampleMessage, ByteBuf byteBuf) throws Exception {

        String threadName = sampleMessage.getThreadName();
        String id = sampleMessage.getId();
        String desc = sampleMessage.getDesc();

        byteBuf.writeInt(threadName.getBytes().length);
        byteBuf.writeBytes(threadName.getBytes());

        byteBuf.writeInt(id.getBytes().length);
        byteBuf.writeBytes(id.getBytes());


        byteBuf.writeInt(desc.getBytes().length);
        byteBuf.writeBytes(desc.getBytes());
        String str = sampleMessage.getThreadName() + ":" + sampleMessage.getDesc() + ":" + sampleMessage.getId();

        System.out.println(str);
    }
}

3、編寫解碼器

public class SampleDecoder extends ByteToMessageDecoder {

    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {

        byteBuf.markReaderIndex();

        String threadName = read(byteBuf);
        if (threadName == null) {
            byteBuf.resetReaderIndex();
            return;
        }

        String id = read(byteBuf);
        if (id == null) {
            byteBuf.resetReaderIndex();
            return;
        }

        String desc = read(byteBuf);
        if (desc == null) {
            byteBuf.resetReaderIndex();
            return;
        }

        SampleMessage sampleMessage = new SampleMessage();
        sampleMessage.setId(id);
        sampleMessage.setThreadName(threadName);
        sampleMessage.setDesc(desc);
        list.add(sampleMessage);
    }

    private String read(ByteBuf byteBuf) {
        if (canReadInt(byteBuf)) {
            int readInt = byteBuf.readInt();
            if (canReadN(byteBuf, readInt)) {
                byte[] bytes = new byte[readInt];
                byteBuf.readBytes(bytes);
                return new String(bytes);
            } 
        }
        return null;
    }


    private boolean canReadInt(ByteBuf byteBuf) {
        return canReadN(byteBuf, 4);
    }

    private boolean canReadN(ByteBuf byteBuf, int n) {
        if (!byteBuf.isReadable()) {
            return false;
        }
        return byteBuf.readableBytes() >= n;
    }
}

4、編寫訊息處理器

public class PrintChannelHandlers extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof SampleMessage) {
            SampleMessage sampleMessage = (SampleMessage) msg;
            System.out.println(sampleMessage.getThreadName() + ":" + sampleMessage.getId() + ":" + sampleMessage.getDesc());
        }
    }
    
}

5、編寫服務端

public class NettyServerMain {

    public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(12))
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline()
                               // .addLast("log",new LoggingHandler(LogLevel.INFO))
                                .addLast("decoder", new SampleDecoder())
                                .addLast("encoder", new SampleEncoder())
                                .addLast("handler", new PrintChannelHandlers());
                    }
                });

        ChannelFuture channelFuture = serverBootstrap.bind(8888);
        channelFuture.syncUninterruptibly();
        System.out.println("連結前");
        Channel channel = channelFuture.channel();
        System.out.println("連結後");
    }
}

6、編寫客戶端

public class NettyClientMain {
    public static void main(String[] args) {
        NettyClientMain nettyClientMain = new NettyClientMain();
        nettyClientMain.open();
    }

    public void open() {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap = new Bootstrap();
        bootstrap.group(new NioEventLoopGroup(10))
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .channel(NioSocketChannel.class);

        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {

                ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                        .addLast("decoder", new SampleDecoder())
                        .addLast("encoder", new SampleEncoder());
                //.addLast("handler", new PrintChannelHandlers());

            }
        });

        SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8888);

        ChannelFuture future = bootstrap.connect(socketAddress);
        boolean ret = future.awaitUninterruptibly(3000, MILLISECONDS);

        if (ret && future.isSuccess()) {
            Channel newChannel = future.channel();
            doProcess(newChannel);
        }
    }

    private void doProcess(Channel channel) {

        AtomicLong atomicLong = new AtomicLong();
        for (int i = 0; i < 15; i++) {
            final char ch = (char) (i + 65);
            final String id = "id" + i;
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        SampleMessage sampleMessage = new SampleMessage();
                        sampleMessage.setThreadName(Thread.currentThread().getName());
                        sampleMessage.setDesc(getdes(ch));
                        sampleMessage.setId("id" + sampleMessage.getDesc().length() + "-" + atomicLong.getAndIncrement());
                        channel.writeAndFlush(sampleMessage);
                    }
                }
            });
            t.start();
        }
    }


    private String getdes(char a) {
        Random random = new Random();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < random.nextInt(500) + 1; i++) {
            buffer.append(a);
        }
        return buffer.toString();
    }
}

7、測試結果

結果符合預期,dubbo 也是通過服務底層公用一條TCP連結,多執行緒進行呼叫該鏈路channel。

相關文章