这样基于Netty重构RPC框架你不可能知道

今天呢!灯塔君跟大家讲:     

基于Netty重构RPC框架

一.CyclicBarrier方法说明

1.单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。。(最开始58 同城的站点架构用一个词概括就是“ALL IN ONE”。就像一个单机系统,所有的东西都部署在一台机器 上,包括站点、数据库、文件等等。而工程师每天的核心工作就是CURD,前端传过来一些数据,然后 业务逻辑层拼装成一些CURD访问数据库,数据库返回数据,数据拼装成页面,最终返回到浏览器。此 时,用于简化增删改查工作量的数据访问框架(ORM)是关键)

2、垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提 升效率。应用拆分为不相干的几个应用,前后端分离,此时用于加速前端页面开发的Web MVC框架是 关键

3、分布式服务服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定 的服务中心,使前端应用能更快速的响应多变的市场需求。同时将公共能力API抽取出来,作为独立的 公共服务供其他调用者消费,以实现服务的共享和重用,降低开发和运维成本。应用拆分之后会按照模 块独立部署,接口调用由本地API演进成跨进程的远程方法调用,此时RPC框架应运而生。此时,用于 提高业务复用及整合的分布式服务框架(RPC)是关键。

4、流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问 压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是 关键。面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆 分,并通过这些服务之间定义良好的接口和协议联系起来。

没有RPC框架之前,我们的服务调用是这样的:

看出接口的调用完全没有规律可循,想怎么调,就怎么调。这导致业务发展到一定阶段之后,对接口的 维护变得非常困难。于是有人提出了服务治理的概念。所有服务间不允许直接调用,而是先到注册中心 进行登记,再由注册中心统一协调和管理所有服务的状态并对外发布,调用者只需要记住服务名称,去 找注册中心获取服务即可。这样,极大地规范了服务的管理,可以提高了所有服务端可控性。整个设计思想其实在我们生活中也能 找到活生生的案例。例如:我们平时工作交流,大多都是用IM 工具,而不是面对面吼。大家只需要相 互记住运营商(也就是注册中心)提供的号码(如:腾讯QQ)即可。再比如:我们打电话,所有电话 号码有运营商分配。我们需要和某一个人通话时,只需要拨通对方的号码,运营商(注册中心,如中国 移动、中国联通、中国电信)就会帮我们将信号转接过去。

二.RPC介绍

1、RPC简介:

Remote Procedure Call远程过程调用RPC就是从一台机器(客户端)上通过参数传递的        方式调用另一台机器(服务器)上的一个函数或 方法(可以统称为服务)并得到返回的结果。

RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模 型。

客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数 (或方法)一样去调用远程的函数(或方法)。

 2、RPC通信原理

RPC的主要作用有三方面:

1、进程间通讯

2、提供和本地方法调用一样的机制

3、屏蔽用户对远程调用的细节实现

RPC框架的好处首先就是长链接,不必每次通信都要像http一样去3次握手,减少了网络开销;其次就 是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无 感知,统一化的操作。

3、RPC通过过程

1)服务消费方(client)调用以本地调用方式调用服务; 

2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体; 

3)client stub找到服务地址,并将消息发送到服务端; 

4)server stub收到消息后进行解码; 

5)server stub根据解码结果调用本地的服务;6)本地服务执行并将结果返回给server stub;7)server stub将返回结果打包成消息并发送至消费方; 

8)client stub接收到消息,并进行解码; 

9)服务消费方得到最终结果。

4、常用的分布式RPC框架

dubbo:国内最早开源的RPC框架,由阿里巴巴公司开发并于2011年末对外开源,仅支持java语言

4、常用的分布式RPC框架

dubbo:国内最早开源的RPC框架,由阿里巴巴公司开发并于2011年末对外开源,仅支持java语言

motan:微博内部使用的RPC框架,于2016年对外开源,仅支持java语言

Thrift:轻量级的跨语言RPC通信方案,支持多大25种变成语言

gRPC:Google于2015年对外开源的跨语言PRC框架,支持常用的C++、java、Python、Go、 Ruby、PHP等多种语言。

目前流行的RPC 服务治理框架主要有Dubbo 和Spring Cloud,

下面我们以比较经典的Dubbo 为例。

Dubbo 核心模块主要有:

Registry:注册中心(主要负责保存所有可用的服务名称和服务地址。) 

Provider:服务中心(实现对外提供的所有服务的具体功能) 

Consumer:消费端(调用远程服务的服务消费方) 

Monitor:监控中心(统计服务的调用次数和调用时间的监控中心) 

Container:服务运行容器

api:主要用来定义对外开放的功能与服务接口。protocol:主要定义自定义传输协议的内容

