Netty实现简单RPC

来源:互联网 发布:淘宝上最火的店铺 编辑:程序博客网 时间:2024/05/18 02:27

使用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> {@Overrideprotected 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的序列化方式。


3、ChannelHandler处理类即客户端请求的处理:

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 {@Overridepublic 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());}@Overridepublic 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即UUIDcache.put(requestInfo.getId(), null);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ResponseMessage<Object> res = (ResponseMessage<Object>) msg;LocalCache cache = LocalCache.instance();// 用读取到的响应消息覆盖原来的null值,关联关系UUIDcache.put(res.getId(), res);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {// 读完之后关闭channel// 这里也不需要关闭,因为在服务端向客户端写完消息后服务端会主动关闭,个人理解按照四次挥手所以服务端有了则客户端不用再close了// ctx.close();}// 出现异常触发该方法@Overridepublic 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> {@Overrideprotected 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;}/** * 接口方法的归宿 */@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 组装请求实体RequestMessage message = new RequestMessage();// 消息唯一idmessage.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 19999LocalCache size:7total time :8ms2w耗时: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();}


2、在处理完客户端的请求后服务端可以发起关闭连接请求,这样在客户端就不用重复发起关闭请求了

@Overridepublic 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


原创粉丝点击