Dubbo 底层使用 Netty 作为网络通信框架。
网络传输问题】:相对于传统的 RPC 或者 RMI 等方式的远程服务过程调用采用了同步阻塞IO,当客户端的并发压力或者网络时延增长之后,同步阻塞 I/O 会由于频繁的等待导致 I/O 线程经常性阻塞,由于线程无法高效的工作,I/O处理能力自然会下降。
序列化性能差链接】:无法跨语言、码流长、性能差。
线程模型问题】:采用同步阻塞IO,这会导致每个 TCP 连接都占用 1个线程,由于线程资源是 JVM 虚拟机非常宝贵的资源,当 I/O 读写阻塞导致线程无法释放时,会导致性能急剧下降。

一、设计思想


模仿 Dubbo 的消费者和提供者约定接口和协议,消费者远程调用提供者,提供者返回数据,消费者打印提供者返回的数据。
【1】创建一个接口,定义抽象方法。用于消费者和提供者之间的约定。
【2】创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据。
【3】创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 Netty 请求提供者返回数据。

二、服务端


【1】添加 Netty Maven 依赖:

1 <dependencies>
2 <dependency>
3 <groupId>io.netty</groupId>
4 <artifactId>netty-all</artifactId>
5 <version>4.1.16.Final</version>
6 </dependency>
7 </dependencies>

【2】首先准备客户端和服务端需要的公共接口

1 public interface HelloInterface {
2 String hello(String msg);
3 }

【3】服务端实现 HelloInterface 接口:

1 public class HelloImpl implements HelloInterface {
2 @Override
3 public String hello(String msg) {
4 //返回客户端的消息
5 return msg != null ? msg + " -----> I am fine." : "I am fine.";
6 }
7 }

【4】实现 Netty Server 端代码(代码固定,通常作为公共代码使用):

 1 public class Provider {
2
3 static void startServer(String hostName, int port) {
4 //配置服务端的 NIO 线程组
5 NioEventLoopGroup bossGroup = new NioEventLoopGroup();
6 NioEventLoopGroup workerGroup = new NioEventLoopGroup();
7 try {
8 ServerBootstrap bootstrap = new ServerBootstrap();
9 bootstrap.group(bossGroup,workerGroup)
10 .channel(NioServerSocketChannel.class)
11 .childHandler(new ChannelInitializer<SocketChannel>() {
12 @Override
13 protected void initChannel(SocketChannel ch) throws Exception {
14 ChannelPipeline p = ch.pipeline();
15 //字符串的编解码器
16 p.addLast(new StringDecoder());
17 p.addLast(new StringEncoder());
18 p.addLast(new HelloHandler());
19 }
20 });
21 //绑定端口,同步等待成功
22 ChannelFuture f = bootstrap.bind(hostName, port).sync();
23 //等待服务端监听端口关闭
24 f.channel().closeFuture().sync();
25 } catch (InterruptedException e) {
26 e.printStackTrace();
27 }finally {
28 //优雅退出释放线程池资源
29 //bossGroup.shutdownGracefully();
30 //workerGroup.shutdownGracefully();
31 }
32 }
33 }

【5】服务端对应的 HelloHandler 类的实现:实现 ChannelInboundHandlerAdapter 适配器,对客户端发送的消息进行处理。这里显示判断了是否符合约定(并没有使用复杂的协议,只是一个字符串判断),然后创建一个具体实现类,并调用方法写回客户端。

 1 public class HelloHandler extends ChannelInboundHandlerAdapter {
2 @Override
3 public void channelRead(ChannelHandlerContext ctx, Object msg) {
4
5 // 如何符合约定,则调用本地方法,返回数据
6 if (msg.toString().startsWith(ClientBootstrap.providerName)) {
7 String result = new HelloImpl()
8 .hello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1));
9 ctx.writeAndFlush(result);
10 }
11 }
12 }

【6】服务端启动类:先运行服务端,后运行客户端

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

三、客户端


消费者有一个需要注意的地方,就是调用需要透明,也就是说,框架使用者不用关心底层的网络实现。这里我们可以使用 JDK 的动态代理链接】来实现这个目的。思路是客户端调用代理方法,返回一个实现了 HelloService 接口的代理对象,调用代理对象的方法,返回结果。当调用代理方法的时候,我们需要初始化 Netty 客户端,还需要向服务端请求数据,并返回数据。