蓝色方框代表业务有交互,绿色方框代表只对Dubbo内部交互。蓝色虚线为初始化时调用,红色虚线为 运行时异步调用,红色实线为运行时同步调用

0、服务在容器中启动,加载,运行Provider

1、Provider在启动时,向Registry注册自己提供的服务 

2、Consumer在启动时,向Registry订阅自己所需的服务 

3、Registry给Consumer返回Provider的地址列表,如果Provider地址有变更(上线/下线机器), Registry将基于长连接推动变更数据给Consumer 

4、Consumer从Provider地址列表中,基于软负载均衡算法,选一台进行调用,如果失败,重试另一 台调用 

5、Consumer和Provider,在内存中累计调用次数和时间,定时每分钟一次将统计数据发送到Monitor

4.具体实现

1、api

package com.rpc.api; 

/**

* API模块,provider和Consumer都遵循API模块的

规范 * 用来定义对外开放的功能与服务接口 

*/ 

public interface IRpcHelloService { 

String hello(String name); 

}

package com.rpc.api; 

public interface IRpcService { 

/*增加用户*/ 

public String addUser(); 

/*删除用户*/ 

public String deleteUser(Integer id); 

/*修改用户*/ 

public String updateUser(Integer id); 

/*查询用户*/ 

public String queryUser(Integer id); 

}

2、Provider:服务中心

package com.rpc.provider; 

import com.rpc.api.IRpcService; 

public class RpcServiceImpl implements 

