Using Netty to implement simple RPC framework

The underlying layer of Dubbo uses Netty as the network communication framework. [network transmission problem]: compared with the traditional RPC or RMI and other remote service process calls, synchronous blocking IO is used. When the concurrent pressure or network delay of the client increases, the synchronous blocking I/O will block the I/O thread frequently due to frequent waiting. Because the thread cannot work efficiently, the I/O processing capacity will naturally decline. Poor serialization performance: unable to cross language, long code stream, poor performance. [thread model problem]: the use of synchronous blocking of IO will cause each TCP connection to occupy one thread. Because the thread resource is a very valuable resource of the JVM virtual machine, when the I/O read-write block causes the thread unable to be released, the performance will drop sharply.

1, Design ideas

Imitating Dubbo's consumer and provider contract interface and protocol, consumers call the provider remotely, the provider returns data, and the consumer prints the data returned by the provider.
[1] Create an interface to define abstract methods. Used for agreements between consumers and providers.
[2] Create a provider that listens to consumer requests and returns data as agreed.
[3] To create a consumer, this class needs to transparently call a method that does not exist. Internally, it needs to use the Netty request provider to return data.

2, Server

[1] To add a Netty Maven dependency:

<dependencies>
	<dependency>
		<groupId>io.netty</groupId>
		<artifactId>netty-all</artifactId>
		<version>4.1.16.Final</version>
	</dependency>
</dependencies>	

[2] First, prepare the public interface required by the client and the server:

public interface HelloInterface {
    String hello(String msg);
}

[3] The server implements the HelloInterface interface:

public class HelloImpl implements HelloInterface {
    @Override
    public String hello(String msg) {
        //Return message from client
        return msg != null ? msg + " -----> I am fine." : "I am fine.";
    }
}

[4] Implement the Netty Server-side code (the code is fixed, usually used as public code):

public class Provider {

    static void startServer(String hostName, int port) {
        //Configure NIO thread group of server
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            //Codec for Strings
                            p.addLast(new StringDecoder());
                            p.addLast(new StringEncoder());
                            p.addLast(new HelloHandler());
                        }
                    });
            //Binding port, synchronization waiting for success
            ChannelFuture f = bootstrap.bind(hostName, port).sync();
            //Wait for the server listening port to close
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //Graceful exit to release the resources of thread pool
            //bossGroup.shutdownGracefully();
            //workerGroup.shutdownGracefully();
        }
    }
}

[5] Implementation of HelloHandler class corresponding to the server: implement channelinboundhandler adapter to process messages sent by the client. It is shown here that it judges whether it conforms to the contract (instead of using complex protocol, it is just a string judgment), and then creates a concrete implementation class and calls methods to write back to the client.

public class HelloHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {

             // How to comply with the contract, call the local method and return the data
             if (msg.toString().startsWith(ClientBootstrap.providerName)) {
                   String result = new HelloImpl()
                       .hello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1));
                   ctx.writeAndFlush(result);
                 }
           }
}

[6] Server start class: run the server first, then the client;

public class Bootstrap {
   public static void main(String[] args) {
             Provider.startServer("localhost", 8088);
   }
}

3, Client

One thing consumers need to pay attention to is that calls need to be transparent, that is to say, framework users don't care about the underlying network implementation. Here we can use JDK's dynamic proxy to achieve this. The idea is that the client calls the proxy method, returns a proxy object that implements the HelloService interface, calls the proxy object method, and returns the result. When invoking the proxy method, we need to initialize the Netty client, request data from the server and return data.

[1] First, create a proxy related class: this class has two methods: creating a proxy and initializing a client. Initialize client logic: create a Netty client, connect to the provider, and set up a custom handler and some codecs of String type. Create proxy logic: using the dynamic proxy technology of JDK, the invoke method in the proxy object is implemented as follows: if the client is not initialized, initialize the client, which is both a handler and a Callback. Set the parameter into the client, use the thread pool to call the client's call method and block waiting for the data to return.

public class Consumer {
    private static ExecutorService executor = Executors
            .newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static HelloClientHandler client;

    /**
     * Create a proxy object
     */
    public Object createProxy(final Class<?> serviceClass,
                              final String providerName) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[]{serviceClass}, (proxy, method, args) -> {
                    if (client == null) {
                        initClient();
                    }
                    // Setting parameters
                    client.setPara(providerName + args[0]);
                    return executor.submit(client).get();
                });
    }

    /**
     * Initialize client
     */
    private static void initClient() {
        client = new HelloClientHandler();
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(new StringDecoder());
                            p.addLast(new StringEncoder());
                            p.addLast(client);
                        }
                    });
            //Initiate asynchronous connection operation
            ChannelFuture f = b.connect("localhost", 8088).sync();
            //Wait for client to close connection
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //group.shutdownGracefully();
        }
    }
}

[2] Implementation of hellonclienthandler class in client Netty: this class caches ChannelHandlerContext for next use, with two properties: return result and request parameter. When the connection is successful, cache the ChannelHandlerContext. When calling the call method, send the request parameters to the server and wait. When the server receives and returns data, it calls the channelRead method, assigns a result to the returned value, and wakes up the thread waiting on the call method. At this point, the proxy object returns the data.

public class HelloClientHandler extends ChannelInboundHandlerAdapter implements Callable {

    private ChannelHandlerContext context;
    private String result;
    private String para;

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        context = ctx;
    }

    /**
     * Receive server data, wake up waiting thread
     */
    @Override
    public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) {
        result = msg.toString();
        notify();
    }

    /**
     * Write out data and start waiting for wakeup
     */
    @Override
    public synchronized Object call() throws InterruptedException {
        context.writeAndFlush(para);
        wait();
        return result;
    }

    void setPara(String para) {
        this.para = para;
    }
}

Four, test

First, a proxy object is created, and then the hello method of the proxy is called every second, and the result returned by the server is printed.

public class ClientBootstrap {
    public static final String providerName = "HelloService#hello#";

    public static void main(String[] args) throws InterruptedException {

        Consumer consumer = new Consumer();
        // Create a proxy object
        HelloInterface service = (HelloInterface) consumer
                .createProxy(HelloInterface.class, providerName);
        for (; ; ) {
            Thread.sleep(1000);
            System.out.println(service.hello("are you ok ?"));
        }
    }
}

Results:

----If you like, please click "hearts" to support the following, thank you----

210 original articles published, 225 praised, 180000 visitors+
Private letter follow

Tags: Netty network Dubbo JDK

Posted on Sun, 02 Feb 2020 04:25:22 -0500 by psolus21