【1】首先创建代理相关的类:该类有 2 个方法,创建代理和初始化客户端。初始化客户端逻辑: 创建一个 Netty 的客户端,并连接提供者,并设置一个自定义 handler,和一些 String 类型的编解码器。创建代理逻辑:使用 JDK 的动态代理技术,代理对象中的 invoke 方法实现如下:如果 client 没有初始化,则初始化 client,这个 client 既是 handler ,也是一个 Callback。将参数设置进 client ,使用线程池调用 client 的 call 方法并阻塞等待数据返回。

 1 public class Consumer {
2 private static ExecutorService executor = Executors
3 .newFixedThreadPool(Runtime.getRuntime().availableProcessors());
4
5 private static HelloClientHandler client;
6
7 /**
8 * 创建一个代理对象
9 */
10 public Object createProxy(final Class<?> serviceClass,
11 final String providerName) {
12 return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
13 new Class<?>[]{serviceClass}, (proxy, method, args) -> {
14 if (client == null) {
15 initClient();
16 }
17 // 设置参数
18 client.setPara(providerName + args[0]);
19 return executor.submit(client).get();
20 });
21 }
22
23 /**
24 * 初始化客户端
25 */
26 private static void initClient() {
27 client = new HelloClientHandler();
28 EventLoopGroup group = new NioEventLoopGroup();
29 try {
30 Bootstrap b = new Bootstrap();
31 b.group(group)
32 .channel(NioSocketChannel.class)
33 .option(ChannelOption.TCP_NODELAY, true)
34 .handler(new ChannelInitializer<SocketChannel>() {
35 @Override
36 public void initChannel(SocketChannel ch) throws Exception {
37 ChannelPipeline p = ch.pipeline();
38 p.addLast(new StringDecoder());
39 p.addLast(new StringEncoder());
40 p.addLast(client);
41 }
42 });
43 //发起异步连接操作
44 ChannelFuture f = b.connect("localhost", 8088).sync();
45 //等待客户端关闭连接
46 f.channel().closeFuture().sync();
47 } catch (InterruptedException e) {
48 e.printStackTrace();
49 }finally {
50 //group.shutdownGracefully();
51 }
52 }
53 }

【2】客户端 Netty 中 HelloClientHandler 类的实现:该类缓存了 ChannelHandlerContext,用于下次使用,有两个属性:返回结果和请求参数。当成功连接后,缓存 ChannelHandlerContext,当调用 call 方法的时候,将请求参数发送到服务端,等待。当服务端收到并返回数据后,调用 channelRead 方法,将返回值赋值个 result,并唤醒等待在 call 方法上的线程。此时,代理对象返回数据。

 1 public class HelloClientHandler extends ChannelInboundHandlerAdapter implements Callable {
2
3 private ChannelHandlerContext context;
4 private String result;
5 private String para;
6
7 @Override
8 public void channelActive(ChannelHandlerContext ctx) {
9 context = ctx;
10 }
11
12 /**
13 * 收到服务端数据,唤醒等待线程
14 */
15 @Override
16 public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) {
17 result = msg.toString();
18 notify();
19 }
20
21 /**
22 * 写出数据,开始等待唤醒
23 */
24 @Override
25 public synchronized Object call() throws InterruptedException {
26 context.writeAndFlush(para);
27 wait();
28 return result;
29 }
30
31 void setPara(String para) {
32 this.para = para;
33 }
34 }

四、测试


首先创建了一个代理对象,然后每隔一秒钟调用代理的 hello 方法,并打印服务端返回的结果。

 1 public class ClientBootstrap {
2 public static final String providerName = "HelloService#hello#";
3
4 public static void main(String[] args) throws InterruptedException {
5
6 Consumer consumer = new Consumer();
7 // 创建一个代理对象
8 HelloInterface service = (HelloInterface) consumer
9 .createProxy(HelloInterface.class, providerName);
10 for (; ; ) {
11 Thread.sleep(1000);
12 System.out.println(service.hello("are you ok ?"));
13 }
14 }
15 }

结果展示:

 

