使用Netty实现简单RPC,主要是netty的使用,所以缓存,服务的暴露等都使用缓存来代替了
netty使用的是5.0+ 所以和4.0+可能有点不同……
下面先看一下主要类:
从两个方面来看,一是netty部分,netty客户端和服务端的实现 二是从调用netty来看,其实也就是代理的实现。
一、netty服务端
因为这里是单线程实现所以服务端和我们平时写的都是一样的。
1、ServerBootstrap 服务引导:
package com.netty.server;
import com.netty.common.constants.Constants;
import com.netty.common.utils.LoadService;
import com.netty.core.ServerInitializerChannel;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* 服务端类,启动时加载暴露的服务
*
* @author whd
* @date 2017年12月9日 下午1:52:06
*/
public class ServerStart {
private void init() {
// 服务端的引导
ServerBootstrap strap = new ServerBootstrap();
// 服务端接收事件
EventLoopGroup boss = new NioEventLoopGroup();
// 服务端处理事件
EventLoopGroup work = new NioEventLoopGroup();
strap.group(boss, work);
strap.channel(NioServerSocketChannel.class);
strap.option(ChannelOption.SO_BACKLOG, 1000);
strap.childHandler(new ServerInitializerChannel());
try {
ChannelFuture future = strap.bind(Constants.IP, Constants.PORT).sync();
future.addListener(future1 -> {
if(future1.isSuccess()){
System.out.println("server statr success ……");
}
});
future.channel().closeFuture().sync();
} catch (Exception e) {
System.err.println(e.getMessage());
//在出现异常的时候平滑的释放资源,也就是关闭线程池,正常请情况下不用释放线程资源,因为一直有请求,如果释放了则创建线程又要消耗一部分资源
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
public static void main(String[] args) {
ServerStart start = new ServerStart();
LoadService.loadService();
start.init();
}
}
其中的LoadService就是接口暴露的服务
2、Pipeline配置类:package com.netty.core;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
/**
* 服务端channel流处理链
*
* @author whd
* @date 2017年12月6日 下午9:46:15
*/
public class ServerInitializerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
ChannelPipeline pipeline = sc.pipeline();
pipeline.addLast("encoderOfJKD", new EncoderOfJDK());
pipeline.addLast("decoderOfJDK", new DecoderOfJDK());
pipeline.addLast("serverChanneHandler", new ServerHandler());
}
}
Channel通道的处理类,两个编解码一个服务端处理类,这里的编解码的序列化使用了jdk的序列化方式。
package com.netty.core;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.netty.common.pojo.RequestMessage;
import com.netty.common.pojo.ResponseMessage;
import com.netty.common.utils.MapperClassInterface;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 服务端处理类,处理客户端请求
*
* @author whd
* @date 2017年12月9日 下午4:42:14
*/
public class ServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws UnsupportedEncodingException, InstantiationException, IllegalAccessException, ClassNotFoundException,
NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
RequestMessage info = (RequestMessage) msg;
String className = MapperClassInterface.instance().get(info.getClassName());
Object obj = Class.forName(className).newInstance();
String methodName = info.getMethodName();
Class<?>[] type = info.getParamType();
Object[] param = info.getParams();
Method method = obj.getClass().getMethod(methodName, type);
Object res = method.invoke(obj, param);
ResponseMessage<Object> resInfo = new ResponseMessage<>();
resInfo.setId(info.getId());
resInfo.setCode(200);
resInfo.setMsg("ok");
resInfo.setData(res);
ctx.write(resInfo);
System.out.println("server res:"+resInfo.getId()+" "+(String)resInfo.getData());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
//这里ctx所属的channel就是客户端连接服务端的channel,这里的关闭应该也是这个channel的关闭,close应该是会引发tcp四次挥手
ctx.flush();
ctx.close();
}
}
二、netty客户端
1、请求发送和返回消息处理类
package com.netty.core;
import com.netty.common.pojo.RequestMessage;
import com.netty.common.pojo.ResponseMessage;
import com.netty.common.utils.LocalCache;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class ClientHandler extends ChannelHandlerAdapter {
/** 这个处理类所属的channel */
private Channel channel;
/**
* 一个channel(通道)有一个对应的channelPipeline所以我们通过channel可以获取到具体处理类的
* 这个方法就是在通道注册完后获取这个处理方法所属的channel
*/
public void channelRegistered(ChannelHandlerContext ctx) {
channel = ctx.channel();
}
/**
* 向channel中write消息 缓存消息id
*
* @param obj
* @throws NoSuchMethodException
* @throws SecurityException
*/
public void sendMessage(Object obj) throws NoSuchMethodException, SecurityException {
channel.writeAndFlush(obj);
RequestMessage requestInfo = (RequestMessage) obj;
LocalCache cache = LocalCache.instance();
//缓存消息唯一id即UUID
cache.put(requestInfo.getId(), null);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ResponseMessage<Object> res = (ResponseMessage<Object>) msg;
LocalCache cache = LocalCache.instance();
// 用读取到的响应消息覆盖原来的null值,关联关系UUID
cache.put(res.getId(), res);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// 读完之后关闭channel
// 这里也不需要关闭,因为在服务端向客户端写完消息后服务端会主动关闭,个人理解按照四次挥手所以服务端有了则客户端不用再close了
// ctx.close();
}
// 出现异常触发该方法
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.err.println("clientHandler error:" + cause.getMessage());
ctx.close();
}
}
其中sendMessage是我们自己订单的,在代理类中调用该方法进行发送消息,而channelRead方法是用来获取服务端返回消息的
2、客户端Pipeline类
package com.netty.core;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
/**
* netty 客户端channel 流处理链
*
* @author whd
* @date 2017年12月6日 下午9:45:36
*/
public class ClientInitializerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
ChannelPipeline pipeline = sc.pipeline();
pipeline.addLast("encoderOfJKD", new EncoderOfJDK());
pipeline.addLast("decoderOfJDK", new DecoderOfJDK());
pipeline.addLast("clientChanneHandler", new ClientHandler());
}
}
这里同样是编解码类和客户端自定义处理类!
3、客户端引导类
package com.netty.client;
import com.netty.common.constants.Constants;
import com.netty.common.pojo.RequestMessage;
import com.netty.common.utils.LocalCache;
import com.netty.core.ClientHandler;
import com.netty.core.ClientInitializerChannel;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* netty客户端引导类
*
* @author whd
* @date 2017年12月10日 下午5:55:22
*/
public class ClientStart {
private ClientStart() {
};
private static class InnerClass {
private static final ClientStart clientStart = new ClientStart();
}
public static ClientStart instance() {
return InnerClass.clientStart;
}
/**
* 配置客户端信息并连接服务端
*
* @param message
* @throws InterruptedException
*/
public void init(RequestMessage message) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup work = new NioEventLoopGroup();
bootstrap.group(work);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new ClientInitializerChannel());
try {
ChannelFuture future = bootstrap.connect(Constants.IP, Constants.PORT);
// 对连接添加监听
future.addListener(status -> {
if (status.isSuccess()) {
// 从通道获取属于该通道的处理链,从处理链中获取具体的一个处理类实例
ClientHandler handler = future.channel().pipeline().get(ClientHandler.class);
// 发送消息,其实也就是调用channel.write()向channel中写入消息
handler.sendMessage(message);
}
});
// 处理客户端向服务端的请求和服务端返回数据的获取的线程
Object obj = null;
long total;
long begin = System.currentTimeMillis();
do {
// 从缓存中按照UUID获取对象
obj = LocalCache.instance().get(message.getId());
total = System.currentTimeMillis() - begin;
// 首先netty是非阻塞的其次网络传输是有延迟的所以这里要等待一下
} while (null == obj && total < Constants.MAX_WAIT_MILLSECOND);
} finally {
// 这个资源一定要释放否则,运行循环1000多次就会内存溢出
work.shutdownGracefully();
}
}
}
(1)、在客户端和往常的demo不同的应该也就是客户端自定义处理类和引导类,其中的处理类中我们添加了一个sendMessage方法,这个方法使用channel的write方法来发送客户端请求,
(2)、而引导类中不同的就是我们通过ChannelFuture 获得channel在从channel中获得该channel的Pipeline,在从这个处理链中获得具体的我们定义的处理类,这样我们就能在这里主动调用处理类中的sendMessage发送消息了!
(3)、当发送完消息后我们拿着UUID从缓存中获取返回的消息对象,但是服务端的处理和网络有延时所以我们要等待一下,所以这里添加了do{}while().
(4)、最后我们要注意,finally{} 我们一定要释放资源,个人经历如果这里不释放资源则客户端循环调用1000次左右内存用完(eden 使用率100% ParOldGen 使用率100%然后本机cpu爆满,内存用完……)
OK这里我们netty相关的完了,下面看看java的动态代理是怎么调用netty来发送请求的!
三、为接口生成动态代理,调用Netty发送请求
1、InvocationHandler实现类
package com.netty.common.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.UUID;
import com.netty.client.ClientStart;
import com.netty.common.pojo.RequestMessage;
import com.netty.common.pojo.ResponseMessage;
/**
* 处理类,其实接口代理类方法中最终都会调用这里的invoke方法来执行相关处理
*
* @author whd
* @date 2017年12月9日 下午4:25:55
*/
public class SendMessageProxy implements InvocationHandler {
private SendMessageProxy() {
}
/**
* 静态内部类实现单列
*
* @author whd
* @date 2017年12月9日 下午4:27:14
*/
private static class InnerClass {
private static final SendMessageProxy proxy = new SendMessageProxy();
}
/**
* 获取实例
*
* @return
*/
public static SendMessageProxy instance() {
return InnerClass.proxy;
}
/**
* 接口方法的归宿
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 组装请求实体
RequestMessage message = new RequestMessage();
// 消息唯一id
message.setId(UUID.randomUUID().toString());
message.setClassName(method.getDeclaringClass().getName());
message.setMethodName(method.getName());
message.setParamType(method.getParameterTypes());
message.setParams(args);
//启动一个netty客户端,调用方法发送请求
ClientStart.instance().init(message);
ResponseMessage<?> response;
LocalCache localCache = LocalCache.instance();
//按照UUID从缓存中获取服务端返回数据
response = localCache.get(message.getId());
//释放资源
localCache.remove(message.getId());
message = null;
if (null == response) {
return null;
}
return response.getData();
}
}
2、获取接口动态代理类
package com.netty.common.utils;
import java.lang.reflect.Proxy;
/**
* 代理类
*
* @author whd
* @date 2017年12月9日 下午4:25:18
*/
@SuppressWarnings("unchecked")
public class SendMessage {
private SendMessage() {
};
private static class InnerClass {
private static final SendMessage message = new SendMessage();
}
public static SendMessage instance() {
return InnerClass.message;
}
/**
* 获取接口代理类
*
* @param classInfo
* @return
*/
public <T> T execture(Class<T> classInfo) {
SendMessageProxy sendMessageProxy = SendMessageProxy.instance();
T t = (T) Proxy.newProxyInstance(classInfo.getClassLoader(), new Class[] { classInfo }, sendMessageProxy);
return t;
}
}
3、接口调用相应方法
(1)、生成接口代理类
(2)、代理类调用方法触发invoke方法
(3)、invoke方法中获取请求信息,组装请求对象、调用netty方法发送请求、从缓存获取返回结果、return 结果
import java.io.IOException;
import com.netty.common.pojo.Person;
import com.netty.common.utils.LocalCache;
import com.netty.common.utils.SendMessage;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
long staret = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
long begin = System.currentTimeMillis();
SendMessage messages = SendMessage.instance();
Person person = messages.execture(Person.class);
Object res = person.say(" hello " + i);
System.err.println("res:" + String.valueOf(res == null ? "is null" : res));
LocalCache localCache = LocalCache.instance();
int size = localCache.getSize();
System.out.println("LocalCache size:" + size);
long end = System.currentTimeMillis();
System.out.println("total time :" + (end - begin) + "ms");
}
long ends = System.currentTimeMillis();
System.out.println("2w耗时:" + (ends - staret));
}
}
res:Student hello 19999
LocalCache size:7
total time :8ms
2w耗时:294166
在2w次的请求中有7次请求失败,每次从客户端发送请求到获取响应对象耗时5-8ms左右,2w请求的所有执行时间在5分钟左右,这些都是在自己的一天机器上跑的,所以网络延时当然是没有了……
OK 到这里这个简单的rpc就完成了,个人能力有限就先学到这里,之后当然会有多线程、zookeeper暴露服务的版本……
总结:
还是总结一下,因为写的时候有几个地方要注意一下,不然可能会出现内存等问题:
1、服务端的线程池在正常执行的情况下是不用关闭的,只有在出现异常的时候关闭,因为服务端一直会收到客户端的请求,所以复用线程池节省资源。
try {
ChannelFuture future = strap.bind(Constants.IP, Constants.PORT).sync();
future.addListener(future1 -> {
if(future1.isSuccess()){
System.out.println("server statr success ……");
}
});
future.channel().closeFuture().sync();
} catch (Exception e) {
System.err.println(e.getMessage());
//在出现异常的时候平滑的释放资源,也就是关闭线程池,正常请情况下不用释放线程资源,因为一直有请求,如果释放了则创建线程又要消耗一部分资源
boss.shutdownGracefully();
work.shutdownGracefully();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
//这里ctx所属的channel就是客户端连接服务端的channel,这里的关闭应该也是这个channel的关闭,close应该是会引发tcp四次挥手
ctx.flush();
ctx.close();
}
3、客户端每次请求后好关闭这次创建的线程池
try {
ChannelFuture future = bootstrap.connect(Constants.IP, Constants.PORT);
// 对连接添加监听
future.addListener(status -> {
if (status.isSuccess()) {
// 从通道获取属于该通道的处理链,从处理链中获取具体的一个处理类实例
ClientHandler handler = future.channel().pipeline().get(ClientHandler.class);
// 发送消息,其实也就是调用channel.write()向channel中写入消息
handler.sendMessage(message);
}
});
// 处理客户端向服务端的请求和服务端返回数据的获取的线程
Object obj = null;
long total;
long begin = System.currentTimeMillis();
do {
// 从缓存中按照UUID获取对象
obj = LocalCache.instance().get(message.getId());
total = System.currentTimeMillis() - begin;
// 首先netty是非阻塞的其次网络传输是有延迟的所以这里要等待一下
} while (null == obj && total < Constants.MAX_WAIT_MILLSECOND);
} finally {
// 这个资源一定要释放否则,运行循环1000多次就会内存溢出
work.shutdownGracefully();
}
finally中的关闭一定要执行,客户端端和服务端不同,客户端没请求一次会创建一个服务该channel的线程池,所以当channel关闭的时候要释放服务于该channel的线程池,否则后果很严重……
4、NioSocketChannel 和NioServerSocketChannel 之间有关闭单是不关联,NioSocketChannel 就是说底层使用socketChannel 而NioServerSocketChannel 则是使用serverSocketChannel,如果你看了之前复用器的文章你应该明白socketChannel 是客户端向服务端的连接,客户端向服务端的请求和服务端的响应都是通过socketChannel 来传递的,而serverSocketChannel则是监听端口,也就是监听有没有客户端连接,这样说来他们是没有关联的。
但是还是有联系的serverSocketChannel可以监听到socketChannel所以我们也是从serverSocketChannel中获取socketChannel这个对象,来从中获取请求消息和从服务器向客户端写消息,socketChannel是双向的即可写也可读。
代码下载:http://download.csdn.net/download/qh_java/10153097