11.1 RPCの基本的な紹介
-
RPC(リモートプロシージャコール)—リモートプロシージャコールは、コンピュータ通信プロトコルです。このプロトコルにより、プログラマーがこの対話をプログラムしなくても、あるコンピューターで実行されているプログラムが別のコンピューターのサブプログラムを呼び出すことができます。
-
2つ以上のアプリケーションが異なるサーバーに分散されており、それらの間の呼び出しはローカルメソッド呼び出しのようなものです
。3)一般的なRPCフレームワークは、Ali's Dubbo、GoogleのgRPC、Go言語など、比較的よく知られています。 rpcx、Apache thrift、
Springの下のSpring Cloud。
11.2 RPC呼び出しのフローチャート
11.3 PRCコールプロセスの説明
1)サービスコンシューマ(クライアント)が市内通話でサービスを呼び出す
2)クライアントスタブは、呼び出しを受信した後、メソッドやパラメータなどをネットワーク送信が可能なメッセージ本文にカプセル化します
。3)クライアントスタブは、メッセージをエンコードしてサーバーに送信します
。4)サーバースタブは、メッセージを受信してデコード
します。5)サーバースタブデコード結果に基づいてローカルサービスを呼び出します
6)ローカルサービスの実行と結果をサーバースタブに返します
7)サーバースタブは返されたインポート結果をエンコードしてコンシューマーに送信し
ます8)クライアントスタブはメッセージを受信してデコードします
9)サービス利用者(クライアント)が結果を取得します
概要:RPCの目標は、これらのステップ2〜8をカプセル化することです。ユーザーはこれらの詳細を気にする必要はありません。ローカルメソッドを呼び出すようなリモートサービス呼び出しを実行できます。
11.4ダボRPCを自分で実装する(Nettyに基づく)
11.4.1要件
- ダボの最下層では、ネットワーク通信フレームワークとしてNettyを使用しているため、Nettyは単純なRPCフレームワークを実装する必要があります。
- ダボを模倣して、コンシューマーとプロバイダーはインターフェイスとプロトコルについて合意します。コンシューマーはプロバイダーのサービスをリモートで呼び出します。プロバイダーは文字列を返し、コンシューマーはプロバイダーから返されたデータを出力します。基礎となるネットワーク通信はNetty 4.1.20を使用します
11.4.2設計手順
- インターフェースを作成し、抽象メソッドを定義します。消費者とプロバイダー間の合意に使用されます。
- プロバイダーを作成します。このクラスは、コンシューマーリクエストをリッスンし、規則に従ってデータを返す必要があります。
- コンシューマーを作成します。このクラスは、独自の存在しないメソッドを透過的に呼び出す必要があり、内部的にNettyを使用してプロバイダーにデータを返すよう要求する必要があります
- 分析チャートを開発
11.4.3コードの実装
//这个是接口,是服务提供方和 服务消费方都需要
public interface HelloService {
String hello(String mes);
}
public class HelloServiceImpl implements HelloService{
private static int count = 0;
//当有消费方调用该方法时, 就返回一个结果
@Override
public String hello(String mes) {
System.out.println("收到客户端消息=" + mes);
//根据mes 返回不同的结果
if(mes != null) {
return "你好客户端, 我已经收到你的消息 [" + mes + "] 第" + (++count) + " 次";
} else {
return "你好客户端, 我已经收到你的消息 ";
}
}
}
Server
//ServerBootstrap 会启动一个服务提供者,就是 NettyServer
public class ServerBootstrap {
public static void main(String[] args) {
NettyServer.startServer("127.0.0.1", 7000);
}
}
public class NettyServer {
public static void startServer(String hostName, int port) {
//不同的启动方式
startServer0(hostName,port);
}
//编写一个方法,完成对NettyServer的初始化和启动
private static void startServer0(String hostname, int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());//编码
pipeline.addLast(new StringEncoder());//解码
pipeline.addLast(new NettyServerHandler()); //业务处理器
}
}
);
ChannelFuture channelFuture = serverBootstrap.bind(hostname, port).sync();
System.out.println("服务提供方开始提供服务~~");
channelFuture.channel().closeFuture().sync();
}catch (Exception e) {
e.printStackTrace();
}
finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
//服务器这边handler比较简单
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取客户端发送的消息,并调用服务
System.out.println("msg=" + msg);
//客户端在调用服务器的api 时,我们需要定义一个协议
//比如我们要求 每次发消息是都必须以某个字符串开头 "HelloService#hello#你好"
if(msg.toString().startsWith(ClientBootstrap.providerName)) {
String result = new HelloServiceImpl().hello(msg.toString()
.substring(msg.toString().lastIndexOf("#") + 1));
ctx.writeAndFlush(result);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
Client
public class ClientBootstrap {
//这里自定义协议头
public static final String providerName = "HelloService#hello#";
public static void main(String[] args) throws Exception{
//创建一个消费者
NettyClient customer = new NettyClient();
//创建代理对象
HelloService service = (HelloService) customer.getBean(HelloService.class, providerName);
for (;; ) {
Thread.sleep(2 * 1000);
//通过代理对象调用服务提供者的方法(服务)
String res = service.hello("你好 dubbo~");
System.out.println("调用的结果 res= " + res);
}
}
}
public class NettyClient {
//创建线程池
private static ExecutorService executor = Executors.
newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static NettyClientHandler client;
private int count = 0;
//编写方法使用代理模式,获取一个代理对象 与服务器交互的自定义的协议信息
public Object getBean(final Class<?> serivceClass, final String providerName) {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{serivceClass}, (proxy, method, args) -> {
System.out.println("(proxy, method, args) 进入...." + (++count) + " 次");
//{} 部分的代码,客户端每调用一次 hello, 就会进入到该代码
if (client == null) {
initClient();
}
//设置要发给服务器端的信息
//providerName 协议头 args[0] 就是客户端调用api hello(???), 参数
client.setPara(providerName + args[0]);
return executor.submit(client).get();
});
}
//初始化客户端
private static void initClient() {
client = new NettyClientHandler();
//创建EventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(client);
}
}
);
try {
bootstrap.connect("127.0.0.1", 7000).sync();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {
private ChannelHandlerContext context;//上下文
private String result; //返回的结果
private String para; //客户端调用方法时,传入的参数
//与服务器的连接创建后,就会被调用, 这个方法是第一个被调用(1)
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(" channelActive 被调用 ");
context = ctx; //因为我们在其它方法会使用到 ctx,所以赋值传给了context
}
//收到服务器的数据后,调用方法 (4)
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(" channelRead 被调用 ");
result = msg.toString();
notify(); //唤醒等待的线程
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
//被代理对象调用, 发送数据给服务器,-> wait -> 等待被唤醒(channelRead) -> 返回结果 (3)-》5
@Override
public synchronized Object call() throws Exception {
System.out.println(" call1 被调用 ");
context.writeAndFlush(para);
//进行wait
wait(); //等待channelRead 方法获取到服务器的结果后,唤醒
System.out.println(" call2 被调用 ");
return result; //服务方返回的结果
}
//(2)
void setPara(String para) {
System.out.println(" setPara ");
this.para = para;
}
}