• 请不要在回答技术问题时复制粘贴 AI 生成的内容
codehole
V2EX  ›  程序员

大厨小鲜——基于 Netty 自己动手实现 RPC 框架

  •  
  •   codehole ·
    pyloque · Apr 16, 2018 · 3085 views
    This topic created in 2972 days ago, the information mentioned may be changed or developed.

    今天我们要来做一道小菜,这道菜就是 RPC 通讯框架。它使用 netty 作为原料,fastjson 序列化工具作为调料,来实现一个极简的多线程 RPC 服务框架。

    我们暂且命名该 RPC 框架为 rpckids。

    食用指南

    在告诉读者完整的制作菜谱之前,我们先来试试这个小菜怎么个吃法,好不好吃,是不是吃起来很方便。如果读者觉得很难吃,那后面的菜谱就没有多大意义了,何必花心思去学习制作一门谁也不爱吃的大烂菜呢?

    例子中我会使用 rpckids 提供的远程 RPC 服务,用于计算斐波那契数和指数,客户端通过 rpckids 提供的 RPC 客户端向远程服务传送参数,并接受返回结果,然后呈现出来。你可以使用 rpckids 定制任意的业务 rpc 服务。

    斐波那契数输入输出比较简单,一个 Integer,一个 Long。 指数输入有两个值,输出除了计算结果外还包含计算耗时,以纳秒计算。之所以包含耗时,只是为了呈现一个完整的自定义的输入和输出类。

    指数服务自定义输入输出类

    // 指数 RPC 的输入
    public class ExpRequest {
    	private int base;
    	private int exp;
        
        // constructor & getter & setter
    }
    
    // 指数 RPC 的输出
    public class ExpResponse {
    
    	private long value;
    	private long costInNanos;
    
    	// constructor & getter & setter
    }
    

    斐波那契和指数计算处理

    public class FibRequestHandler implements IMessageHandler<Integer> {
    
    	private List<Long> fibs = new ArrayList<>();
    
    	{
    		fibs.add(1L); // fib(0) = 1
    		fibs.add(1L); // fib(1) = 1
    	}
    
    	@Override
    	public void handle(ChannelHandlerContext ctx, String requestId, Integer n) {
    		for (int i = fibs.size(); i < n + 1; i++) {
    			long value = fibs.get(i - 2) + fibs.get(i - 1);
    			fibs.add(value);
    		}
    		// 输出响应
    		ctx.writeAndFlush(new MessageOutput(requestId, "fib_res", fibs.get(n)));
    	}
    
    }
    
    public class ExpRequestHandler implements IMessageHandler<ExpRequest> {
    
    	@Override
    	public void handle(ChannelHandlerContext ctx, String requestId, ExpRequest message) {
    		int base = message.getBase();
    		int exp = message.getExp();
    		long start = System.nanoTime();
    		long res = 1;
    		for (int i = 0; i < exp; i++) {
    			res *= base;
    		}
    		long cost = System.nanoTime() - start;
    		// 输出响应
    		ctx.writeAndFlush(new MessageOutput(requestId, "exp_res", new ExpResponse(res, cost)));
    	}
    
    }
    

    构建 RPC 服务器

    RPC 服务类要监听指定 IP 端口,设定 io 线程数和业务计算线程数,然后注册斐波那契服务输入类和指数服务输入类,还有相应的计算处理器。

    public class DemoServer {
    
    	public static void main(String[] args) {
    		RPCServer server = new RPCServer("localhost", 8888, 2, 16);
    		server.service("fib", Integer.class, new FibRequestHandler())
    			  .service("exp", ExpRequest.class, new ExpRequestHandler());
    		server.start();
    	}
    
    }
    

    构建 RPC 客户端

    RPC 客户端要链接远程 IP 端口,并注册服务输出类(RPC 响应类),然后分别调用 20 次斐波那契服务和指数服务,输出结果

    public class DemoClient {
    
    	private RPCClient client;
    
    	public DemoClient(RPCClient client) {
    		this.client = client;
    		// 注册服务返回类型
    		this.client.rpc("fib_res", Long.class).rpc("exp_res", ExpResponse.class);
    	}
    
    	public long fib(int n) {
    		return (Long) client.send("fib", n);
    	}
    
    	public ExpResponse exp(int base, int exp) {
    		return (ExpResponse) client.send("exp", new ExpRequest(base, exp));
    	}
    
    	public static void main(String[] args) {
    		RPCClient client = new RPCClient("localhost", 8888);
    		DemoClient demo = new DemoClient(client);
    		for (int i = 0; i < 20; i++) {
    			System.out.printf("fib(%d) = %d\n", i, demo.fib(i));
    		}
    		for (int i = 0; i < 20; i++) {
    			ExpResponse res = demo.exp(2, i);
    			System.out.printf("exp2(%d) = %d cost=%dns\n", i, res.getValue(), res.getCostInNanos());
    		}
    	}
    
    }
    

    运行

    先运行服务器,服务器输出如下,从日志中可以看到客户端链接过来了,然后发送了一系列消息,最后关闭链接走了。

    server started @ localhost:8888
    connection comes
    read a message
    read a message
    ...
    connection leaves
    

    再运行客户端,可以看到一些列的计算结果都成功完成了输出。

    fib(0) = 1
    fib(1) = 1
    fib(2) = 2
    fib(3) = 3
    fib(4) = 5
    ...
    exp2(0) = 1 cost=559ns
    exp2(1) = 2 cost=495ns
    exp2(2) = 4 cost=524ns
    exp2(3) = 8 cost=640ns
    exp2(4) = 16 cost=711ns
    ...
    
    

    牢骚

    本以为是小菜一碟,但是编写完整的代码和文章却将近花费了一天的时间,深感写码要比做菜耗时太多了。因为只是为了教学目的,所以在实现细节上还有好多没有仔细去雕琢的地方。如果是要做一个开源项目,力求非常完美的话。至少还要考虑一下几点。

    1. 客户端连接池
    2. 多服务进程负载均衡
    3. 日志输出
    4. 参数校验,异常处理
    5. 客户端流量攻击
    6. 服务器压力极限

    如果要参考 grpc 的话,还得实现流式响应处理。如果还要为了节省网络流量的话,又需要在协议上下功夫。这一大堆的问题还是抛给读者自己思考去吧。

    关注公众号「码洞」,发送「 RPC 」即可获取以上完整菜谱的 GitHub 开源代码链接

    codehole
        1
    codehole  
    OP
       Apr 16, 2018
    怕文章太长,看起来难受,后面的菜谱详情略去了,愿意细读的去掘金看看吧

    [大厨小鲜——基于 Netty 自己动手实现 RPC 框架]( https://juejin.im/post/5ad2a99ff265da238d51264d)
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2734 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 11:46 · PVG 19:46 · LAX 04:46 · JFK 07:46
    ♥ Do have faith in what you're doing.