使用 Netty 实现简单的 RPC 框架的更多相关文章

  1. Java实现简单的RPC框架(美团面试)

    一.RPC简介 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用) ...

  2. 最简单的RPC框架实现

    通过java原生的序列化,Socket通信,动态代理和反射机制,实现一个简单的RPC框架,由三部分组成: 1.服务提供者,运行再服务端,负责提供服务接口定义和服务实现类 2.服务发布者,运行再RPC服 ...

  3. 分布式架构的基石.简单的 RPC 框架实现(JAVA)

    前言 RPC 的全称是 Remote Procedure Call,它是一种进程间通信方式.允许像调用本地服务一样调用远程服务. 学习来源:<分布式系统架构:原理与实践> - 李林锋 1. ...

  4. 学习写简单的RPC框架demo

    学习实现一个简单的RPC框架. 工程主要目录分级结构: rpc-common: 公共基础包,能力提供包 rpc-provider: 服务提供者 rpc-consumer:服务消费者 rpc-servi ...

  5. 徒手撸一个简单的RPC框架

    来源:https://juejin.im/post/5c4481a4f265da613438aec3 之前在牛逼哄哄的 RPC 框架,底层到底什么原理得知了RPC(远程过程调用)简单来说就是调用远程的 ...

  6. Java实现简单的RPC框架

    一.RPC简介 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用) ...

  7. 造个轮子之基于 Netty 实现自己的 RPC 框架

    原文地址: haifeiWu和他朋友们的博客 博客地址:www.hchstudio.cn 欢迎转载,转载请注明作者及出处,谢谢! 服务端开发都会或多或少的涉及到 RPC 的使用,当然如果止步于会用,对 ...

  8. Java 实现简单的RPC框架

    0 引言 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用).He ...

  9. netty 实现简单的rpc调用

    yls 2020/5/23 netty 实现简单rpc准备 使用netty传输java bean对象,可以使用protobuf,也可以通过json转化 客户端要将调用的接口名称,方法名称,参数列表的类 ...

  10. Java 实现简单的 RPC 框架

    RPC 简介 RPC,全称为 Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式,而不需要了解底层网络技术 ...

随机推荐

  1. bzoj 4817

    LCT好题 首先我们考虑实际询问的是什么: 从LCT的角度考虑,如果我们认为一开始树上每一条边都是虚边,把一次涂色看作一次access操作,那么询问的实际就是两个节点间的虚边数量+1和子树中的最大虚边 ...

  2. c++11 智能指针学习汇总

    c++为什么要引入智能指针? C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念.这里不置 ...

  3. 05 js利用ajax向服务器发送请求并返回json值

    创建一个php $json = '';$data = array();$con =mysqli_connect($servername, $username, $password, $dbname); ...

  4. Leetcode61

    !!Given the head of a linked list, rotate the list to the right by k places.!!   # Definition for si ...

  5. 2022/7/28 第七组陈美娜 API类

    API:Application Program Interface应用程序接口 JDK给我们提供的一些已经写好的类,可以直接调方法来解决问题 类的方法在宏观上都可以称为接口 接口:1.interfac ...

  6. LOJ数列分块入门九题(中)

    #6281. 数列分块入门 5 - 题目 - LibreOJ (loj.ac) 区间开方,区间求和题. 显然,针对区间维护开方操作很难做到,于是考虑其值的性质,显然,int范围内的值最多开方6次就会变 ...

  7. springboot+mybatis+vue

    https://www.cnblogs.com/wlovet/p/10980579.html

  8. P3512 [POI2010]PIL-Pilots 单调队列的应用

    题目描述 给定n,k和一个长度为n的序列,求最长的最大值最小值相差不超过k的序列 输入格式 第一行两个有空格隔开的整数k(0<=k<=2000,000,000),n(1<=n< ...

  9. windows 获取USB,发现安卓设备,转载自www.jb51.net/article/164456.htm

    转载 作者:jgw2008 import win32com.client def CheckDev(): wmi = win32com.client.GetObject ("winmgmts ...

  10. windows 2016 安装docker

    windows 2016 安装docker 前提条件:windows server 2016安装更新 1:用管理员打开windows PowerShell Install-PackageProvide ...