粗漢手撕RPC核心原理-Netty版
文章目錄
源碼地址: https://gitee.com/kylin1991_admin/mini-tomcat/tree/master/nio-tomcat
環境準備
1、maven
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<!-- 5 版本已經被丟棄了-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.48.Final</version>
</dependency>
2、插件:Lombok
1、protocol協議包
- 直接用一個協議類就好了,用於數據傳輸過程的包裝類
@Data
public class InvokeProtocol implements Serializable {
private String className;
private String methodName;
private Class<?>[] params;
private Object[] values;
}
2、registry註冊中心
a、RpcRegistry註冊中心類
- 主要就是服務開啓。用netty來開啓服務
public class RpcRegistry {
private static final int PORT = 8080;
public static void main(String[] args) {
new RpcRegistry().start();
}
private void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap registry = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel serverSocketChannel) throws Exception {
ChannelPipeline pipeline = serverSocketChannel.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast(new RegistryHandler());
}
});
try {
ChannelFuture f = registry.bind(this.PORT).sync();
System.out.println("start rpc registry success , prot : " + this.PORT);
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
b、RegistryHandler初始化邏輯類
- 邏輯處理的,主要是初始化容器,然後用於回調。其實這裏也可以用Proxy來遠程調用。
- 所以真正的情況就是consumer遠程調用。provider遠程調用。簡化簡化哈哈
public class RegistryHandler extends ChannelInboundHandlerAdapter {
private static final List<String> classNames = new ArrayList<>();
private static final Map<String, Object> serverMap = new HashMap<>();
// 指定掃描的實現路徑,正常的情況是去讀取配置文件,爲了方便
private static final String PACKAGE_NAME = "org.example.provider";
public RegistryHandler() {
scannerClassName(PACKAGE_NAME);
doRegistry();
}
private void doRegistry() {
if (classNames == null) return;
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
Class<?> server = clazz.getInterfaces()[0];
serverMap.put(server.getName(), clazz.newInstance());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
private void scannerClassName(String packageName) {
URL url = this.getClass().getResource("/" + packageName.replaceAll("\\.", "/"));
File files = new File(url.getFile());
for (File file : files.listFiles()) {
if (file.isDirectory()) {
scannerClassName(file.getName());
} else {
classNames.add(packageName + "." + file.getName().replace(".class", ""));
}
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Object result = null;
if (msg instanceof InvokeProtocol) {
InvokeProtocol request = (InvokeProtocol) msg;
if (serverMap.containsKey(request.getClassName())) {
Object service = serverMap.get(request.getClassName());
Method method = service.getClass().getMethod(request.getMethodName(), request.getParams());
result = method.invoke(service, request.getValues());
}
}
ctx.writeAndFlush(result);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("registry exception ");
throw new RuntimeException(cause.getMessage());
}
}
3、api
- 用於提供接口使用的jar包
public interface IHelloService {
String sayHello(String content);
}
4、provider服務提供方
public class HelloServiceImpl implements IHelloService {
@Override
public String sayHello(String content) {
System.out.println("request in :" + content);
return "SUCCESS";
}
}
5、Consumer客戶調用方
還是要用AOP代理的思想
a、Consumer調用類
public class Consumer {
public static void main(String[] args) {
IHelloService helloService = ProxyClient.create(IHelloService.class);
System.out.println(helloService.sayHello("七樓"));
}
}
b、RpcProxyClient代理類
- 利用的jdk的動態代理,然後代理時採用netty來做客戶端請求
public class RpcProxyClient {
public static <T> T create(Class<?> interfacesCls) {
return (T) Proxy.newProxyInstance(interfacesCls.getClassLoader(), new Class<?>[]{interfacesCls}, new ProxyHandler());
}
private static class ProxyHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
InvokeProtocol request = new InvokeProtocol();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParams(method.getParameterTypes());
request.setValues(args);
RemoteHandler remoteHandler = new RemoteHandler();
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap client = new Bootstrap()
.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast(remoteHandler);
}
});
ChannelFuture f = client.connect("localhost", 8080).sync();
f.channel().writeAndFlush(request).sync();
f.channel().closeFuture().sync();
workerGroup.shutdownGracefully();
return remoteHandler.getResult();
}
}
}
c、RemoteHandler遠程調用邏輯類
- 接受返回結果
public class RemoteHandler extends ChannelInboundHandlerAdapter {
private Object result;
public Object getResult() {
return result;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
result = msg;
super.channelRead(ctx, msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("rpc registry exception");
throw new RuntimeException(cause.getMessage());
}
}
6、測試服務類
-
啓動RpcRegistry
-
直接consumer上操作就好了
好了。至此。直接啓動Netty版的RPC就完成了就可以訪問了。