IRpcService { 

@Override 

public String addUser() { 

return "增加用户"; 

}

@Override 

public String deleteUser(Integer id) { 

return "删除了编号为"+id+"的用户"; 

}

@Override 

public String updateUser(Integer id) { 

return "修改了编号为"+id+"的用户"; 

}

@Override 

public String queryUser(Integer id) { 

return "查询到了编号为"+id+"这个用户的信息"; 

}

package com.rpc.provider; 

import com.rpc.api.IRpcHelloService; 

/**

* 服务中心,实现对外提供的所有服务的具体功能 

*/ 

public class RpcHelloServiceImpl implements IRpcHelloService { 

public String hello(String name) { 

return "Hello " + name + "!"; 

}

3、protocol传输协议内容

ackage com.rpc.protocol; 

import java.io.Serializable; 

/**

* 自定义传输协议内容 

*/ 

public class InvokerProtocol implements 

Serializable { 

private String className;//类名 

private String methodName;//函数名称 

private Class<?>[] parames;//形参列表

private Object[] values;//实参列表 

public String getClassName() { 

return className; 

}

public void setClassName(String className)

 { 

this.className = className; 

}

public String getMethodName() { 

return methodName; 

}

public void setMethodName

(String methodName)

 { 

this.methodName = methodName; 

}

public Class<?>[] getParames() { 

return parames; 

}

public void setParames(Class<?>[] parames) 

this.parames = parames; 

}

public Object[] getValues() { 

return values; 

}

public void setValues(Object[] values) { 

this.values = values; 

}

4、Registry注册中心

package com.rpc.registry; 

import io.netty.bootstrap.ServerBootstrap; 

import io.netty.channel.*; 

import io.netty.channel.nio.NioEventLoopGroup; 

import io.netty.channel.socket.SocketChannel; 

importio.netty.channel.socket.nio.

NioServerSocketChannel; 

import io.netty.handler.codec.

LengthFieldBasedFrameDecoder; 

import io.netty.handler.codec.LengthFieldPrepender; 

import io.netty.handler.codec.serialization.

ClassResolvers; 

import io.netty.handler.codec.serialization.

ObjectDecoder; 

import io.netty.handler.codec.serialization.

ObjectEncoder; 

/**

* 注册中心主要功能就是负责将所有Provider的

服务名称和服务引用地址注册到一个容器中, 

* 并对外发布。启动一个对外的服务,

并对外提供一个可访问的端口 

* 主要负责保存所有可用的服务名称和服务地址 

*/ 

public class RpcRegistry { 

private int port;

public RpcRegistry(int port){ 

this.port = port; 

}

public void start(){ 

EventLoopGroup bossGroup = new NioEventLoopGroup(); 

EventLoopGroup workerGroup = new NioEventLoopGroup(); 

try {

ServerBootstrap b = new ServerBootstrap(); 

b.group(bossGroup, workerGroup) 

.channel(NioServerSocketChannel.class) 

.childHandler(new ChannelInitializer<SocketChannel>() { 

@Override 

protected void initChannel(SocketChannel ch) throws 

Exception { 

ChannelPipeline pipeline = ch.pipeline(); 

//自定义协议解码器 

/** 入参有5个,分别解释如下 

(1) maxFrameLength - 发送的数据包最大长度; 

(2) lengthFieldOffset - 长度域偏移量,指的是长度域位于 

整个数据包字节数组中的下标; 

(3) lengthFieldLength - 长度域的自己的字节数长度。 

(4) lengthAdjustment – 长度域的偏移量矫正。如果长度域 

的值,除了包含有效数据域的长度外,

还包含了其他域(如长度域自身)长度,那么,

就需要进行矫正。矫 正的值为:包长 - 长度域的值 – 

长度域偏移 – 长度域长。 

(5) initialBytesToStrip – 丢弃的起始字节数。

丢弃处于有 效数据前面的字节数量。

比如前面有4个节点的长度域,则它的值为4。 

*/ 

pipeline.addLast(new 

LengthFieldBasedFrameDecoder(Integer.

MAX_VALUE, 0, 4, 0, 4)); 

//自定义协议编码器 

pipeline.addLast(new LengthFieldPrepender(4)); 

//对象参数类型编码器 

pipeline.addLast("encoder",new ObjectEncoder()); 

//对象参数类型解码器 

pipeline.addLast("decoder",new 

ObjectDecoder(Integer.MAX_VALUE, 

ClassResolvers.cacheDisabled(null))); 

pipeline.addLast(new RegistryHandler()); 

})

.option(ChannelOption.SO_BACKLOG, 128) 

.childOption(ChannelOption.SO_KEEPALIVE, true); 

ChannelFuture future = b.bind(port).sync(); 

System.out.println("jiangym RPC Registry 

start listen at " + port ); 

future.channel().closeFuture().sync(); 

} catch (Exception e) { 

bossGroup.shutdownGracefully(); 

workerGroup.shutdownGracefully(); 

}

//主启动类 

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

new RpcRegistry(8888).start(); 

}

package com.rpc.registry; 

import com.rpc.protocol.InvokerProtocol; 

import io.netty.channel.ChannelHandlerContext; 

import io.netty.channel.ChannelInboundHandlerAdapter; 

import java.io.File; 

import java.lang.reflect.Method; 

import java.net.URL; 

import java.util.ArrayList; 

import java.util.List; 

import java.util.concurrent.ConcurrentHashMap; 

public class RegistryHandler extends ChannelInboundHandlerAdapter { 

//保存所有可用的服务(ConcurrentHashMap

是线程安全且高效的HashMap实现) 

public static ConcurrentHashMap

<String, Object> 

registryMap = new ConcurrentHashMap<String, Object>(); 

//保存所有相关的服务类 

private List<String> classNames = new ArrayList<String>(); 

public RegistryHandler() { 

//完成递归扫描 

scannerClass("com.rpc.provider"); 

doRegister(); 

}

@Override 

public void channelRead(ChannelHandlerContext ctx, Object msg) throws 

Exception { 

Object result = new Object(); 

InvokerProtocol request = (InvokerProtocol) msg; 

//当客户端建立连接时,需要从自定义协议中获取信息,

拿到具体的服务和实参 

//使用反射调用 

if (registryMap.containsKey(request.getClassName())) { 

Object clazz = registryMap.get(request.getClassName()); 

Method method = clazz.getClass().getMethod(request.

getMethodName(), 

request.getParames()); 

result = method.invoke(clazz, 

request.getValues()); 

}

ctx.write(result); 

ctx.flush(); 

ctx.close(); 

}

@Override 

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 

throws Exception { 

cause.printStackTrace(); 

ctx.close(); 

}

/*

* 递归扫描 

*/ 

private void scannerClass(String packageName)

 { 

URL url = 

this.getClass().getClassLoader().getResource

(packageName.replaceAll("\\.", 

"/")); 

File dir = new File(url.getFile());

for (File file : dir.listFiles()) { 

//如果是一个文件夹,继续递归 

if (file.isDirectory()) { 

scannerClass(packageName + "." +

 file.getName()); 

} else { 

classNames.add(packageName + "." + 

file.getName().replace(".class", "").trim()); 

}

/**

* 完成注册 

*/ 

private void doRegister() { 

if (classNames.size() == 0) { 

return; 

}

for (String className : classNames) { 

try {

Class<?> clazz = Class.forName(className); 

Class<?> i = clazz.getInterfaces()[0]; 

registryMap.put(i.getName(),

clazz.newInstance()); 

} catch (Exception e) { 

e.printStackTrace(); 

}

4、消费端

package com.rpc.consumer; 

import com.rpc.api.IRpcHelloService; 

import com.rpc.api.IRpcService; 

public class RpcConsumer { 

public static void main(String [] args){ 

IRpcHelloService rpcHello = RpcProxy.create(IRpcHelloService.class); 

System.out.println(rpcHello.hello("Netty")); 

IRpcService service = RpcProxy.create(IRpcService.class); 

System.out.println(service.deleteUser(4)); 

System.out.println(service.updateUser(3)); 

System.out.println(service.queryUser(2)); 

System.out.println(service.addUser()); 

}

package com.rpc.consumer; 

import com.rpc.protocol.InvokerProtocol; 

import io.netty.bootstrap.Bootstrap; 

import io.netty.channel.*; 

import io.netty.channel.nio.NioEventLoopGroup; 

import io.netty.channel.socket.SocketChannel;

import io.netty.channel.socket.nio.NioSocketChannel; 

import io.netty.handler.codec.

LengthFieldBasedFrameDecoder; 

import io.netty.handler.codec.LengthFieldPrepender; 

import io.netty.handler.codec.serialization.ClassResolvers; 

import io.netty.handler.codec.serialization.ObjectDecoder; 

import io.netty.handler.codec.serialization.ObjectEncoder; 

import java.lang.reflect.InvocationHandler; 

import java.lang.reflect.Method; 

import java.lang.reflect.Proxy; 

public class RpcProxy { 

public static <T> T create(Class<?> clazz) { 

//clazz传进来本身就是interface 

MethodProxy proxy = new MethodProxy(clazz); 

Class<?>[] interfaces = clazz.isInterface() ? 

new Class[]{clazz} : 

clazz.getInterfaces(); 

T result = (T) Proxy.newProxyInstance(clazz.getClassLoader(), 

interfaces, proxy); 

return result; 

}

private static class MethodProxy implements InvocationHandler { 

private Class<?> clazz; 

public MethodProxy(Class<?> clazz) { 

this.clazz = clazz; 

}

public Object invoke(Object proxy, Method 

method, Object[] args) throws 

Throwable { 

//如果传进来是一个已实现的具体类 

if (Object.class.equals(method.

getDeclaringClass())) { 

try {

return method.invoke(this, args); 

} catch (Throwable t) { 

t.printStackTrace(); 

}

//如果传进来的是一个接口(核心) 

} else { 

return rpcInvoke(proxy, method, args); 

}

return null; 

}

/**

* 实现接口的核心方法 

*

* @param method 

* @param args 

* @return 

*/ 

public Object rpcInvoke(Object proxy, Method method, Object[] args) { 

//传输协议封装 

InvokerProtocol msg = new InvokerProtocol(); 

msg.setClassName(this.clazz.getName()); 

msg.setMethodName(method.getName()); 

msg.setValues(args); 

msg.setParames(method.getParameterTypes());

final RpcProxyHandler consumerHandler = new RpcProxyHandler(); 

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 pipeline = ch.pipeline(); 

//自定义协议解码器 

/** 入参有5个,分别解释如下 

maxFrameLength:框架的最大长度。

如果帧的长度大于此 值,则将抛出TooLongFrameException。

lengthFieldOffset:长度字段的偏移量:

即对应的长度字 段在整个消息数据中得位置 

lengthFieldLength:长度字段的长度:

如:长度字段是int 型表示,那么这个值就是

4(long型就是8)

lengthAdjustment:要添加到长度字段值的补偿值 

initialBytesToStrip:从解码帧中去除的第一个字节数 

*/ 

pipeline.addLast("frameDecoder", new 

LengthFieldBasedFrameDecoder

(Integer.MAX_VALUE, 0, 4, 0, 4)); 

//自定义协议编码器 

pipeline.addLast("frameEncoder", new 

LengthFieldPrepender(4)); 

//对象参数类型编码器 

pipeline.addLast("encoder", new 

ObjectEncoder()); 

//对象参数类型解码器 

pipeline.addLast("decoder", new 

ObjectDecoder( 

Integer.MAX_VALUE, 

ClassResolvers.cacheDisabled(null))); 

pipeline.addLast("handler", consumerHandler); 

}); 

ChannelFuture future = b.connect("localhost", 8888).sync(); 

future.channel().writeAndFlush(msg).sync(); 

future.channel().closeFuture().sync(); 

} catch (Exception e) { 

e.printStackTrace(); 

} finally { 

group.shutdownGracefully(); 

}

return consumerHandler.getResponse(); 

}

package com.rpc.consumer; 

import io.netty.channel.ChannelHandlerContext; 

import io.netty.channe

l.

ChannelInboundHandlerAdapter;public class RpcProxyHandler extends ChannelInboundHandlerAdapter { 

private Object response; 

public Object getResponse() { 

return response; 

}

@Override 

public void channelRead

(ChannelHandlerContext ctx, Object msg) throws 

Exception { 

response = msg; 

}

@Override 

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 

throws Exception { 

System.out.println("client exception is general"); 

}

今天的干货就到这啦!

​原创申明:本文由公众号【名师猿灯塔】原创,转载请说明出处标注

今天是猿灯塔“365天原创计划”第6天。

今天呢!灯塔君跟大家讲:     

       基于Netty重构RPC框架

一.CyclicBarrier方法说明

1.单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。。(最开始58 同城的站点架构用一个词概括就是“ALL IN ONE”。就像一个单机系统,所有的东西都部署在一台机器 上,包括站点、数据库、文件等等。而工程师每天的核心工作就是CURD,前端传过来一些数据,然后 业务逻辑层拼装成一些CURD访问数据库,数据库返回数据,数据拼装成页面,最终返回到浏览器。此 时,用于简化增删改查工作量的数据访问框架(ORM)是关键)

2、垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提 升效率。应用拆分为不相干的几个应用,前后端分离,此时用于加速前端页面开发的Web MVC框架是 关键

3、分布式服务服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定 的服务中心,使前端应用能更快速的响应多变的市场需求。同时将公共能力API抽取出来,作为独立的 公共服务供其他调用者消费,以实现服务的共享和重用,降低开发和运维成本。应用拆分之后会按照模 块独立部署,接口调用由本地API演进成跨进程的远程方法调用,此时RPC框架应运而生。此时,用于 提高业务复用及整合的分布式服务框架(RPC)是关键。

4、流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问 压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是 关键。面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆 分,并通过这些服务之间定义良好的接口和协议联系起来。

没有RPC框架之前,我们的服务调用是这样的:

看出接口的调用完全没有规律可循,想怎么调,就怎么调。这导致业务发展到一定阶段之后,对接口的 维护变得非常困难。于是有人提出了服务治理的概念。所有服务间不允许直接调用,而是先到注册中心 进行登记,再由注册中心统一协调和管理所有服务的状态并对外发布,调用者只需要记住服务名称,去 找注册中心获取服务即可。这样,极大地规范了服务的管理,可以提高了所有服务端可控性。整个设计思想其实在我们生活中也能 找到活生生的案例。例如:我们平时工作交流,大多都是用IM 工具,而不是面对面吼。大家只需要相 互记住运营商(也就是注册中心)提供的号码(如:腾讯QQ)即可。再比如:我们打电话,所有电话 号码有运营商分配。我们需要和某一个人通话时,只需要拨通对方的号码,运营商(注册中心,如中国 移动、中国联通、中国电信)就会帮我们将信号转接过去。

二.RPC介绍

1、RPC简介:

Remote Procedure Call远程过程调用RPC就是从一台机器(客户端)上通过参数传递的        方式调用另一台机器(服务器)上的一个函数或 方法(可以统称为服务)并得到返回的结果。

RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模 型。

客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数 (或方法)一样去调用远程的函数(或方法)。

 2、RPC通信原理

RPC的主要作用有三方面:

1、进程间通讯

2、提供和本地方法调用一样的机制

3、屏蔽用户对远程调用的细节实现

RPC框架的好处首先就是长链接,不必每次通信都要像http一样去3次握手,减少了网络开销;其次就 是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无 感知,统一化的操作。

3、RPC通过过程

1)服务消费方(client)调用以本地调用方式调用服务; 

2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体; 

3)client stub找到服务地址,并将消息发送到服务端; 

4)server stub收到消息后进行解码; 

5)server stub根据解码结果调用本地的服务;6)本地服务执行并将结果返回给server stub;7)server stub将返回结果打包成消息并发送至消费方; 

8)client stub接收到消息,并进行解码; 

9)服务消费方得到最终结果。

4、常用的分布式RPC框架

dubbo:国内最早开源的RPC框架,由阿里巴巴公司开发并于2011年末对外开源,仅支持java语言

4、常用的分布式RPC框架

dubbo:国内最早开源的RPC框架,由阿里巴巴公司开发并于2011年末对外开源,仅支持java语言

motan:微博内部使用的RPC框架,于2016年对外开源,仅支持java语言

Thrift:轻量级的跨语言RPC通信方案,支持多大25种变成语言

gRPC:Google于2015年对外开源的跨语言PRC框架,支持常用的C++、java、Python、Go、 Ruby、PHP等多种语言。

目前流行的RPC 服务治理框架主要有Dubbo 和Spring Cloud,

下面我们以比较经典的Dubbo 为例。

Dubbo 核心模块主要有:

Registry:注册中心(主要负责保存所有可用的服务名称和服务地址。) 

Provider:服务中心(实现对外提供的所有服务的具体功能) 

Consumer:消费端(调用远程服务的服务消费方) 

Monitor:监控中心(统计服务的调用次数和调用时间的监控中心) 

Container:服务运行容器

api:主要用来定义对外开放的功能与服务接口。protocol:主要定义自定义传输协议的内容

蓝色方框代表业务有交互,绿色方框代表只对Dubbo内部交互。蓝色虚线为初始化时调用,红色虚线为 运行时异步调用,红色实线为运行时同步调用

0、服务在容器中启动,加载,运行Provider

1、Provider在启动时,向Registry注册自己提供的服务 

2、Consumer在启动时,向Registry订阅自己所需的服务 

3、Registry给Consumer返回Provider的地址列表,如果Provider地址有变更(上线/下线机器), Registry将基于长连接推动变更数据给Consumer 

4、Consumer从Provider地址列表中,基于软负载均衡算法,选一台进行调用,如果失败,重试另一 台调用 

5、Consumer和Provider,在内存中累计调用次数和时间,定时每分钟一次将统计数据发送到Monitor

4.具体实现

1、api

package com.rpc.api; 

/**

* API模块,provider和Consumer都遵循API模块的

规范 * 用来定义对外开放的功能与服务接口 

*/ 

public interface IRpcHelloService { 

String hello(String name); 

}

package com.rpc.api; 

public interface IRpcService { 

/*增加用户*/ 

public String addUser(); 

/*删除用户*/ 

public String deleteUser(Integer id); 

/*修改用户*/ 

public String updateUser(Integer id); 

/*查询用户*/ 

public String queryUser(Integer id); 

}

2、Provider:服务中心

package com.rpc.provider; 

import com.rpc.api.IRpcService; 

public class RpcServiceImpl implements 

IRpcService { 

@Override 

public String addUser() { 

return "增加用户"; 

}

@Override 

public String deleteUser(Integer id) { 

return "删除了编号为"+id+"的用户"; 

}

@Override 

public String updateUser(Integer id) { 

return "修改了编号为"+id+"的用户"; 

}

@Override 

public String queryUser(Integer id) { 

return "查询到了编号为"+id+"这个用户的信息"; 

}

package com.rpc.provider; 

import com.rpc.api.IRpcHelloService; 

/**

* 服务中心,实现对外提供的所有服务的具体功能 

*/ 

public class RpcHelloServiceImpl implements IRpcHelloService { 

public String hello(String name) { 

return "Hello " + name + "!"; 

}

3、protocol传输协议内容

ackage com.rpc.protocol; 

import java.io.Serializable; 

/**

* 自定义传输协议内容 

*/ 

public class InvokerProtocol implements 

Serializable { 

private String className;//类名 

private String methodName;//函数名称 

private Class<?>[] parames;//形参列表

private Object[] values;//实参列表 

public String getClassName() { 

return className; 

}

public void setClassName(String className)

 { 

this.className = className; 

}

public String getMethodName() { 

return methodName; 

}

public void setMethodName

(String methodName)

 { 

this.methodName = methodName; 

}

public Class<?>[] getParames() { 

return parames; 

}

public void setParames(Class<?>[] parames) 

this.parames = parames; 

}

public Object[] getValues() { 

return values; 

}

public void setValues(Object[] values) { 

this.values = values; 

}

4、Registry注册中心

package com.rpc.registry; 

import io.netty.bootstrap.ServerBootstrap; 

import io.netty.channel.*; 

import io.netty.channel.nio.NioEventLoopGroup; 

import io.netty.channel.socket.SocketChannel; 

importio.netty.channel.socket.nio.

NioServerSocketChannel; 

import io.netty.handler.codec.

LengthFieldBasedFrameDecoder; 

import io.netty.handler.codec.LengthFieldPrepender; 

import io.netty.handler.codec.serialization.

ClassResolvers; 

import io.netty.handler.codec.serialization.

ObjectDecoder; 

import io.netty.handler.codec.serialization.

ObjectEncoder; 

/**

* 注册中心主要功能就是负责将所有Provider的

服务名称和服务引用地址注册到一个容器中, 

* 并对外发布。启动一个对外的服务,

并对外提供一个可访问的端口 

* 主要负责保存所有可用的服务名称和服务地址 

*/ 

public class RpcRegistry { 

private int port;

public RpcRegistry(int port){ 

this.port = port; 

}

public void start(){ 

EventLoopGroup bossGroup = new NioEventLoopGroup(); 

EventLoopGroup workerGroup = new NioEventLoopGroup(); 

try {

ServerBootstrap b = new ServerBootstrap(); 

b.group(bossGroup, workerGroup) 

.channel(NioServerSocketChannel.class) 

.childHandler(new ChannelInitializer<SocketChannel>() { 

@Override 

protected void initChannel(SocketChannel ch) throws 

Exception { 

ChannelPipeline pipeline = ch.pipeline(); 

//自定义协议解码器 

/** 入参有5个,分别解释如下 

(1) maxFrameLength - 发送的数据包最大长度; 

(2) lengthFieldOffset - 长度域偏移量,指的是长度域位于 

整个数据包字节数组中的下标; 

(3) lengthFieldLength - 长度域的自己的字节数长度。 

(4) lengthAdjustment – 长度域的偏移量矫正。如果长度域 

的值,除了包含有效数据域的长度外,

还包含了其他域(如长度域自身)长度,那么,

就需要进行矫正。矫 正的值为:包长 - 长度域的值 – 

长度域偏移 – 长度域长。 

(5) initialBytesToStrip – 丢弃的起始字节数。

丢弃处于有 效数据前面的字节数量。

比如前面有4个节点的长度域,则它的值为4。 

*/ 

pipeline.addLast(new 

LengthFieldBasedFrameDecoder(Integer.

MAX_VALUE, 0, 4, 0, 4)); 

//自定义协议编码器 

pipeline.addLast(new LengthFieldPrepender(4)); 

//对象参数类型编码器 

pipeline.addLast("encoder",new ObjectEncoder()); 

//对象参数类型解码器 

pipeline.addLast("decoder",new 

ObjectDecoder(Integer.MAX_VALUE, 

ClassResolvers.cacheDisabled(null))); 

pipeline.addLast(new RegistryHandler()); 

})

.option(ChannelOption.SO_BACKLOG, 128) 

.childOption(ChannelOption.SO_KEEPALIVE, true); 

ChannelFuture future = b.bind(port).sync(); 

System.out.println("jiangym RPC Registry 

start listen at " + port ); 

future.channel().closeFuture().sync(); 

} catch (Exception e) { 

bossGroup.shutdownGracefully(); 

workerGroup.shutdownGracefully(); 

}

//主启动类 

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

new RpcRegistry(8888).start(); 

}

package com.rpc.registry; 

import com.rpc.protocol.InvokerProtocol; 

import io.netty.channel.ChannelHandlerContext; 

import io.netty.channel.ChannelInboundHandlerAdapter; 

import java.io.File; 

import java.lang.reflect.Method; 

import java.net.URL; 

import java.util.ArrayList; 

import java.util.List; 

import java.util.concurrent.ConcurrentHashMap; 

public class RegistryHandler extends ChannelInboundHandlerAdapter { 

//保存所有可用的服务(ConcurrentHashMap

是线程安全且高效的HashMap实现) 

public static ConcurrentHashMap

<String, Object> 

registryMap = new ConcurrentHashMap<String, Object>(); 

//保存所有相关的服务类 

private List<String> classNames = new ArrayList<String>(); 

public RegistryHandler() { 

//完成递归扫描 

scannerClass("com.rpc.provider"); 

doRegister(); 

}

@Override 

public void channelRead(ChannelHandlerContext ctx, Object msg) throws 

Exception { 

Object result = new Object(); 

InvokerProtocol request = (InvokerProtocol) msg; 

//当客户端建立连接时,需要从自定义协议中获取信息,

拿到具体的服务和实参 

//使用反射调用 

if (registryMap.containsKey(request.getClassName())) { 

Object clazz = registryMap.get(request.getClassName()); 

Method method = clazz.getClass().getMethod(request.

getMethodName(), 

request.getParames()); 

result = method.invoke(clazz, 

request.getValues()); 

}

ctx.write(result); 

ctx.flush(); 

ctx.close(); 

}

@Override 

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 

throws Exception { 

cause.printStackTrace(); 

ctx.close(); 

}

/*

* 递归扫描 

*/ 

private void scannerClass(String packageName)

 { 

URL url = 

this.getClass().getClassLoader().getResource

(packageName.replaceAll("\\.", 

"/")); 

File dir = new File(url.getFile());

for (File file : dir.listFiles()) { 

//如果是一个文件夹,继续递归 

if (file.isDirectory()) { 

scannerClass(packageName + "." +

 file.getName()); 

} else { 

classNames.add(packageName + "." + 

file.getName().replace(".class", "").trim()); 

}

/**

* 完成注册 

*/ 

private void doRegister() { 

if (classNames.size() == 0) { 

return; 

}

for (String className : classNames) { 

try {

Class<?> clazz = Class.forName(className); 

Class<?> i = clazz.getInterfaces()[0]; 

registryMap.put(i.getName(),

clazz.newInstance()); 

} catch (Exception e) { 

e.printStackTrace(); 

}

4、消费端

package com.rpc.consumer; 

import com.rpc.api.IRpcHelloService; 

import com.rpc.api.IRpcService; 

public class RpcConsumer { 

public static void main(String [] args){ 

IRpcHelloService rpcHello = RpcProxy.create(IRpcHelloService.class); 

System.out.println(rpcHello.hello("Netty")); 

IRpcService service = RpcProxy.create(IRpcService.class); 

System.out.println(service.deleteUser(4)); 

System.out.println(service.updateUser(3)); 

System.out.println(service.queryUser(2)); 

System.out.println(service.addUser()); 

}

package com.rpc.consumer; 

import com.rpc.protocol.InvokerProtocol; 

import io.netty.bootstrap.Bootstrap; 

import io.netty.channel.*; 

import io.netty.channel.nio.NioEventLoopGroup; 

import io.netty.channel.socket.SocketChannel;

import io.netty.channel.socket.nio.NioSocketChannel; 

import io.netty.handler.codec.

LengthFieldBasedFrameDecoder; 

import io.netty.handler.codec.LengthFieldPrepender; 

import io.netty.handler.codec.serialization.ClassResolvers; 

import io.netty.handler.codec.serialization.ObjectDecoder; 

import io.netty.handler.codec.serialization.ObjectEncoder; 

import java.lang.reflect.InvocationHandler; 

import java.lang.reflect.Method; 

import java.lang.reflect.Proxy; 

public class RpcProxy { 

public static <T> T create(Class<?> clazz) { 

//clazz传进来本身就是interface 

MethodProxy proxy = new MethodProxy(clazz); 

Class<?>[] interfaces = clazz.isInterface() ? 

new Class[]{clazz} : 

clazz.getInterfaces(); 

T result = (T) Proxy.newProxyInstance(clazz.getClassLoader(), 

interfaces, proxy); 

return result; 

}

private static class MethodProxy implements InvocationHandler { 

private Class<?> clazz; 

public MethodProxy(Class<?> clazz) { 

this.clazz = clazz; 

}

public Object invoke(Object proxy, Method 

method, Object[] args) throws 

Throwable { 

//如果传进来是一个已实现的具体类 

if (Object.class.equals(method.

getDeclaringClass())) { 

try {

return method.invoke(this, args); 

} catch (Throwable t) { 

t.printStackTrace(); 

}

//如果传进来的是一个接口(核心) 

} else { 

return rpcInvoke(proxy, method, args); 

}

return null; 

}

/**

* 实现接口的核心方法 

*

* @param method 

* @param args 

* @return 

*/ 

public Object rpcInvoke(Object proxy, Method method, Object[] args) { 

//传输协议封装 

InvokerProtocol msg = new InvokerProtocol(); 

msg.setClassName(this.clazz.getName()); 

msg.setMethodName(method.getName()); 

msg.setValues(args); 

msg.setParames(method.getParameterTypes());

final RpcProxyHandler consumerHandler = new RpcProxyHandler(); 

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 pipeline = ch.pipeline(); 

//自定义协议解码器 

/** 入参有5个,分别解释如下 

maxFrameLength:框架的最大长度。

如果帧的长度大于此 值,则将抛出TooLongFrameException。

lengthFieldOffset:长度字段的偏移量:

即对应的长度字 段在整个消息数据中得位置 

lengthFieldLength:长度字段的长度:

如:长度字段是int 型表示,那么这个值就是

4(long型就是8)

lengthAdjustment:要添加到长度字段值的补偿值 

initialBytesToStrip:从解码帧中去除的第一个字节数 

*/ 

pipeline.addLast("frameDecoder", new 

LengthFieldBasedFrameDecoder

(Integer.MAX_VALUE, 0, 4, 0, 4)); 

//自定义协议编码器 

pipeline.addLast("frameEncoder", new 

LengthFieldPrepender(4)); 

//对象参数类型编码器 

pipeline.addLast("encoder", new 

ObjectEncoder()); 

//对象参数类型解码器 

pipeline.addLast("decoder", new 

ObjectDecoder( 

Integer.MAX_VALUE, 

ClassResolvers.cacheDisabled(null))); 

pipeline.addLast("handler", consumerHandler); 

}); 

ChannelFuture future = b.connect("localhost", 8888).sync(); 

future.channel().writeAndFlush(msg).sync(); 

future.channel().closeFuture().sync(); 

} catch (Exception e) { 

e.printStackTrace(); 

} finally { 

group.shutdownGracefully(); 

}

return consumerHandler.getResponse(); 

}

package com.rpc.consumer; 

import io.netty.channel.ChannelHandlerContext; 

import io.netty.channe

l.

ChannelInboundHandlerAdapter;public class RpcProxyHandler extends ChannelInboundHandlerAdapter { 

private Object response; 

public Object getResponse() { 

return response; 

}

@Override 

public void channelRead

(ChannelHandlerContext ctx, Object msg) throws 

Exception { 

response = msg; 

}

@Override 

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 

throws Exception { 

System.out.println("client exception is general"); 

}

今天的干货就到这啦!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容